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:
parent
8c0f02cf33
commit
61a45ae376
26 changed files with 731 additions and 75 deletions
|
@ -436,7 +436,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
||||||
statusNameBar = itemView.findViewById(R.id.status_name_bar);
|
statusNameBar = itemView.findViewById(R.id.status_name_bar);
|
||||||
displayName = itemView.findViewById(R.id.status_display_name);
|
displayName = itemView.findViewById(R.id.status_display_name);
|
||||||
username = itemView.findViewById(R.id.status_username);
|
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);
|
statusContent = itemView.findViewById(R.id.notification_content);
|
||||||
statusAvatar = itemView.findViewById(R.id.notification_status_avatar);
|
statusAvatar = itemView.findViewById(R.id.notification_status_avatar);
|
||||||
notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar);
|
notificationAvatar = itemView.findViewById(R.id.notification_notification_avatar);
|
||||||
|
|
|
@ -38,7 +38,9 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
private var emojis: List<Emoji> = emptyList()
|
private var emojis: List<Emoji> = emptyList()
|
||||||
private var resultClickListener: View.OnClickListener? = null
|
private var resultClickListener: View.OnClickListener? = null
|
||||||
private var animateEmojis = false
|
private var animateEmojis = false
|
||||||
|
private var enabled = true
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
fun setup(
|
fun setup(
|
||||||
options: List<PollOptionViewData>,
|
options: List<PollOptionViewData>,
|
||||||
voteCount: Int,
|
voteCount: Int,
|
||||||
|
@ -46,7 +48,8 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
emojis: List<Emoji>,
|
emojis: List<Emoji>,
|
||||||
mode: Int,
|
mode: Int,
|
||||||
resultClickListener: View.OnClickListener?,
|
resultClickListener: View.OnClickListener?,
|
||||||
animateEmojis: Boolean
|
animateEmojis: Boolean,
|
||||||
|
enabled: Boolean = true
|
||||||
) {
|
) {
|
||||||
this.pollOptions = options
|
this.pollOptions = options
|
||||||
this.voteCount = voteCount
|
this.voteCount = voteCount
|
||||||
|
@ -55,6 +58,7 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
this.mode = mode
|
this.mode = mode
|
||||||
this.resultClickListener = resultClickListener
|
this.resultClickListener = resultClickListener
|
||||||
this.animateEmojis = animateEmojis
|
this.animateEmojis = animateEmojis
|
||||||
|
this.enabled = enabled
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +86,9 @@ class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
radioButton.visible(mode == SINGLE)
|
radioButton.visible(mode == SINGLE)
|
||||||
checkBox.visible(mode == MULTIPLE)
|
checkBox.visible(mode == MULTIPLE)
|
||||||
|
|
||||||
|
radioButton.isEnabled = enabled
|
||||||
|
checkBox.isEnabled = enabled
|
||||||
|
|
||||||
when (mode) {
|
when (mode) {
|
||||||
RESULT -> {
|
RESULT -> {
|
||||||
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
|
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
|
||||||
|
|
|
@ -94,7 +94,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
private ImageView avatarInset;
|
private ImageView avatarInset;
|
||||||
|
|
||||||
public ImageView avatar;
|
public ImageView avatar;
|
||||||
public TextView timestampInfo;
|
public TextView metaInfo;
|
||||||
public TextView content;
|
public TextView content;
|
||||||
public TextView contentWarningDescription;
|
public TextView contentWarningDescription;
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
displayName = itemView.findViewById(R.id.status_display_name);
|
displayName = itemView.findViewById(R.id.status_display_name);
|
||||||
username = itemView.findViewById(R.id.status_username);
|
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);
|
content = itemView.findViewById(R.id.status_content);
|
||||||
avatar = itemView.findViewById(R.id.status_avatar);
|
avatar = itemView.findViewById(R.id.status_avatar);
|
||||||
replyButton = itemView.findViewById(R.id.status_reply);
|
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;
|
String timestampText;
|
||||||
if (statusDisplayOptions.useAbsoluteTime()) {
|
if (statusDisplayOptions.useAbsoluteTime()) {
|
||||||
timestampText = absoluteTimeFormatter.format(createdAt, true);
|
timestampText = absoluteTimeFormatter.format(createdAt, true);
|
||||||
|
@ -320,15 +325,15 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
} else {
|
} else {
|
||||||
long then = createdAt.getTime();
|
long then = createdAt.getTime();
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
String readout = TimestampUtils.getRelativeTimeSpanString(timestampInfo.getContext(), then, now);
|
String readout = TimestampUtils.getRelativeTimeSpanString(metaInfo.getContext(), then, now);
|
||||||
timestampText = readout;
|
timestampText = readout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editedAt != null) {
|
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,
|
private CharSequence getCreatedAtDescription(Date createdAt,
|
||||||
|
@ -715,7 +720,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
Status actionable = status.getActionable();
|
Status actionable = status.getActionable();
|
||||||
setDisplayName(actionable.getAccount().getName(), actionable.getAccount().getEmojis(), statusDisplayOptions);
|
setDisplayName(actionable.getAccount().getName(), actionable.getAccount().getEmojis(), statusDisplayOptions);
|
||||||
setUsername(status.getUsername());
|
setUsername(status.getUsername());
|
||||||
setCreatedAt(actionable.getCreatedAt(), actionable.getEditedAt(), statusDisplayOptions);
|
setMetaData(status, statusDisplayOptions, listener);
|
||||||
setIsReply(actionable.getInReplyToId() != null);
|
setIsReply(actionable.getInReplyToId() != null);
|
||||||
setReplyCount(actionable.getRepliesCount());
|
setReplyCount(actionable.getRepliesCount());
|
||||||
setAvatar(actionable.getAccount().getAvatar(), status.getRebloggedAvatar(),
|
setAvatar(actionable.getAccount().getAvatar(), status.getRebloggedAvatar(),
|
||||||
|
@ -767,7 +772,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
if (payloads instanceof List)
|
if (payloads instanceof List)
|
||||||
for (Object item : (List<?>) payloads) {
|
for (Object item : (List<?>) payloads) {
|
||||||
if (Key.KEY_CREATED.equals(item)) {
|
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) {
|
if (visibility == null) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -1138,7 +1143,7 @@ public abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
avatarInset.setVisibility(visibility);
|
avatarInset.setVisibility(visibility);
|
||||||
displayName.setVisibility(visibility);
|
displayName.setVisibility(visibility);
|
||||||
username.setVisibility(visibility);
|
username.setVisibility(visibility);
|
||||||
timestampInfo.setVisibility(visibility);
|
metaInfo.setVisibility(visibility);
|
||||||
contentWarningDescription.setVisibility(visibility);
|
contentWarningDescription.setVisibility(visibility);
|
||||||
contentWarningButton.setVisibility(visibility);
|
contentWarningButton.setVisibility(visibility);
|
||||||
content.setVisibility(visibility);
|
content.setVisibility(visibility);
|
||||||
|
|
|
@ -2,13 +2,18 @@ package com.keylesspalace.tusky.adapter;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
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.method.LinkMovementMethod;
|
||||||
|
import android.text.style.DynamicDrawableSpan;
|
||||||
|
import android.text.style.ImageSpan;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.keylesspalace.tusky.R;
|
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.interfaces.StatusActionListener;
|
||||||
import com.keylesspalace.tusky.util.CardViewMode;
|
import com.keylesspalace.tusky.util.CardViewMode;
|
||||||
import com.keylesspalace.tusky.util.LinkHelper;
|
import com.keylesspalace.tusky.util.LinkHelper;
|
||||||
|
import com.keylesspalace.tusky.util.NoUnderlineURLSpan;
|
||||||
import com.keylesspalace.tusky.util.StatusDisplayOptions;
|
import com.keylesspalace.tusky.util.StatusDisplayOptions;
|
||||||
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
import com.keylesspalace.tusky.viewdata.StatusViewData;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
public class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
private final TextView reblogs;
|
private final TextView reblogs;
|
||||||
private final TextView favourites;
|
private final TextView favourites;
|
||||||
private final View infoDivider;
|
private final View infoDivider;
|
||||||
|
|
||||||
|
private static final DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT);
|
||||||
|
|
||||||
public StatusDetailedViewHolder(View view) {
|
public StatusDetailedViewHolder(View view) {
|
||||||
super(view);
|
super(view);
|
||||||
reblogs = view.findViewById(R.id.status_reblogs);
|
reblogs = view.findViewById(R.id.status_reblogs);
|
||||||
|
@ -37,17 +43,74 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setCreatedAt(Date createdAt, Date editedAt, StatusDisplayOptions statusDisplayOptions) {
|
protected void setMetaData(StatusViewData.Concrete statusViewData, StatusDisplayOptions statusDisplayOptions, StatusActionListener listener) {
|
||||||
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.SHORT);
|
|
||||||
Context context = timestampInfo.getContext();
|
Status status = statusViewData.getActionable();
|
||||||
List<String> list = new ArrayList<>();
|
|
||||||
|
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) {
|
if (createdAt != null) {
|
||||||
list.add(dateFormat.format(createdAt));
|
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(dateFormat.format(createdAt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Date editedAt = status.getEditedAt();
|
||||||
|
|
||||||
if (editedAt != null) {
|
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());
|
||||||
}
|
}
|
||||||
timestampInfo.setText(TextUtils.join(context.getString(R.string.timestamp_joiner), list));
|
};
|
||||||
|
|
||||||
|
sb.setSpan(editedClickSpan, spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
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
|
@Override
|
||||||
public void setupWithStatus(@NonNull final StatusViewData.Concrete status,
|
public void setupWithStatus(@NonNull final StatusViewData.Concrete status,
|
||||||
@NonNull final StatusActionListener listener,
|
@NonNull final StatusActionListener listener,
|
||||||
|
@ -121,17 +169,13 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
} else {
|
} else {
|
||||||
hideQuantitativeStats();
|
hideQuantitativeStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
setApplication(actionable.getApplication());
|
|
||||||
|
|
||||||
setStatusVisibility(actionable.getVisibility());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setStatusVisibility(Status.Visibility visibility) {
|
private @Nullable Drawable getVisibilityIcon(@Nullable Status.Visibility visibility) {
|
||||||
|
|
||||||
if (visibility == null) {
|
if (visibility == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int visibilityIcon;
|
int visibilityIcon;
|
||||||
|
@ -149,29 +193,26 @@ public class StatusDetailedViewHolder extends StatusBaseViewHolder {
|
||||||
visibilityIcon = R.drawable.ic_email_24dp;
|
visibilityIcon = R.drawable.ic_email_24dp;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Drawable visibilityDrawable = this.timestampInfo.getContext()
|
final Drawable visibilityDrawable = AppCompatResources.getDrawable(
|
||||||
.getDrawable(visibilityIcon);
|
this.metaInfo.getContext(), visibilityIcon
|
||||||
|
);
|
||||||
if (visibilityDrawable == null) {
|
if (visibilityDrawable == null) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int size = (int) this.timestampInfo.getTextSize();
|
final int size = (int) this.metaInfo.getTextSize();
|
||||||
visibilityDrawable.setBounds(
|
visibilityDrawable.setBounds(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
size,
|
size,
|
||||||
size
|
size
|
||||||
);
|
);
|
||||||
visibilityDrawable.setTint(this.timestampInfo.getCurrentTextColor());
|
visibilityDrawable.setTint(this.metaInfo.getCurrentTextColor());
|
||||||
this.timestampInfo.setCompoundDrawables(
|
|
||||||
visibilityDrawable,
|
return visibilityDrawable;
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideQuantitativeStats() {
|
private void hideQuantitativeStats() {
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
||||||
|
|
||||||
setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions);
|
setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions);
|
||||||
setUsername(account.getUsername());
|
setUsername(account.getUsername());
|
||||||
setCreatedAt(status.getCreatedAt(), status.getEditedAt(), statusDisplayOptions);
|
setMetaData(statusViewData, statusDisplayOptions, listener);
|
||||||
setIsReply(status.getInReplyToId() != null);
|
setIsReply(status.getInReplyToId() != null);
|
||||||
setFavourited(status.getFavourited());
|
setFavourited(status.getFavourited());
|
||||||
setBookmarked(status.getBookmarked());
|
setBookmarked(status.getBookmarked());
|
||||||
|
@ -121,7 +121,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
||||||
if (payloads instanceof List) {
|
if (payloads instanceof List) {
|
||||||
for (Object item : (List<?>) payloads) {
|
for (Object item : (List<?>) payloads) {
|
||||||
if (Key.KEY_CREATED.equals(item)) {
|
if (Key.KEY_CREATED.equals(item)) {
|
||||||
setCreatedAt(status.getCreatedAt(), status.getEditedAt(), statusDisplayOptions);
|
setMetaData(statusViewData, statusDisplayOptions, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
import androidx.fragment.app.commit
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
@ -33,6 +34,7 @@ import com.keylesspalace.tusky.AccountListActivity
|
||||||
import com.keylesspalace.tusky.AccountListActivity.Companion.newIntent
|
import com.keylesspalace.tusky.AccountListActivity.Companion.newIntent
|
||||||
import com.keylesspalace.tusky.BaseActivity
|
import com.keylesspalace.tusky.BaseActivity
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
|
import com.keylesspalace.tusky.components.viewthread.edits.ViewEditsFragment
|
||||||
import com.keylesspalace.tusky.databinding.FragmentViewThreadBinding
|
import com.keylesspalace.tusky.databinding.FragmentViewThreadBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
|
@ -104,6 +106,7 @@ class ViewThreadFragment : SFragment(), OnRefreshListener, StatusActionListener,
|
||||||
binding.toolbar.setNavigationOnClickListener {
|
binding.toolbar.setNavigationOnClickListener {
|
||||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||||
}
|
}
|
||||||
|
binding.toolbar.inflateMenu(R.menu.view_thread_toolbar)
|
||||||
binding.toolbar.setOnMenuItemClickListener { menuItem ->
|
binding.toolbar.setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
R.id.action_reveal -> {
|
R.id.action_reveal -> {
|
||||||
|
@ -325,6 +328,17 @@ class ViewThreadFragment : SFragment(), OnRefreshListener, StatusActionListener,
|
||||||
viewModel.voteInPoll(choices, status)
|
viewModel.voteInPoll(choices, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onShowEdits(position: Int) {
|
||||||
|
val status = adapter.currentList[position]
|
||||||
|
val viewEditsFragment = ViewEditsFragment.newInstance(status.actionableId)
|
||||||
|
|
||||||
|
parentFragmentManager.commit {
|
||||||
|
setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left, R.anim.slide_from_left, R.anim.slide_to_right)
|
||||||
|
replace(R.id.fragment_container, viewEditsFragment, "ViewEditsFragment_$id")
|
||||||
|
addToBackStack(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ViewThreadFragment"
|
private const val TAG = "ViewThreadFragment"
|
||||||
|
|
||||||
|
|
|
@ -364,7 +364,9 @@ class ViewThreadViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Status.toViewData(detailed: Boolean = false): StatusViewData.Concrete {
|
private fun Status.toViewData(
|
||||||
|
detailed: Boolean = false
|
||||||
|
): StatusViewData.Concrete {
|
||||||
val oldStatus = (_uiState.value as? ThreadUiState.Success)?.statuses?.find { it.id == this.id }
|
val oldStatus = (_uiState.value as? ThreadUiState.Success)?.statuses?.find { it.id == this.id }
|
||||||
return toViewData(
|
return toViewData(
|
||||||
isShowingContent = oldStatus?.isShowingContent ?: (alwaysShowSensitiveMedia || !actionableStatus.sensitive),
|
isShowingContent = oldStatus?.isShowingContent ?: (alwaysShowSensitiveMedia || !actionableStatus.sensitive),
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
package com.keylesspalace.tusky.components.viewthread.edits
|
||||||
|
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.keylesspalace.tusky.R
|
||||||
|
import com.keylesspalace.tusky.adapter.PollAdapter
|
||||||
|
import com.keylesspalace.tusky.adapter.PollAdapter.Companion.MULTIPLE
|
||||||
|
import com.keylesspalace.tusky.adapter.PollAdapter.Companion.SINGLE
|
||||||
|
import com.keylesspalace.tusky.databinding.ItemStatusEditBinding
|
||||||
|
import com.keylesspalace.tusky.entity.Attachment.Focus
|
||||||
|
import com.keylesspalace.tusky.entity.StatusEdit
|
||||||
|
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||||
|
import com.keylesspalace.tusky.util.AbsoluteTimeFormatter
|
||||||
|
import com.keylesspalace.tusky.util.BindingHolder
|
||||||
|
import com.keylesspalace.tusky.util.ThemeUtils
|
||||||
|
import com.keylesspalace.tusky.util.aspectRatios
|
||||||
|
import com.keylesspalace.tusky.util.decodeBlurHash
|
||||||
|
import com.keylesspalace.tusky.util.emojify
|
||||||
|
import com.keylesspalace.tusky.util.hide
|
||||||
|
import com.keylesspalace.tusky.util.loadAvatar
|
||||||
|
import com.keylesspalace.tusky.util.parseAsMastodonHtml
|
||||||
|
import com.keylesspalace.tusky.util.setClickableText
|
||||||
|
import com.keylesspalace.tusky.util.show
|
||||||
|
import com.keylesspalace.tusky.util.visible
|
||||||
|
import com.keylesspalace.tusky.viewdata.toViewData
|
||||||
|
|
||||||
|
class ViewEditsAdapter(
|
||||||
|
private val edits: List<StatusEdit>,
|
||||||
|
private val animateAvatars: Boolean,
|
||||||
|
private val animateEmojis: Boolean,
|
||||||
|
private val useBlurhash: Boolean,
|
||||||
|
private val listener: LinkListener
|
||||||
|
) : RecyclerView.Adapter<BindingHolder<ItemStatusEditBinding>>() {
|
||||||
|
|
||||||
|
private val absoluteTimeFormatter = AbsoluteTimeFormatter()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): BindingHolder<ItemStatusEditBinding> {
|
||||||
|
val binding = ItemStatusEditBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
binding.statusEditMediaPreview.clipToOutline = true
|
||||||
|
return BindingHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: BindingHolder<ItemStatusEditBinding>, position: Int) {
|
||||||
|
|
||||||
|
val edit = edits[position]
|
||||||
|
|
||||||
|
val binding = holder.binding
|
||||||
|
|
||||||
|
val context = binding.root.context
|
||||||
|
|
||||||
|
val avatarRadius: Int = context.resources
|
||||||
|
.getDimensionPixelSize(R.dimen.avatar_radius_48dp)
|
||||||
|
|
||||||
|
loadAvatar(edit.account.avatar, binding.statusEditAvatar, avatarRadius, animateAvatars)
|
||||||
|
|
||||||
|
val infoStringRes = if (position == edits.size - 1) {
|
||||||
|
R.string.status_created_info
|
||||||
|
} else {
|
||||||
|
R.string.status_edit_info
|
||||||
|
}
|
||||||
|
|
||||||
|
val timestamp = absoluteTimeFormatter.format(edit.createdAt, false)
|
||||||
|
|
||||||
|
binding.statusEditInfo.text = context.getString(
|
||||||
|
infoStringRes,
|
||||||
|
edit.account.name,
|
||||||
|
timestamp
|
||||||
|
).emojify(edit.account.emojis, binding.statusEditInfo, animateEmojis)
|
||||||
|
|
||||||
|
if (edit.spoilerText.isEmpty()) {
|
||||||
|
binding.statusEditContentWarningDescription.hide()
|
||||||
|
binding.statusEditContentWarningSeparator.hide()
|
||||||
|
} else {
|
||||||
|
binding.statusEditContentWarningDescription.show()
|
||||||
|
binding.statusEditContentWarningSeparator.show()
|
||||||
|
binding.statusEditContentWarningDescription.text = edit.spoilerText.emojify(
|
||||||
|
edit.emojis,
|
||||||
|
binding.statusEditContentWarningDescription,
|
||||||
|
animateEmojis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val emojifiedText = edit.content.parseAsMastodonHtml().emojify(edit.emojis, binding.statusEditContent, animateEmojis)
|
||||||
|
setClickableText(binding.statusEditContent, emojifiedText, emptyList(), emptyList(), listener)
|
||||||
|
|
||||||
|
if (edit.poll == null) {
|
||||||
|
binding.statusEditPollOptions.hide()
|
||||||
|
binding.statusEditPollDescription.hide()
|
||||||
|
} else {
|
||||||
|
binding.statusEditPollOptions.show()
|
||||||
|
|
||||||
|
// not used for now since not reported by the api
|
||||||
|
// https://github.com/mastodon/mastodon/issues/22571
|
||||||
|
// binding.statusEditPollDescription.show()
|
||||||
|
|
||||||
|
val pollAdapter = PollAdapter()
|
||||||
|
binding.statusEditPollOptions.adapter = pollAdapter
|
||||||
|
binding.statusEditPollOptions.layoutManager = LinearLayoutManager(context)
|
||||||
|
|
||||||
|
pollAdapter.setup(
|
||||||
|
options = edit.poll.options.map { it.toViewData(false) },
|
||||||
|
voteCount = 0,
|
||||||
|
votersCount = null,
|
||||||
|
emojis = edit.emojis,
|
||||||
|
mode = if (edit.poll.multiple) { // not reported by the api
|
||||||
|
MULTIPLE
|
||||||
|
} else {
|
||||||
|
SINGLE
|
||||||
|
},
|
||||||
|
resultClickListener = null,
|
||||||
|
animateEmojis = animateEmojis,
|
||||||
|
enabled = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edit.mediaAttachments.isEmpty()) {
|
||||||
|
binding.statusEditMediaPreview.hide()
|
||||||
|
binding.statusEditMediaSensitivity.hide()
|
||||||
|
} else {
|
||||||
|
binding.statusEditMediaPreview.show()
|
||||||
|
binding.statusEditMediaPreview.aspectRatios = edit.mediaAttachments.aspectRatios()
|
||||||
|
|
||||||
|
binding.statusEditMediaPreview.forEachIndexed { index, _, imageView, descriptionIndicator ->
|
||||||
|
|
||||||
|
val attachment = edit.mediaAttachments[index]
|
||||||
|
val hasDescription = !attachment.description.isNullOrBlank()
|
||||||
|
|
||||||
|
if (hasDescription) {
|
||||||
|
imageView.contentDescription = attachment.description
|
||||||
|
} else {
|
||||||
|
imageView.contentDescription =
|
||||||
|
imageView.context.getString(R.string.action_view_media)
|
||||||
|
}
|
||||||
|
descriptionIndicator.visibility = if (hasDescription) View.VISIBLE else View.GONE
|
||||||
|
|
||||||
|
val blurhash = attachment.blurhash
|
||||||
|
|
||||||
|
val placeholder: Drawable = if (blurhash != null && useBlurhash) {
|
||||||
|
decodeBlurHash(context, blurhash)
|
||||||
|
} else {
|
||||||
|
ColorDrawable(ThemeUtils.getColor(context, R.attr.colorBackgroundAccent))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment.previewUrl.isNullOrEmpty()) {
|
||||||
|
imageView.removeFocalPoint()
|
||||||
|
Glide.with(imageView)
|
||||||
|
.load(placeholder)
|
||||||
|
.centerInside()
|
||||||
|
.into(imageView)
|
||||||
|
} else {
|
||||||
|
val focus: Focus? = attachment.meta?.focus
|
||||||
|
|
||||||
|
if (focus != null) {
|
||||||
|
imageView.setFocalPoint(focus)
|
||||||
|
Glide.with(imageView.context)
|
||||||
|
.load(attachment.previewUrl)
|
||||||
|
.placeholder(placeholder)
|
||||||
|
.centerInside()
|
||||||
|
.addListener(imageView)
|
||||||
|
.into(imageView)
|
||||||
|
} else {
|
||||||
|
imageView.removeFocalPoint()
|
||||||
|
Glide.with(imageView)
|
||||||
|
.load(attachment.previewUrl)
|
||||||
|
.placeholder(placeholder)
|
||||||
|
.centerInside()
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.statusEditMediaSensitivity.visible(edit.sensitive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = edits.size
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
/* Copyright 2022 Tusky Contributors
|
||||||
|
*
|
||||||
|
* 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.components.viewthread.edits
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
|
import com.keylesspalace.tusky.BottomSheetActivity
|
||||||
|
import com.keylesspalace.tusky.R
|
||||||
|
import com.keylesspalace.tusky.StatusListActivity
|
||||||
|
import com.keylesspalace.tusky.components.account.AccountActivity
|
||||||
|
import com.keylesspalace.tusky.databinding.FragmentViewThreadBinding
|
||||||
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
|
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||||
|
import com.keylesspalace.tusky.settings.PrefKeys
|
||||||
|
import com.keylesspalace.tusky.util.hide
|
||||||
|
import com.keylesspalace.tusky.util.show
|
||||||
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ViewEditsFragment : Fragment(R.layout.fragment_view_thread), LinkListener, Injectable {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
||||||
|
private val viewModel: ViewEditsViewModel by viewModels { viewModelFactory }
|
||||||
|
|
||||||
|
private val binding by viewBinding(FragmentViewThreadBinding::bind)
|
||||||
|
|
||||||
|
private lateinit var statusId: String
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|
||||||
|
binding.toolbar.setNavigationOnClickListener {
|
||||||
|
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||||
|
}
|
||||||
|
binding.toolbar.title = getString(R.string.title_edits)
|
||||||
|
binding.swipeRefreshLayout.isEnabled = false
|
||||||
|
|
||||||
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
|
binding.recyclerView.layoutManager = LinearLayoutManager(context)
|
||||||
|
|
||||||
|
val divider = DividerItemDecoration(context, LinearLayout.VERTICAL)
|
||||||
|
binding.recyclerView.addItemDecoration(divider)
|
||||||
|
(binding.recyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||||
|
|
||||||
|
statusId = requireArguments().getString(STATUS_ID_EXTRA)!!
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
|
|
||||||
|
val animateAvatars = preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false)
|
||||||
|
val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false)
|
||||||
|
val useBlurhash = preferences.getBoolean(PrefKeys.USE_BLURHASH, true)
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
viewModel.uiState.collect { uiState ->
|
||||||
|
when (uiState) {
|
||||||
|
EditsUiState.Initial -> {}
|
||||||
|
EditsUiState.Loading -> {
|
||||||
|
binding.recyclerView.hide()
|
||||||
|
binding.statusView.hide()
|
||||||
|
binding.progressBar.show()
|
||||||
|
}
|
||||||
|
is EditsUiState.Error -> {
|
||||||
|
Log.w(TAG, "failed to load edits", uiState.throwable)
|
||||||
|
|
||||||
|
binding.recyclerView.hide()
|
||||||
|
binding.statusView.show()
|
||||||
|
binding.progressBar.hide()
|
||||||
|
|
||||||
|
if (uiState.throwable is IOException) {
|
||||||
|
binding.statusView.setup(R.drawable.elephant_offline, R.string.error_network) {
|
||||||
|
viewModel.loadEdits(statusId, force = true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.statusView.setup(R.drawable.elephant_error, R.string.error_generic) {
|
||||||
|
viewModel.loadEdits(statusId, force = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is EditsUiState.Success -> {
|
||||||
|
binding.recyclerView.show()
|
||||||
|
binding.statusView.hide()
|
||||||
|
binding.progressBar.hide()
|
||||||
|
|
||||||
|
binding.recyclerView.adapter = ViewEditsAdapter(
|
||||||
|
edits = uiState.edits,
|
||||||
|
animateAvatars = animateAvatars,
|
||||||
|
animateEmojis = animateEmojis,
|
||||||
|
useBlurhash = useBlurhash,
|
||||||
|
listener = this@ViewEditsFragment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadEdits(statusId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewAccount(id: String) {
|
||||||
|
bottomSheetActivity?.startActivityWithSlideInAnimation(AccountActivity.getIntent(requireContext(), id))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewTag(tag: String) {
|
||||||
|
bottomSheetActivity?.startActivityWithSlideInAnimation(StatusListActivity.newHashtagIntent(requireContext(), tag))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewUrl(url: String) {
|
||||||
|
bottomSheetActivity?.viewUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val bottomSheetActivity
|
||||||
|
get() = (activity as? BottomSheetActivity)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ViewEditsFragment"
|
||||||
|
|
||||||
|
private const val STATUS_ID_EXTRA = "id"
|
||||||
|
|
||||||
|
fun newInstance(statusId: String): ViewEditsFragment {
|
||||||
|
val arguments = Bundle(1)
|
||||||
|
val fragment = ViewEditsFragment()
|
||||||
|
arguments.putString(STATUS_ID_EXTRA, statusId)
|
||||||
|
fragment.arguments = arguments
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* Copyright 2022 Tusky Contributors
|
||||||
|
*
|
||||||
|
* 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.components.viewthread.edits
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import at.connyduck.calladapter.networkresult.fold
|
||||||
|
import com.keylesspalace.tusky.entity.StatusEdit
|
||||||
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ViewEditsViewModel @Inject constructor(
|
||||||
|
private val api: MastodonApi
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState: MutableStateFlow<EditsUiState> = MutableStateFlow(EditsUiState.Initial)
|
||||||
|
val uiState: Flow<EditsUiState>
|
||||||
|
get() = _uiState
|
||||||
|
|
||||||
|
fun loadEdits(statusId: String, force: Boolean = false, refreshing: Boolean = false) {
|
||||||
|
if (force || _uiState.value is EditsUiState.Initial) {
|
||||||
|
if (!refreshing) {
|
||||||
|
_uiState.value = EditsUiState.Loading
|
||||||
|
}
|
||||||
|
viewModelScope.launch {
|
||||||
|
api.statusEdits(statusId).fold(
|
||||||
|
{ edits ->
|
||||||
|
val sortedEdits = edits.sortedBy { edit -> edit.createdAt }.reversed()
|
||||||
|
_uiState.value = EditsUiState.Success(sortedEdits)
|
||||||
|
},
|
||||||
|
{ throwable ->
|
||||||
|
_uiState.value = EditsUiState.Error(throwable)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface EditsUiState {
|
||||||
|
object Initial : EditsUiState
|
||||||
|
object Loading : EditsUiState
|
||||||
|
class Error(val throwable: Throwable) : EditsUiState
|
||||||
|
data class Success(
|
||||||
|
val edits: List<StatusEdit>
|
||||||
|
) : EditsUiState
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragmen
|
||||||
import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment
|
import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment
|
||||||
import com.keylesspalace.tusky.components.timeline.TimelineFragment
|
import com.keylesspalace.tusky.components.timeline.TimelineFragment
|
||||||
import com.keylesspalace.tusky.components.viewthread.ViewThreadFragment
|
import com.keylesspalace.tusky.components.viewthread.ViewThreadFragment
|
||||||
|
import com.keylesspalace.tusky.components.viewthread.edits.ViewEditsFragment
|
||||||
import com.keylesspalace.tusky.fragment.AccountListFragment
|
import com.keylesspalace.tusky.fragment.AccountListFragment
|
||||||
import com.keylesspalace.tusky.fragment.NotificationsFragment
|
import com.keylesspalace.tusky.fragment.NotificationsFragment
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
@ -51,6 +52,9 @@ abstract class FragmentBuildersModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun viewThreadFragment(): ViewThreadFragment
|
abstract fun viewThreadFragment(): ViewThreadFragment
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract fun viewEditsFragment(): ViewEditsFragment
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun timelineFragment(): TimelineFragment
|
abstract fun timelineFragment(): TimelineFragment
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import com.keylesspalace.tusky.components.search.SearchViewModel
|
||||||
import com.keylesspalace.tusky.components.timeline.viewmodel.CachedTimelineViewModel
|
import com.keylesspalace.tusky.components.timeline.viewmodel.CachedTimelineViewModel
|
||||||
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel
|
import com.keylesspalace.tusky.components.timeline.viewmodel.NetworkTimelineViewModel
|
||||||
import com.keylesspalace.tusky.components.viewthread.ViewThreadViewModel
|
import com.keylesspalace.tusky.components.viewthread.ViewThreadViewModel
|
||||||
|
import com.keylesspalace.tusky.components.viewthread.edits.ViewEditsViewModel
|
||||||
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
|
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
|
||||||
import com.keylesspalace.tusky.viewmodel.EditProfileViewModel
|
import com.keylesspalace.tusky.viewmodel.EditProfileViewModel
|
||||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel
|
import com.keylesspalace.tusky.viewmodel.ListsViewModel
|
||||||
|
@ -118,6 +119,11 @@ abstract class ViewModelModule {
|
||||||
@ViewModelKey(ViewThreadViewModel::class)
|
@ViewModelKey(ViewThreadViewModel::class)
|
||||||
internal abstract fun viewThreadViewModel(viewModel: ViewThreadViewModel): ViewModel
|
internal abstract fun viewThreadViewModel(viewModel: ViewThreadViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(ViewEditsViewModel::class)
|
||||||
|
internal abstract fun viewEditsViewModel(viewModel: ViewEditsViewModel): ViewModel
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(AccountMediaViewModel::class)
|
@ViewModelKey(AccountMediaViewModel::class)
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.keylesspalace.tusky.entity
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
data class StatusEdit(
|
||||||
|
val content: String,
|
||||||
|
@SerializedName("spoiler_text") val spoilerText: String,
|
||||||
|
val sensitive: Boolean,
|
||||||
|
@SerializedName("created_at") val createdAt: Date,
|
||||||
|
val account: TimelineAccount,
|
||||||
|
val poll: Poll?,
|
||||||
|
@SerializedName("media_attachments") val mediaAttachments: List<Attachment>,
|
||||||
|
val emojis: List<Emoji>
|
||||||
|
)
|
|
@ -63,4 +63,6 @@ public interface StatusActionListener extends LinkListener {
|
||||||
|
|
||||||
void onVoteInPoll(int position, @NonNull List<Integer> choices);
|
void onVoteInPoll(int position, @NonNull List<Integer> choices);
|
||||||
|
|
||||||
|
default void onShowEdits(int position) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import com.keylesspalace.tusky.entity.ScheduledStatus
|
||||||
import com.keylesspalace.tusky.entity.SearchResult
|
import com.keylesspalace.tusky.entity.SearchResult
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.entity.StatusContext
|
import com.keylesspalace.tusky.entity.StatusContext
|
||||||
|
import com.keylesspalace.tusky.entity.StatusEdit
|
||||||
import com.keylesspalace.tusky.entity.StatusSource
|
import com.keylesspalace.tusky.entity.StatusSource
|
||||||
import com.keylesspalace.tusky.entity.TimelineAccount
|
import com.keylesspalace.tusky.entity.TimelineAccount
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
@ -194,6 +195,11 @@ interface MastodonApi {
|
||||||
@Path("id") statusId: String
|
@Path("id") statusId: String
|
||||||
): NetworkResult<StatusContext>
|
): NetworkResult<StatusContext>
|
||||||
|
|
||||||
|
@GET("api/v1/statuses/{id}/history")
|
||||||
|
suspend fun statusEdits(
|
||||||
|
@Path("id") statusId: String
|
||||||
|
): NetworkResult<List<StatusEdit>>
|
||||||
|
|
||||||
@GET("api/v1/statuses/{id}/reblogged_by")
|
@GET("api/v1/statuses/{id}/reblogged_by")
|
||||||
suspend fun statusRebloggedBy(
|
suspend fun statusRebloggedBy(
|
||||||
@Path("id") statusId: String,
|
@Path("id") statusId: String,
|
||||||
|
|
|
@ -40,7 +40,6 @@ sealed class StatusViewData {
|
||||||
*
|
*
|
||||||
* @return Whether the post is collapsed or fully expanded.
|
* @return Whether the post is collapsed or fully expanded.
|
||||||
*/
|
*/
|
||||||
/** Whether the status meets the requirement to be collapse */
|
|
||||||
val isCollapsed: Boolean,
|
val isCollapsed: Boolean,
|
||||||
val isDetailed: Boolean = false
|
val isDetailed: Boolean = false
|
||||||
) : StatusViewData() {
|
) : StatusViewData() {
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
app:menu="@menu/view_thread_toolbar"
|
|
||||||
app:navigationContentDescription="@string/abc_action_bar_up_description"
|
app:navigationContentDescription="@string/abc_action_bar_up_description"
|
||||||
app:navigationIcon="?attr/homeAsUpIndicator"
|
app:navigationIcon="?attr/homeAsUpIndicator"
|
||||||
app:title="@string/title_view_thread" />
|
app:title="@string/title_view_thread" />
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
app:menu="@menu/view_thread_toolbar"
|
|
||||||
app:navigationContentDescription="@string/abc_action_bar_up_description"
|
app:navigationContentDescription="@string/abc_action_bar_up_description"
|
||||||
app:navigationIcon="?attr/homeAsUpIndicator"
|
app:navigationIcon="?attr/homeAsUpIndicator"
|
||||||
app:title="@string/title_view_thread" />
|
app:title="@string/title_view_thread" />
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
android:textStyle="normal|bold"
|
android:textStyle="normal|bold"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintEnd_toStartOf="@id/status_timestamp_info"
|
app:layout_constraintEnd_toStartOf="@id/status_meta_info"
|
||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintStart_toEndOf="@id/status_avatar"
|
app:layout_constraintStart_toEndOf="@id/status_avatar"
|
||||||
app:layout_constraintTop_toBottomOf="@id/conversation_name"
|
app:layout_constraintTop_toBottomOf="@id/conversation_name"
|
||||||
|
@ -106,13 +106,13 @@
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
app:layout_constraintEnd_toStartOf="@id/status_timestamp_info"
|
app:layout_constraintEnd_toStartOf="@id/status_meta_info"
|
||||||
app:layout_constraintStart_toEndOf="@id/status_display_name"
|
app:layout_constraintStart_toEndOf="@id/status_display_name"
|
||||||
app:layout_constraintTop_toTopOf="@id/status_display_name"
|
app:layout_constraintTop_toTopOf="@id/status_display_name"
|
||||||
tools:text="\@Entenhausen@birbsarecooooooooooool.site" />
|
tools:text="\@Entenhausen@birbsarecooooooooooool.site" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/status_timestamp_info"
|
android:id="@+id/status_meta_info"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
android:id="@+id/status_poll_radio_button"
|
android:id="@+id/status_poll_radio_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
app:buttonTint="@color/compound_button_color"
|
app:buttonTint="@color/compound_button_color"
|
||||||
tools:text="Option 1" />
|
tools:text="Option 1" />
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:lines="1"
|
android:lines="1"
|
||||||
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
app:buttonTint="@color/compound_button_color"
|
app:buttonTint="@color/compound_button_color"
|
||||||
tools:text="Option 1" />
|
tools:text="Option 1" />
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
android:textStyle="normal|bold"
|
android:textStyle="normal|bold"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintEnd_toStartOf="@id/status_timestamp_info"
|
app:layout_constraintEnd_toStartOf="@id/status_meta_info"
|
||||||
app:layout_constraintHorizontal_bias="0"
|
app:layout_constraintHorizontal_bias="0"
|
||||||
app:layout_constraintStart_toEndOf="@id/status_avatar"
|
app:layout_constraintStart_toEndOf="@id/status_avatar"
|
||||||
app:layout_constraintTop_toBottomOf="@id/status_info"
|
app:layout_constraintTop_toBottomOf="@id/status_info"
|
||||||
|
@ -85,13 +85,13 @@
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textColor="?android:textColorSecondary"
|
android:textColor="?android:textColorSecondary"
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
app:layout_constraintEnd_toStartOf="@id/status_timestamp_info"
|
app:layout_constraintEnd_toStartOf="@id/status_meta_info"
|
||||||
app:layout_constraintStart_toEndOf="@id/status_display_name"
|
app:layout_constraintStart_toEndOf="@id/status_display_name"
|
||||||
app:layout_constraintTop_toTopOf="@id/status_display_name"
|
app:layout_constraintTop_toTopOf="@id/status_display_name"
|
||||||
tools:text="\@Entenhausen@birbsarecooooooooooool.site" />
|
tools:text="\@Entenhausen@birbsarecooooooooooool.site" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/status_timestamp_info"
|
android:id="@+id/status_meta_info"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
|
|
|
@ -266,7 +266,7 @@
|
||||||
tools:text="7 votes • 7 hours remaining" />
|
tools:text="7 votes • 7 hours remaining" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/status_timestamp_info"
|
android:id="@+id/status_meta_info"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="14dp"
|
android:layout_marginStart="14dp"
|
||||||
|
@ -274,6 +274,7 @@
|
||||||
android:layout_marginEnd="14dp"
|
android:layout_marginEnd="14dp"
|
||||||
android:drawablePadding="4dp"
|
android:drawablePadding="4dp"
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
|
android:lineSpacingMultiplier="1.1"
|
||||||
android:textColor="?android:textColorTertiary"
|
android:textColor="?android:textColorTertiary"
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
@ -285,7 +286,7 @@
|
||||||
android:id="@+id/status_info_divider"
|
android:id="@+id/status_info_divider"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:layout_below="@id/status_timestamp_info"
|
android:layout_below="@id/status_meta_info"
|
||||||
android:layout_marginStart="14dp"
|
android:layout_marginStart="14dp"
|
||||||
android:layout_marginTop="6dp"
|
android:layout_marginTop="6dp"
|
||||||
android:layout_marginEnd="14dp"
|
android:layout_marginEnd="14dp"
|
||||||
|
@ -293,7 +294,7 @@
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
app:layout_constraintTop_toBottomOf="@id/status_timestamp_info" />
|
app:layout_constraintTop_toBottomOf="@id/status_meta_info" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/status_reblogs"
|
android:id="@+id/status_reblogs"
|
||||||
|
|
148
app/src/main/res/layout/item_status_edit.xml
Normal file
148
app/src/main/res/layout/item_status_edit.xml
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingBottom="6dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/status_edit_avatar"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:contentDescription="@string/action_view_profile"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/avatar_default" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_edit_info"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="?attr/status_text_medium"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/status_edit_avatar"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="\@Tusky edited 18th December 2022" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_edit_content_warning_description"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="14dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hyphenationFrequency="full"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:lineSpacingMultiplier="1.1"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="?attr/status_text_medium"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_edit_avatar"
|
||||||
|
tools:text="content warning which is very long and it doesn't fit"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/status_edit_content_warning_separator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="14dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:background="?android:textColorPrimary"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_edit_content_warning_description" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_edit_content"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="14dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:focusable="true"
|
||||||
|
android:hyphenationFrequency="full"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:lineSpacingMultiplier="1.1"
|
||||||
|
android:textColor="?android:textColorPrimary"
|
||||||
|
android:textSize="?attr/status_text_medium"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_edit_content_warning_separator"
|
||||||
|
tools:text="This is an edited status" />
|
||||||
|
|
||||||
|
<com.keylesspalace.tusky.view.MediaPreviewLayout
|
||||||
|
android:id="@+id/status_edit_media_preview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="@dimen/status_media_preview_margin_top"
|
||||||
|
android:layout_marginEnd="14dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:background="@drawable/media_preview_outline"
|
||||||
|
android:importantForAccessibility="noHideDescendants"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_edit_content" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_edit_media_sensitivity"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginEnd="14dp"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:text="@string/post_sensitive_media_title"
|
||||||
|
android:textColor="?android:attr/textColorTertiary"
|
||||||
|
android:textSize="?attr/status_text_medium"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_edit_media_preview" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/status_edit_poll_options"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginEnd="14dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_edit_media_sensitivity" />
|
||||||
|
|
||||||
|
<!-- hidden because as of Mastodon 4.0.2 we don't get this info via the api -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/status_edit_poll_description"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="14dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:layout_marginEnd="14dp"
|
||||||
|
android:textSize="?attr/status_text_medium"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/status_edit_poll_options"
|
||||||
|
tools:text="ends at 12:30" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -51,7 +51,7 @@
|
||||||
android:id="@+id/status_username"
|
android:id="@+id/status_username"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_toStartOf="@+id/status_timestamp_info"
|
android:layout_toStartOf="@+id/status_meta_info"
|
||||||
android:layout_toEndOf="@id/status_display_name"
|
android:layout_toEndOf="@id/status_display_name"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
tools:text="\@Entenhausen" />
|
tools:text="\@Entenhausen" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/status_timestamp_info"
|
android:id="@+id/status_meta_info"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<string name="emoji_shortcode_format" translatable="false">:%s:</string>
|
<string name="emoji_shortcode_format" translatable="false">:%s:</string>
|
||||||
<string name="post_timestamp_with_edited_indicator" translatable="false">%s *</string>
|
<string name="post_timestamp_with_edited_indicator" translatable="false">%s *</string>
|
||||||
<string name="timestamp_joiner" translatable="false">" • "</string>
|
<string name="metadata_joiner" translatable="false">" • "</string>
|
||||||
|
|
||||||
<string-array name="post_privacy_values">
|
<string-array name="post_privacy_values">
|
||||||
<item>public</item>
|
<item>public</item>
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
<string name="title_announcements">Announcements</string>
|
<string name="title_announcements">Announcements</string>
|
||||||
<string name="title_licenses">Licenses</string>
|
<string name="title_licenses">Licenses</string>
|
||||||
<string name="title_followed_hashtags">Followed Hashtags</string>
|
<string name="title_followed_hashtags">Followed Hashtags</string>
|
||||||
|
<string name="title_edits">Edits</string>
|
||||||
|
|
||||||
<string name="post_username_format">\@%s</string>
|
<string name="post_username_format">\@%s</string>
|
||||||
<string name="post_boosted_format">%s boosted</string>
|
<string name="post_boosted_format">%s boosted</string>
|
||||||
|
@ -712,4 +713,9 @@
|
||||||
|
|
||||||
<string name="action_unfollow_hashtag_format">Unfollow #%s?</string>
|
<string name="action_unfollow_hashtag_format">Unfollow #%s?</string>
|
||||||
|
|
||||||
|
<!--@Tusky edited 19th December 2022 13:37 -->
|
||||||
|
<string name="status_edit_info">%1$s edited %2$s</string>
|
||||||
|
<!--@Tusky created 19th December 2022 13:12 -->
|
||||||
|
<string name="status_created_info">%1$s created %2$s</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue