show status edits (#3049)

* show status edits part 1

* show status edits part 2 - load status edits

* fix code formatting

* add dialog to show status edits

* small improvements

* use ALIGN_CENTER to position status visibility icon when possible

* rename status_timestamp_info view to status_meta_info

* make dateFormat static

* remove commented-out code

* move edits to dedicated fragment
This commit is contained in:
Konrad Pozniak 2023-01-02 14:09:18 +01:00 committed by GitHub
commit 61a45ae376
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 731 additions and 75 deletions

View file

@ -436,7 +436,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
statusNameBar = itemView.findViewById(R.id.status_name_bar);
displayName = itemView.findViewById(R.id.status_display_name);
username = itemView.findViewById(R.id.status_username);
timestampInfo = itemView.findViewById(R.id.status_timestamp_info);
timestampInfo = itemView.findViewById(R.id.status_meta_info);
statusContent = itemView.findViewById(R.id.notification_content);
statusAvatar = itemView.findViewById(R.id.notification_status_avatar);
notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar);

View file

@ -38,7 +38,9 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
private var emojis: List<Emoji> = emptyList()
private var resultClickListener: View.OnClickListener? = null
private var animateEmojis = false
private var enabled = true
@JvmOverloads
fun setup(
options: List<PollOptionViewData>,
voteCount: Int,
@ -46,7 +48,8 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
emojis: List<Emoji>,
mode: Int,
resultClickListener: View.OnClickListener?,
animateEmojis: Boolean
animateEmojis: Boolean,
enabled: Boolean = true
) {
this.pollOptions = options
this.voteCount = voteCount
@ -55,6 +58,7 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
this.mode = mode
this.resultClickListener = resultClickListener
this.animateEmojis = animateEmojis
this.enabled = enabled
notifyDataSetChanged()
}
@ -82,6 +86,9 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
radioButton.visible(mode == SINGLE)
checkBox.visible(mode == MULTIPLE)
radioButton.isEnabled = enabled
checkBox.isEnabled = enabled
when (mode) {
RESULT -> {
val percent = calculatePercent(option.votesCount, votersCount, voteCount)

View file

@ -94,7 +94,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
private ImageView avatarInset;
public ImageView avatar;
public TextView timestampInfo;
public TextView metaInfo;
public TextView content;
public TextView contentWarningDescription;
@ -123,7 +123,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
super(itemView);
displayName = itemView.findViewById(R.id.status_display_name);
username = itemView.findViewById(R.id.status_username);
timestampInfo = itemView.findViewById(R.id.status_timestamp_info);
metaInfo = itemView.findViewById(R.id.status_meta_info);
content = itemView.findViewById(R.id.status_content);
avatar = itemView.findViewById(R.id.status_avatar);
replyButton = itemView.findViewById(R.id.status_reply);
@ -310,7 +310,12 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
protected void setCreatedAt(Date createdAt, Date editedAt, StatusDisplayOptions statusDisplayOptions) {
protected void setMetaData(StatusViewData.Concrete statusViewData, StatusDisplayOptions statusDisplayOptions, StatusActionListener listener) {
Status status = statusViewData.getActionable();
Date createdAt = status.getCreatedAt();
Date editedAt = status.getEditedAt();
String timestampText;
if (statusDisplayOptions.useAbsoluteTime()) {
timestampText = absoluteTimeFormatter.format(createdAt, true);
@ -320,15 +325,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
} else {
long then = createdAt.getTime();
long now = System.currentTimeMillis();
String readout = TimestampUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now);
String readout = TimestampUtils.getRelativeTimeSpanString(metaInfo.getContext(), then, now);
timestampText = readout;
}
}
if (editedAt != null) {
timestampText = timestampInfo.getContext().getString(R.string.post_timestamp_with_edited_indicator, timestampText);
timestampText = metaInfo.getContext().getString(R.string.post_timestamp_with_edited_indicator, timestampText);
}
timestampInfo.setText(timestampText);
metaInfo.setText(timestampText);
}
private CharSequence getCreatedAtDescription(Date createdAt,
@ -715,7 +720,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
Status actionable = status.getActionable();
setDisplayName(actionable.getAccount().getName(), actionable.getAccount().getEmojis(), statusDisplayOptions);
setUsername(status.getUsername());
setCreatedAt(actionable.getCreatedAt(), actionable.getEditedAt(), statusDisplayOptions);
setMetaData(status, statusDisplayOptions, listener);
setIsReply(actionable.getInReplyToId() != null);
setReplyCount(actionable.getRepliesCount());
setAvatar(actionable.getAccount().getAvatar(), status.getRebloggedAvatar(),
@ -767,7 +772,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
if (payloads instanceof List)
for (Object item : (List<?>) payloads) {
if (Key.KEY_CREATED.equals(item)) {
setCreatedAt(status.getActionable().getCreatedAt(), status.getActionable().getEditedAt(), statusDisplayOptions);
setMetaData(status, statusDisplayOptions, listener);
}
}
@ -849,7 +854,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
}
}
private static CharSequence getVisibilityDescription(Context context, Status.Visibility visibility) {
protected static CharSequence getVisibilityDescription(Context context, Status.Visibility visibility) {
if (visibility == null) {
return "";
@ -1138,7 +1143,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
avatarInset.setVisibility(visibility);
displayName.setVisibility(visibility);
username.setVisibility(visibility);
timestampInfo.setVisibility(visibility);
metaInfo.setVisibility(visibility);
contentWarningDescription.setVisibility(visibility);
contentWarningButton.setVisibility(visibility);
content.setVisibility(visibility);

View file

@ -2,13 +2,18 @@ package com.keylesspalace.tusky.adapter;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ImageSpan;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.recyclerview.widget.RecyclerView;
import com.keylesspalace.tusky.R;
@ -16,19 +21,20 @@ import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.NoUnderlineURLSpan;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class StatusDetailedViewHolder extends StatusBaseViewHolder {
private final TextView reblogs;
private final TextView favourites;
private final View infoDivider;
private static final DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT);
public StatusDetailedViewHolder(View view) {
super(view);
reblogs = view.findViewById(R.id.status_reblogs);
@ -37,17 +43,74 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
}
@Override
protected void setCreatedAt(Date createdAt, Date editedAt, StatusDisplayOptions statusDisplayOptions) {
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT);
Context context = timestampInfo.getContext();
List<String> list = new ArrayList<>();
protected void setMetaData(StatusViewData.Concrete statusViewData, StatusDisplayOptions statusDisplayOptions, StatusActionListener listener) {
Status status = statusViewData.getActionable();
Status.Visibility visibility = status.getVisibility();
Context context = metaInfo.getContext();
Drawable visibilityIcon = getVisibilityIcon(visibility);
CharSequence visibilityString = getVisibilityDescription(context, visibility);
SpannableStringBuilder sb = new SpannableStringBuilder(visibilityString);
if (visibilityIcon != null) {
ImageSpan visibilityIconSpan = new ImageSpan(
visibilityIcon,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ? DynamicDrawableSpan.ALIGN_CENTER : DynamicDrawableSpan.ALIGN_BASELINE
);
sb.setSpan(visibilityIconSpan, 0, visibilityString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
String metadataJoiner = context.getString(R.string.metadata_joiner);
Date createdAt = status.getCreatedAt();
if (createdAt != null) {
list.add(dateFormat.format(createdAt));
sb.append(" ");
sb.append(dateFormat.format(createdAt));
}
Date editedAt = status.getEditedAt();
if (editedAt != null) {
list.add(context.getString(R.string.post_edited, dateFormat.format(editedAt)));
String editedAtString = context.getString(R.string.post_edited, dateFormat.format(editedAt));
sb.append(metadataJoiner);
int spanStart = sb.length();
int spanEnd = spanStart + editedAtString.length();
sb.append(editedAtString);
if (statusViewData.getStatus().getEditedAt() != null) {
NoUnderlineURLSpan editedClickSpan = new NoUnderlineURLSpan("") {
@Override
public void onClick(@NonNull View view) {
listener.onShowEdits(getBindingAdapterPosition());
}
};
sb.setSpan(editedClickSpan, spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
timestampInfo.setText(TextUtils.join(context.getString(R.string.timestamp_joiner), list));
Status.Application app = status.getApplication();
if (app != null) {
sb.append(metadataJoiner);
if (app.getWebsite() != null) {
CharSequence text = LinkHelper.createClickableText(app.getName(), app.getWebsite());
sb.append(text);
} else {
sb.append(app.getName());
}
}
metaInfo.setMovementMethod(LinkMovementMethod.getInstance());
metaInfo.setText(sb);
}
private void setReblogAndFavCount(int reblogCount, int favCount, StatusActionListener listener) {
@ -85,21 +148,6 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
});
}
private void setApplication(@Nullable Status.Application app) {
if (app != null) {
timestampInfo.append("");
if (app.getWebsite() != null) {
CharSequence text = LinkHelper.createClickableText(app.getName(), app.getWebsite());
timestampInfo.append(text);
timestampInfo.setMovementMethod(LinkMovementMethod.getInstance());
} else {
timestampInfo.append(app.getName());
}
}
}
@Override
public void setupWithStatus(@NonNull final StatusViewData.Concrete status,
@NonNull final StatusActionListener listener,
@ -107,8 +155,8 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
@Nullable Object payloads) {
// We never collapse statuses in the detail view
StatusViewData.Concrete uncollapsedStatus = (status.isCollapsible() && status.isCollapsed()) ?
status.copyWithCollapsed(false) :
status;
status.copyWithCollapsed(false) :
status;
super.setupWithStatus(uncollapsedStatus, listener, statusDisplayOptions, payloads);
setupCard(uncollapsedStatus, CardViewMode.FULL_WIDTH, statusDisplayOptions, listener); // Always show card for detailed status
@ -121,17 +169,13 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
} else {
hideQuantitativeStats();
}
setApplication(actionable.getApplication());
setStatusVisibility(actionable.getVisibility());
}
}
private void setStatusVisibility(Status.Visibility visibility) {
private @Nullable Drawable getVisibilityIcon(@Nullable Status.Visibility visibility) {
if (visibility == null) {
return;
return null;
}
int visibilityIcon;
@ -149,29 +193,26 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
visibilityIcon = R.drawable.ic_email_24dp;
break;
default:
return;
return null;
}
final Drawable visibilityDrawable = this.timestampInfo.getContext()
.getDrawable(visibilityIcon);
final Drawable visibilityDrawable = AppCompatResources.getDrawable(
this.metaInfo.getContext(), visibilityIcon
);
if (visibilityDrawable == null) {
return;
return null;
}
final int size = (int) this.timestampInfo.getTextSize();
final int size = (int) this.metaInfo.getTextSize();
visibilityDrawable.setBounds(
0,
0,
size,
size
);
visibilityDrawable.setTint(this.timestampInfo.getCurrentTextColor());
this.timestampInfo.setCompoundDrawables(
visibilityDrawable,
null,
null,
null
);
visibilityDrawable.setTint(this.metaInfo.getCurrentTextColor());
return visibilityDrawable;
}
private void hideQuantitativeStats() {