Preserve status states on updates. UI layer refactoring.
Some things were pulled out of adapters to fragments. New classes were introduced - StatusViewData and NotificationViewData. They not only have view state in them but also help decoupling. Because introducing parallel model list requires a lot of synchronisation PairedList was added. Also synchronisation between fragments and adapters is quiet tedious and error-prone and should be replaces with better solution. Oh, I also couldn’t resist and fixed bug with buttons animation in the same commit.
This commit is contained in:
parent
f68f6d7473
commit
90c1a83ba4
15 changed files with 1194 additions and 358 deletions
|
@ -31,13 +31,13 @@ import android.widget.TextView;
|
|||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.entity.Notification;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.viewdata.NotificationViewData;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
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 {
|
||||
|
@ -46,16 +46,14 @@ 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;
|
||||
|
||||
private List<Notification> notifications;
|
||||
private List<NotificationViewData> notifications;
|
||||
private StatusActionListener statusListener;
|
||||
private NotificationActionListener notificationActionListener;
|
||||
private FooterViewHolder.State footerState;
|
||||
private boolean mediaPreviewEnabled;
|
||||
private String bottomId;
|
||||
private String topId;
|
||||
|
||||
public NotificationsAdapter(StatusActionListener statusListener,
|
||||
NotificationActionListener notificationActionListener) {
|
||||
NotificationActionListener notificationActionListener) {
|
||||
super();
|
||||
notifications = new ArrayList<>();
|
||||
this.statusListener = statusListener;
|
||||
|
@ -94,28 +92,29 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
|
|||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
if (position < notifications.size()) {
|
||||
Notification notification = notifications.get(position);
|
||||
Notification.Type type = notification.type;
|
||||
NotificationViewData notification = notifications.get(position);
|
||||
Notification.Type type = notification.getType();
|
||||
switch (type) {
|
||||
case MENTION: {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = notification.status;
|
||||
holder.setupWithStatus(status, statusListener, mediaPreviewEnabled);
|
||||
StatusViewData status = notification.getStatusViewData();
|
||||
holder.setupWithStatus(status,
|
||||
statusListener, mediaPreviewEnabled);
|
||||
break;
|
||||
}
|
||||
case FAVOURITE:
|
||||
case REBLOG: {
|
||||
StatusNotificationViewHolder holder = (StatusNotificationViewHolder) viewHolder;
|
||||
holder.setMessage(type, notification.account.getDisplayName(),
|
||||
notification.status);
|
||||
holder.setupButtons(notificationActionListener, notification.account.id);
|
||||
holder.setMessage(type, notification.getStatusViewData().getUserFullName(),
|
||||
notification.getStatusViewData());
|
||||
holder.setupButtons(notificationActionListener, notification.getAccount().id);
|
||||
break;
|
||||
}
|
||||
case FOLLOW: {
|
||||
FollowViewHolder holder = (FollowViewHolder) viewHolder;
|
||||
holder.setMessage(notification.account.getDisplayName(),
|
||||
notification.account.username, notification.account.avatar);
|
||||
holder.setupButtons(notificationActionListener, notification.account.id);
|
||||
holder.setMessage(notification.getAccount().getDisplayName(),
|
||||
notification.getAccount().username, notification.getAccount().avatar);
|
||||
holder.setupButtons(notificationActionListener, notification.getAccount().id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -135,8 +134,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
|
|||
if (position == notifications.size()) {
|
||||
return VIEW_TYPE_FOOTER;
|
||||
} else {
|
||||
Notification notification = notifications.get(position);
|
||||
switch (notification.type) {
|
||||
NotificationViewData notification = notifications.get(position);
|
||||
switch (notification.getType()) {
|
||||
default:
|
||||
case MENTION: {
|
||||
return VIEW_TYPE_MENTION;
|
||||
|
@ -160,9 +159,9 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
|
|||
|
||||
@Override
|
||||
public void removeAllByAccountId(String id) {
|
||||
for (int i = 0; i < notifications.size();) {
|
||||
Notification notification = notifications.get(i);
|
||||
if (id.equals(notification.account.id)) {
|
||||
for (int i = 0; i < notifications.size(); ) {
|
||||
NotificationViewData notification = notifications.get(i);
|
||||
if (id.equals(notification.getAccount().id)) {
|
||||
notifications.remove(i);
|
||||
notifyItemRemoved(i);
|
||||
} else {
|
||||
|
@ -172,61 +171,31 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public Notification getItem(int position) {
|
||||
public NotificationViewData getItem(int position) {
|
||||
if (position >= 0 && position < notifications.size()) {
|
||||
return notifications.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void update(@Nullable List<Notification> newNotifications, @Nullable String fromId,
|
||||
@Nullable String uptoId) {
|
||||
public void update(@Nullable List<NotificationViewData> newNotifications) {
|
||||
if (newNotifications == null || newNotifications.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (fromId != null) {
|
||||
bottomId = fromId;
|
||||
}
|
||||
if (uptoId != null) {
|
||||
topId = uptoId;
|
||||
}
|
||||
if (notifications.isEmpty()) {
|
||||
// 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++) {
|
||||
notifications.remove(0);
|
||||
}
|
||||
int newIndex = newNotifications.indexOf(notifications.get(0));
|
||||
if (newIndex == -1) {
|
||||
notifications.addAll(0, newNotifications);
|
||||
} else {
|
||||
notifications.addAll(0, newNotifications.subList(0, newIndex));
|
||||
}
|
||||
}
|
||||
notifications.clear();
|
||||
notifications.addAll(newNotifications);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void addItems(List<Notification> newNotifications, @Nullable String fromId) {
|
||||
if (fromId != null) {
|
||||
bottomId = fromId;
|
||||
}
|
||||
int end = notifications.size();
|
||||
Notification last = notifications.get(end - 1);
|
||||
if (last != null && !findNotification(newNotifications, last.id)) {
|
||||
notifications.addAll(newNotifications);
|
||||
notifyItemRangeInserted(end, newNotifications.size());
|
||||
}
|
||||
public void updateItemWithNotify(int position, NotificationViewData notification,
|
||||
boolean notifyAdapter) {
|
||||
notifications.set(position, notification);
|
||||
if (notifyAdapter) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private static boolean findNotification(List<Notification> notifications, String id) {
|
||||
for (Notification notification : notifications) {
|
||||
if (notification.id.equals(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
public void addItems(List<NotificationViewData> newNotifications, @Nullable String fromId) {
|
||||
notifications.addAll(newNotifications);
|
||||
notifyItemRangeInserted(notifications.size(), newNotifications.size());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
@ -238,16 +207,6 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
|
|||
footerState = newFooterState;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getBottomId() {
|
||||
return bottomId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getTopId() {
|
||||
return topId;
|
||||
}
|
||||
|
||||
public void setMediaPreviewEnabled(boolean enabled) {
|
||||
mediaPreviewEnabled = enabled;
|
||||
}
|
||||
|
@ -314,7 +273,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
|
|||
container = (ViewGroup) itemView.findViewById(R.id.notification_container);
|
||||
}
|
||||
|
||||
void setMessage(Notification.Type type, String displayName, Status status) {
|
||||
void setMessage(Notification.Type type, String displayName, StatusViewData status) {
|
||||
Context context = message.getContext();
|
||||
String format;
|
||||
switch (type) {
|
||||
|
@ -339,7 +298,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter implements Adapte
|
|||
str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
message.setText(str);
|
||||
statusContent.setText(status.content);
|
||||
statusContent.setText(status.getContent());
|
||||
}
|
||||
|
||||
void setupButtons(final NotificationActionListener listener, final String accountId) {
|
||||
|
|
|
@ -38,13 +38,14 @@ import com.keylesspalace.tusky.entity.Status;
|
|||
import com.keylesspalace.tusky.util.DateUtils;
|
||||
import com.keylesspalace.tusky.util.LinkHelper;
|
||||
import com.keylesspalace.tusky.util.ThemeUtils;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.varunest.sparkbutton.SparkButton;
|
||||
import com.varunest.sparkbutton.SparkEventListener;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||
public class StatusViewHolder extends RecyclerView.ViewHolder {
|
||||
private View container;
|
||||
private TextView displayName;
|
||||
private TextView username;
|
||||
|
@ -173,7 +174,7 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
reblogButton.setChecked(reblogged);
|
||||
}
|
||||
|
||||
/** This should only be called after setReblogged, in order to override the tint correctly. */
|
||||
// This should only be called after setReblogged, in order to override the tint correctly.
|
||||
private void setRebloggingEnabled(boolean enabled, Status.Visibility visibility) {
|
||||
reblogButton.setEnabled(enabled);
|
||||
|
||||
|
@ -202,7 +203,7 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
|
||||
private void setMediaPreviews(final Status.MediaAttachment[] attachments, boolean sensitive,
|
||||
final StatusActionListener listener) {
|
||||
final StatusActionListener listener, boolean showingSensitive) {
|
||||
final ImageView[] previews = {
|
||||
mediaPreview0,
|
||||
mediaPreview1,
|
||||
|
@ -257,10 +258,13 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
|
||||
if (sensitive) {
|
||||
sensitiveMediaWarning.setVisibility(View.VISIBLE);
|
||||
sensitiveMediaWarning.setVisibility(showingSensitive ? View.GONE : View.VISIBLE);
|
||||
sensitiveMediaWarning.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onContentHiddenChange(true, getAdapterPosition());
|
||||
}
|
||||
v.setVisibility(View.GONE);
|
||||
v.setOnClickListener(null);
|
||||
}
|
||||
|
@ -277,23 +281,29 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
private static String getLabelTypeText(Context context, Status.MediaAttachment.Type type) {
|
||||
switch (type) {
|
||||
default:
|
||||
case IMAGE: return context.getString(R.string.status_media_images);
|
||||
case IMAGE:
|
||||
return context.getString(R.string.status_media_images);
|
||||
case GIFV:
|
||||
case VIDEO: return context.getString(R.string.status_media_video);
|
||||
case VIDEO:
|
||||
return context.getString(R.string.status_media_video);
|
||||
}
|
||||
}
|
||||
|
||||
private static @DrawableRes int getLabelIcon(Status.MediaAttachment.Type type) {
|
||||
private static
|
||||
@DrawableRes
|
||||
int getLabelIcon(Status.MediaAttachment.Type type) {
|
||||
switch (type) {
|
||||
default:
|
||||
case IMAGE: return R.drawable.ic_photo_24dp;
|
||||
case IMAGE:
|
||||
return R.drawable.ic_photo_24dp;
|
||||
case GIFV:
|
||||
case VIDEO: return R.drawable.ic_videocam_24dp;
|
||||
case VIDEO:
|
||||
return R.drawable.ic_videocam_24dp;
|
||||
}
|
||||
}
|
||||
|
||||
private void setMediaLabel(Status.MediaAttachment[] attachments, boolean sensitive,
|
||||
final StatusActionListener listener) {
|
||||
final StatusActionListener listener) {
|
||||
if (attachments.length == 0) {
|
||||
mediaLabel.setVisibility(View.GONE);
|
||||
return;
|
||||
|
@ -334,15 +344,17 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
sensitiveMediaWarning.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setSpoilerText(String spoilerText) {
|
||||
private void setSpoilerText(String spoilerText, final boolean expanded, final StatusActionListener listener) {
|
||||
contentWarningDescription.setText(spoilerText);
|
||||
contentWarningBar.setVisibility(View.VISIBLE);
|
||||
content.setVisibility(View.GONE);
|
||||
contentWarningButton.setChecked(false);
|
||||
contentWarningButton.setChecked(expanded);
|
||||
contentWarningButton.setOnCheckedChangeListener(
|
||||
new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
listener.onExpandedChange(isChecked, getAdapterPosition());
|
||||
}
|
||||
if (isChecked) {
|
||||
content.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
|
@ -350,6 +362,11 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
}
|
||||
});
|
||||
if (expanded) {
|
||||
content.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
content.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideSpoilerText() {
|
||||
|
@ -378,19 +395,21 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
});
|
||||
reblogButton.setEventListener(new SparkEventListener() {
|
||||
@Override
|
||||
public void onEvent(ImageView button, boolean buttonState) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onReblog(!reblogged, position);
|
||||
}
|
||||
@Override
|
||||
public void onEvent(ImageView button, boolean buttonState) {
|
||||
int position = getAdapterPosition();
|
||||
if (position != RecyclerView.NO_POSITION) {
|
||||
listener.onReblog(!reblogged, position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEventAnimationEnd(ImageView button, boolean buttonState) {}
|
||||
@Override
|
||||
public void onEventAnimationEnd(ImageView button, boolean buttonState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEventAnimationStart(ImageView button, boolean buttonState) {}
|
||||
@Override
|
||||
public void onEventAnimationStart(ImageView button, boolean buttonState) {
|
||||
}
|
||||
});
|
||||
favouriteButton.setEventListener(new SparkEventListener() {
|
||||
@Override
|
||||
|
@ -402,10 +421,12 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onEventAnimationEnd(ImageView button, boolean buttonState) {}
|
||||
public void onEventAnimationEnd(ImageView button, boolean buttonState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEventAnimationStart(ImageView button, boolean buttonState) {}
|
||||
public void onEventAnimationStart(ImageView button, boolean buttonState) {
|
||||
}
|
||||
});
|
||||
moreButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
|
@ -433,27 +454,25 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
container.setOnClickListener(viewThreadListener);
|
||||
}
|
||||
|
||||
void setupWithStatus(Status status, final StatusActionListener listener,
|
||||
void setupWithStatus(StatusViewData status, final StatusActionListener listener,
|
||||
boolean mediaPreviewEnabled) {
|
||||
Status realStatus = status.getActionableStatus();
|
||||
|
||||
setDisplayName(realStatus.account.getDisplayName());
|
||||
setUsername(realStatus.account.username);
|
||||
setCreatedAt(realStatus.createdAt);
|
||||
setContent(realStatus.content, realStatus.mentions, listener);
|
||||
setAvatar(realStatus.account.avatar);
|
||||
setReblogged(realStatus.reblogged);
|
||||
setFavourited(realStatus.favourited);
|
||||
String rebloggedByDisplayName = status.account.getDisplayName();
|
||||
if (status.reblog == null) {
|
||||
setDisplayName(status.getUserFullName());
|
||||
setUsername(status.getNickname());
|
||||
setCreatedAt(status.getCreatedAt());
|
||||
setContent(status.getContent(), status.getMentions(), listener);
|
||||
setAvatar(status.getAvatar());
|
||||
setReblogged(status.isReblogged());
|
||||
setFavourited(status.isFavourited());
|
||||
String rebloggedByDisplayName = status.getRebloggedByUsername();
|
||||
if (rebloggedByDisplayName == null) {
|
||||
hideRebloggedByDisplayName();
|
||||
} else {
|
||||
setRebloggedByDisplayName(rebloggedByDisplayName);
|
||||
}
|
||||
Status.MediaAttachment[] attachments = realStatus.attachments;
|
||||
boolean sensitive = realStatus.sensitive;
|
||||
Status.MediaAttachment[] attachments = status.getAttachments();
|
||||
boolean sensitive = status.isSensitive();
|
||||
if (mediaPreviewEnabled) {
|
||||
setMediaPreviews(attachments, sensitive, listener);
|
||||
setMediaPreviews(attachments, sensitive, listener, status.isShowingSensitiveContent());
|
||||
/* A status without attachments is sometimes still marked sensitive, so it's necessary
|
||||
* to check both whether there are any attachments and if it's marked sensitive. */
|
||||
if (!sensitive || attachments.length == 0) {
|
||||
|
@ -475,12 +494,12 @@ class StatusViewHolder extends RecyclerView.ViewHolder {
|
|||
videoIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setupButtons(listener, realStatus.account.id);
|
||||
setRebloggingEnabled(status.rebloggingAllowed(), status.getVisibility());
|
||||
if (realStatus.spoilerText.isEmpty()) {
|
||||
setupButtons(listener, status.getSenderId());
|
||||
setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
|
||||
if (status.getSpoilerText() == null || status.getSpoilerText().isEmpty()) {
|
||||
hideSpoilerText();
|
||||
} else {
|
||||
setSpoilerText(realStatus.spoilerText);
|
||||
setSpoilerText(status.getSpoilerText(), status.isExpanded(), listener);
|
||||
}
|
||||
|
||||
// I think it's not efficient to create new object every time we bind a holder.
|
||||
|
|
|
@ -21,23 +21,20 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||
private List<Status> statuses;
|
||||
public class ThreadAdapter extends RecyclerView.Adapter {
|
||||
private List<StatusViewData> statuses;
|
||||
private StatusActionListener statusActionListener;
|
||||
private int statusIndex;
|
||||
private boolean mediaPreviewEnabled;
|
||||
|
||||
public ThreadAdapter(StatusActionListener listener) {
|
||||
this.statusActionListener = listener;
|
||||
this.statuses = new ArrayList<>();
|
||||
this.statusIndex = 0;
|
||||
mediaPreviewEnabled = true;
|
||||
}
|
||||
|
||||
|
@ -51,8 +48,9 @@ public class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRe
|
|||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = statuses.get(position);
|
||||
holder.setupWithStatus(status, statusActionListener, mediaPreviewEnabled);
|
||||
StatusViewData status = statuses.get(position);
|
||||
holder.setupWithStatus(status,
|
||||
statusActionListener, mediaPreviewEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,77 +58,42 @@ public class ThreadAdapter extends RecyclerView.Adapter implements AdapterItemRe
|
|||
return statuses.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeItem(int position) {
|
||||
statuses.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
public void setStatuses(List<StatusViewData> statuses) {
|
||||
this.statuses.clear();
|
||||
this.statuses.addAll(statuses);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllByAccountId(String accountId) {
|
||||
for (int i = 0; i < statuses.size();) {
|
||||
Status status = statuses.get(i);
|
||||
if (accountId.equals(status.account.id)) {
|
||||
statuses.remove(i);
|
||||
notifyItemRemoved(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
public void addItem(int position, StatusViewData statusViewData) {
|
||||
statuses.add(position, statusViewData);
|
||||
notifyItemInserted(position);
|
||||
}
|
||||
|
||||
public Status getItem(int position) {
|
||||
return statuses.get(position);
|
||||
}
|
||||
|
||||
public int setStatus(Status status) {
|
||||
if (statuses.size() > 0
|
||||
&& statusIndex < statuses.size()
|
||||
&& statuses.get(statusIndex).equals(status)) {
|
||||
// Do not add this status on refresh, it's already in there.
|
||||
statuses.set(statusIndex, status);
|
||||
return statusIndex;
|
||||
}
|
||||
int i = statusIndex;
|
||||
statuses.add(i, status);
|
||||
notifyItemInserted(i);
|
||||
return i;
|
||||
}
|
||||
|
||||
public void setContext(List<Status> ancestors, List<Status> descendants) {
|
||||
Status mainStatus = null;
|
||||
|
||||
// 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
|
||||
public void clearItems() {
|
||||
int oldSize = statuses.size();
|
||||
if (oldSize > 1) {
|
||||
mainStatus = statuses.get(statusIndex);
|
||||
statuses.clear();
|
||||
notifyItemRangeRemoved(0, oldSize);
|
||||
}
|
||||
statuses.clear();
|
||||
notifyItemRangeRemoved(0, oldSize);
|
||||
}
|
||||
|
||||
// Insert newly fetched ancestors
|
||||
statusIndex = ancestors.size();
|
||||
statuses.addAll(0, ancestors);
|
||||
notifyItemRangeInserted(0, statusIndex);
|
||||
public void addAll(int position, List<StatusViewData> statuses) {
|
||||
this.statuses.addAll(position, statuses);
|
||||
notifyItemRangeInserted(position, statuses.size());
|
||||
}
|
||||
|
||||
if (mainStatus != null) {
|
||||
// In case we needed to delete everything (which is way easier than deleting
|
||||
// everything except one), re-insert the remaining status here.
|
||||
statuses.add(statusIndex, mainStatus);
|
||||
notifyItemInserted(statusIndex);
|
||||
}
|
||||
|
||||
// Insert newly fetched descendants
|
||||
public void addAll(List<StatusViewData> statuses) {
|
||||
int end = statuses.size();
|
||||
statuses.addAll(descendants);
|
||||
notifyItemRangeInserted(end, descendants.size());
|
||||
this.statuses.addAll(statuses);
|
||||
notifyItemRangeInserted(end, statuses.size());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
statuses.clear();
|
||||
notifyDataSetChanged();
|
||||
statusIndex = 0;
|
||||
}
|
||||
|
||||
public void setItem(int position, StatusViewData status, boolean notifyAdapter) {
|
||||
statuses.set(position, status);
|
||||
if (notifyAdapter) notifyItemChanged(position);
|
||||
}
|
||||
|
||||
public void setMediaPreviewEnabled(boolean enabled) {
|
||||
|
|
|
@ -22,24 +22,20 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
|
||||
import com.keylesspalace.tusky.R;
|
||||
import com.keylesspalace.tusky.interfaces.AdapterItemRemover;
|
||||
import com.keylesspalace.tusky.interfaces.StatusActionListener;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItemRemover {
|
||||
public class TimelineAdapter extends RecyclerView.Adapter {
|
||||
private static final int VIEW_TYPE_STATUS = 0;
|
||||
private static final int VIEW_TYPE_FOOTER = 1;
|
||||
|
||||
private List<Status> statuses;
|
||||
private List<StatusViewData> statuses;
|
||||
private StatusActionListener statusListener;
|
||||
private FooterViewHolder.State footerState;
|
||||
private boolean mediaPreviewEnabled;
|
||||
private String topId;
|
||||
private String bottomId;
|
||||
|
||||
public TimelineAdapter(StatusActionListener statusListener) {
|
||||
super();
|
||||
|
@ -70,7 +66,7 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
|
|||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
|
||||
if (position < statuses.size()) {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
Status status = statuses.get(position);
|
||||
StatusViewData status = statuses.get(position);
|
||||
holder.setupWithStatus(status, statusListener, mediaPreviewEnabled);
|
||||
} else {
|
||||
FooterViewHolder holder = (FooterViewHolder) viewHolder;
|
||||
|
@ -92,73 +88,23 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeItem(int position) {
|
||||
statuses.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllByAccountId(String accountId) {
|
||||
for (int i = 0; i < statuses.size();) {
|
||||
Status status = statuses.get(i);
|
||||
if (accountId.equals(status.account.id)) {
|
||||
statuses.remove(i);
|
||||
notifyItemRemoved(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update(@Nullable List<Status> newStatuses, @Nullable String fromId,
|
||||
@Nullable String uptoId) {
|
||||
public void update(@Nullable List<StatusViewData> newStatuses) {
|
||||
if (newStatuses == null || newStatuses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (fromId != null) {
|
||||
bottomId = fromId;
|
||||
}
|
||||
if (uptoId != null) {
|
||||
topId = uptoId;
|
||||
}
|
||||
if (statuses.isEmpty()) {
|
||||
// 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++) {
|
||||
statuses.remove(0);
|
||||
}
|
||||
int newIndex = newStatuses.indexOf(statuses.get(0));
|
||||
if (newIndex == -1) {
|
||||
statuses.addAll(0, newStatuses);
|
||||
} else {
|
||||
statuses.addAll(0, newStatuses.subList(0, newIndex));
|
||||
}
|
||||
}
|
||||
statuses.clear();
|
||||
statuses.addAll(newStatuses);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void addItems(List<Status> newStatuses, @Nullable String fromId) {
|
||||
if (fromId != null) {
|
||||
bottomId = fromId;
|
||||
}
|
||||
int end = statuses.size();
|
||||
Status last = statuses.get(end - 1);
|
||||
if (last != null && !findStatus(newStatuses, last.id)) {
|
||||
statuses.addAll(newStatuses);
|
||||
notifyItemRangeInserted(end, newStatuses.size());
|
||||
}
|
||||
public void addItems(List<StatusViewData> newStatuses) {
|
||||
statuses.addAll(newStatuses);
|
||||
notifyItemRangeInserted(statuses.size(), 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 changeItem(int position, StatusViewData newData, boolean notifyAdapter) {
|
||||
statuses.set(position, newData);
|
||||
if (notifyAdapter) notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
@ -166,13 +112,6 @@ public class TimelineAdapter extends RecyclerView.Adapter implements AdapterItem
|
|||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Status getItem(int position) {
|
||||
if (position >= 0 && position < statuses.size()) {
|
||||
return statuses.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setFooterState(FooterViewHolder.State newFooterState) {
|
||||
FooterViewHolder.State oldValue = footerState;
|
||||
|
@ -185,14 +124,4 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue