Makes the main status of a thread appear as a more detailed view.

This commit is contained in:
Vavassor 2017-08-03 00:29:31 -04:00
commit 309c89eefc
9 changed files with 565 additions and 38 deletions

View file

@ -51,7 +51,6 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
private View container;
private TextView displayName;
private TextView username;
private TextView sinceCreated;
private TextView content;
private ImageView avatar;
private ImageView avatarReblog;
@ -74,12 +73,14 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
private TextView contentWarningDescription;
private ToggleButton contentWarningButton;
TextView timestamp;
StatusViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.status_container);
displayName = (TextView) itemView.findViewById(R.id.status_display_name);
username = (TextView) itemView.findViewById(R.id.status_username);
sinceCreated = (TextView) itemView.findViewById(R.id.status_since_created);
timestamp = (TextView) itemView.findViewById(R.id.status_timestamp);
content = (TextView) itemView.findViewById(R.id.status_content);
avatar = (ImageView) itemView.findViewById(R.id.status_avatar);
avatarReblog = (ImageView) itemView.findViewById(R.id.status_avatar_reblog);
@ -147,19 +148,21 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
.into(avatar);
}
if (hasReblog) {
avatarReblog.setVisibility(View.VISIBLE);
Picasso.with(context)
.load(rebloggedUrl)
.fit()
.transform(new RoundedTransformation(7, 0))
.into(avatarReblog);
} else {
avatarReblog.setVisibility(View.GONE);
if (avatarReblog != null) {
if (hasReblog) {
avatarReblog.setVisibility(View.VISIBLE);
Picasso.with(context)
.load(rebloggedUrl)
.fit()
.transform(new RoundedTransformation(7, 0))
.into(avatarReblog);
} else {
avatarReblog.setVisibility(View.GONE);
}
}
}
private void setCreatedAt(@Nullable Date createdAt) {
protected void setCreatedAt(@Nullable Date createdAt) {
// This is the visible timestamp.
String readout;
/* This one is for screen-readers. Frequently, they would mispronounce timestamps like "17m"
@ -168,7 +171,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
if (createdAt != null) {
long then = createdAt.getTime();
long now = new Date().getTime();
readout = DateUtils.getRelativeTimeSpanString(sinceCreated.getContext(), then, now);
readout = DateUtils.getRelativeTimeSpanString(timestamp.getContext(), then, now);
readoutAloud = android.text.format.DateUtils.getRelativeTimeSpanString(then, now,
android.text.format.DateUtils.SECOND_IN_MILLIS,
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE);
@ -177,11 +180,14 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
readout = "?m";
readoutAloud = "? minutes";
}
sinceCreated.setText(readout);
sinceCreated.setContentDescription(readoutAloud);
timestamp.setText(readout);
timestamp.setContentDescription(readoutAloud);
}
private void setRebloggedByDisplayName(String name) {
if (rebloggedByDisplayName == null || rebloggedBar == null) {
return;
}
Context context = rebloggedByDisplayName.getContext();
String format = context.getString(R.string.status_boosted_format);
String boostedText = String.format(format, name);
@ -190,6 +196,9 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
}
private void hideRebloggedByDisplayName() {
if (rebloggedBar == null) {
return;
}
rebloggedBar.setVisibility(View.GONE);
}
@ -529,11 +538,13 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
// 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());
}
});
if (rebloggedBar != null) {
rebloggedBar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onOpenReblog(getAdapterPosition());
}
});
}
}
}

View file

@ -15,42 +15,83 @@
package com.keylesspalace.tusky.adapter;
import android.content.Context;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomTabURLSpan;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class ThreadAdapter extends RecyclerView.Adapter {
private static final int VIEW_TYPE_STATUS = 0;
private static final int VIEW_TYPE_STATUS_DETAILED = 1;
private List<StatusViewData> statuses;
private StatusActionListener statusActionListener;
private boolean mediaPreviewEnabled;
private int detailedStatusPosition;
public ThreadAdapter(StatusActionListener listener) {
this.statusActionListener = listener;
this.statuses = new ArrayList<>();
mediaPreviewEnabled = true;
detailedStatusPosition = RecyclerView.NO_POSITION;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_status, parent, false);
return new StatusViewHolder(view);
switch (viewType) {
default:
case VIEW_TYPE_STATUS: {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_status, parent, false);
return new StatusViewHolder(view);
}
case VIEW_TYPE_STATUS_DETAILED: {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_status_detailed, parent, false);
return new StatusDetailedViewHolder(view);
}
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
StatusViewHolder holder = (StatusViewHolder) viewHolder;
StatusViewData status = statuses.get(position);
holder.setupWithStatus(status,
statusActionListener, mediaPreviewEnabled);
if (position == detailedStatusPosition) {
StatusDetailedViewHolder holder = (StatusDetailedViewHolder) viewHolder;
StatusViewData status = statuses.get(position);
holder.setupWithStatus(status, statusActionListener, mediaPreviewEnabled);
} else {
StatusViewHolder holder = (StatusViewHolder) viewHolder;
StatusViewData status = statuses.get(position);
holder.setupWithStatus(status, statusActionListener, mediaPreviewEnabled);
}
}
@Override
public int getItemViewType(int position) {
if (position == detailedStatusPosition) {
return VIEW_TYPE_STATUS_DETAILED;
} else {
return VIEW_TYPE_STATUS;
}
}
@Override
@ -72,6 +113,7 @@ public class ThreadAdapter extends RecyclerView.Adapter {
public void clearItems() {
int oldSize = statuses.size();
statuses.clear();
detailedStatusPosition = RecyclerView.NO_POSITION;
notifyItemRangeRemoved(0, oldSize);
}
@ -88,15 +130,85 @@ public class ThreadAdapter extends RecyclerView.Adapter {
public void clear() {
statuses.clear();
detailedStatusPosition = RecyclerView.NO_POSITION;
notifyDataSetChanged();
}
public void setItem(int position, StatusViewData status, boolean notifyAdapter) {
statuses.set(position, status);
if (notifyAdapter) notifyItemChanged(position);
if (notifyAdapter) {
notifyItemChanged(position);
}
}
public void setMediaPreviewEnabled(boolean enabled) {
mediaPreviewEnabled = enabled;
}
public void setDetailedStatusPosition(int position) {
if (position != detailedStatusPosition
&& detailedStatusPosition != RecyclerView.NO_POSITION) {
int prior = detailedStatusPosition;
detailedStatusPosition = position;
notifyItemChanged(prior);
} else {
detailedStatusPosition = position;
}
}
private static class StatusDetailedViewHolder extends StatusViewHolder {
private TextView reblogs;
private TextView favourites;
private TextView application;
StatusDetailedViewHolder(View view) {
super(view);
reblogs = (TextView) view.findViewById(R.id.status_reblogs);
favourites = (TextView) view.findViewById(R.id.status_favourites);
application = (TextView) view.findViewById(R.id.status_application);
}
@Override
protected void setCreatedAt(@Nullable Date createdAt) {
if (createdAt != null) {
DateFormat dateFormat = android.text.format.DateFormat.getMediumDateFormat(
timestamp.getContext());
timestamp.setText(dateFormat.format(createdAt));
} else {
timestamp.setText("");
}
}
private void setApplication(@Nullable Status.Application app) {
if (app == null) {
return;
}
if (app.website != null) {
URLSpan span;
Context context = application.getContext();
boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean("customTabs", true);
if (useCustomTabs) {
span = new CustomTabURLSpan(app.website);
} else {
span = new URLSpan(app.website);
}
SpannableStringBuilder text = new SpannableStringBuilder(app.name);
text.setSpan(span, 0, app.name.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
application.setText(text);
application.setMovementMethod(LinkMovementMethod.getInstance());
} else {
application.setText(app.name);
}
}
@Override
void setupWithStatus(StatusViewData status, final StatusActionListener listener,
boolean mediaPreviewEnabled) {
super.setupWithStatus(status, listener, mediaPreviewEnabled);
reblogs.setText(status.getReblogsCount());
favourites.setText(status.getFavouritesCount());
setApplication(status.getApplication());
}
}
}

View file

@ -100,6 +100,8 @@ public class Status {
public Mention[] mentions;
public Application application;
public static final int MAX_MEDIA_ATTACHMENTS = 4;
@Override
@ -172,4 +174,9 @@ public class Status {
@SerializedName("username")
public String localUsername;
}
public static class Application {
public String name;
public String website;
}
}

View file

@ -270,6 +270,7 @@ public class ViewThreadFragment extends SFragment implements
}
}
statusIndex = statuses.indexOf(status);
adapter.setDetailedStatusPosition(statusIndex);
adapter.setStatuses(statuses.getPairedCopy());
}
@ -344,6 +345,7 @@ public class ViewThreadFragment extends SFragment implements
}
int i = statusIndex;
statuses.add(i, status);
adapter.setDetailedStatusPosition(i);
adapter.addItem(i, statuses.getPairedItem(i));
return i;
}
@ -362,6 +364,7 @@ public class ViewThreadFragment extends SFragment implements
// Insert newly fetched ancestors
statusIndex = ancestors.size();
adapter.setDetailedStatusPosition(statusIndex);
statuses.addAll(0, ancestors);
List<StatusViewData> ancestorsViewDatas = statuses.getPairedCopy().subList(0, statusIndex);
if (BuildConfig.DEBUG && ancestors.size() != ancestorsViewDatas.size()) {

View file

@ -14,8 +14,8 @@ import android.view.View;
import com.keylesspalace.tusky.R;
class CustomTabURLSpan extends URLSpan {
CustomTabURLSpan(String url) {
public class CustomTabURLSpan extends URLSpan {
public CustomTabURLSpan(String url) {
super(url);
}

View file

@ -26,6 +26,8 @@ public final class ViewDataUtils {
.setAvatar(visibleStatus.account.avatar)
.setContent(visibleStatus.content)
.setCreatedAt(visibleStatus.createdAt)
.setReblogsCount(visibleStatus.reblogsCount)
.setFavouritesCount(visibleStatus.favouritesCount)
.setFavourited(visibleStatus.favourited)
.setReblogged(visibleStatus.reblogged)
.setIsExpanded(false)
@ -40,6 +42,7 @@ public final class ViewDataUtils {
.setVisibility(visibleStatus.visibility)
.setSenderId(visibleStatus.account.id)
.setRebloggingEnabled(visibleStatus.rebloggingAllowed())
.setApplication(visibleStatus.application)
.createStatusViewData();
}

View file

@ -31,19 +31,23 @@ public final class StatusViewData {
private final String nickname;
private final String avatar;
private final Date createdAt;
private final String reblogsCount;
private final String favouritesCount;
// I would rather have something else but it would be too much of a rewrite
@Nullable
private final Status.Mention[] mentions;
private final String senderId;
private final boolean rebloggingEnabled;
private final Status.Application application;
public StatusViewData(String id, Spanned content, boolean reblogged, boolean favourited,
String spoilerText, Status.Visibility visibility,
Status.MediaAttachment[] attachments, String rebloggedByUsername,
String rebloggedAvatar, boolean sensitive, boolean isExpanded,
boolean isShowingSensitiveWarning, String userFullName, String nickname,
String avatar, Date createdAt, Status.Mention[] mentions,
String senderId, boolean rebloggingEnabled) {
String avatar, Date createdAt, String reblogsCount,
String favouritesCount, Status.Mention[] mentions, String senderId,
boolean rebloggingEnabled, Status.Application application) {
this.id = id;
this.content = content;
this.reblogged = reblogged;
@ -60,9 +64,12 @@ public final class StatusViewData {
this.nickname = nickname;
this.avatar = avatar;
this.createdAt = createdAt;
this.reblogsCount = reblogsCount;
this.favouritesCount = favouritesCount;
this.mentions = mentions;
this.senderId = senderId;
this.rebloggingEnabled = rebloggingEnabled;
this.application = application;
}
public String getId() {
@ -132,6 +139,14 @@ public final class StatusViewData {
return createdAt;
}
public String getReblogsCount() {
return reblogsCount;
}
public String getFavouritesCount() {
return favouritesCount;
}
public String getSenderId() {
return senderId;
}
@ -145,6 +160,10 @@ public final class StatusViewData {
return mentions;
}
public Status.Application getApplication() {
return application;
}
public static class Builder {
private String id;
private Spanned content;
@ -162,9 +181,12 @@ public final class StatusViewData {
private String nickname;
private String avatar;
private Date createdAt;
private String reblogsCount;
private String favouritesCount;
private Status.Mention[] mentions;
private String senderId;
private boolean rebloggingEnabled;
private Status.Application application;
public Builder() {
}
@ -186,9 +208,12 @@ public final class StatusViewData {
nickname = viewData.nickname;
avatar = viewData.avatar;
createdAt = new Date(viewData.createdAt.getTime());
reblogsCount = viewData.reblogsCount;
favouritesCount = viewData.favouritesCount;
mentions = viewData.mentions == null ? null : viewData.mentions.clone();
senderId = viewData.senderId;
rebloggingEnabled = viewData.rebloggingEnabled;
application = viewData.application;
}
public Builder setId(String id) {
@ -271,6 +296,16 @@ public final class StatusViewData {
return this;
}
public Builder setReblogsCount(String reblogsCount) {
this.reblogsCount = reblogsCount;
return this;
}
public Builder setFavouritesCount(String favouritesCount) {
this.favouritesCount = favouritesCount;
return this;
}
public Builder setMentions(Status.Mention[] mentions) {
this.mentions = mentions;
return this;
@ -286,11 +321,17 @@ public final class StatusViewData {
return this;
}
public Builder setApplication(Status.Application application) {
this.application = application;
return this;
}
public StatusViewData createStatusViewData() {
return new StatusViewData(id, content, reblogged, favourited, spoilerText, visibility,
attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded,
isShowingSensitiveContent, userFullName, nickname, avatar, createdAt, mentions,
senderId, rebloggingEnabled);
isShowingSensitiveContent, userFullName, nickname, avatar, createdAt,
reblogsCount, favouritesCount, mentions, senderId, rebloggingEnabled,
application);
}
}
}