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);
|
||||
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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -83,7 +83,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
|
||||
setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions);
|
||||
setUsername(account.getUsername());
|
||||
setCreatedAt(status.getCreatedAt(), status.getEditedAt(), statusDisplayOptions);
|
||||
setMetaData(statusViewData, statusDisplayOptions, listener);
|
||||
setIsReply(status.getInReplyToId() != null);
|
||||
setFavourited(status.getFavourited());
|
||||
setBookmarked(status.getBookmarked());
|
||||
|
@ -121,7 +121,7 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
|
|||
if (payloads instanceof List) {
|
||||
for (Object item : (List<?>) payloads) {
|
||||
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.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.PreferenceManager
|
||||
|
@ -33,6 +34,7 @@ import com.keylesspalace.tusky.AccountListActivity
|
|||
import com.keylesspalace.tusky.AccountListActivity.Companion.newIntent
|
||||
import com.keylesspalace.tusky.BaseActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.viewthread.edits.ViewEditsFragment
|
||||
import com.keylesspalace.tusky.databinding.FragmentViewThreadBinding
|
||||
import com.keylesspalace.tusky.di.Injectable
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
|
@ -104,6 +106,7 @@ class ViewThreadFragment : SFragment(), OnRefreshListener, StatusActionListener,
|
|||
binding.toolbar.setNavigationOnClickListener {
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
binding.toolbar.inflateMenu(R.menu.view_thread_toolbar)
|
||||
binding.toolbar.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_reveal -> {
|
||||
|
@ -325,6 +328,17 @@ class ViewThreadFragment : SFragment(), OnRefreshListener, StatusActionListener,
|
|||
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 {
|
||||
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 }
|
||||
return toViewData(
|
||||
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.timeline.TimelineFragment
|
||||
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.NotificationsFragment
|
||||
import dagger.Module
|
||||
|
@ -51,6 +52,9 @@ abstract class FragmentBuildersModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract fun viewThreadFragment(): ViewThreadFragment
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract fun viewEditsFragment(): ViewEditsFragment
|
||||
|
||||
@ContributesAndroidInjector
|
||||
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.NetworkTimelineViewModel
|
||||
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.EditProfileViewModel
|
||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel
|
||||
|
@ -118,6 +119,11 @@ abstract class ViewModelModule {
|
|||
@ViewModelKey(ViewThreadViewModel::class)
|
||||
internal abstract fun viewThreadViewModel(viewModel: ViewThreadViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ViewEditsViewModel::class)
|
||||
internal abstract fun viewEditsViewModel(viewModel: ViewEditsViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@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);
|
||||
|
||||
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.Status
|
||||
import com.keylesspalace.tusky.entity.StatusContext
|
||||
import com.keylesspalace.tusky.entity.StatusEdit
|
||||
import com.keylesspalace.tusky.entity.StatusSource
|
||||
import com.keylesspalace.tusky.entity.TimelineAccount
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
|
@ -194,6 +195,11 @@ interface MastodonApi {
|
|||
@Path("id") statusId: String
|
||||
): 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")
|
||||
suspend fun statusRebloggedBy(
|
||||
@Path("id") statusId: String,
|
||||
|
|
|
@ -40,7 +40,6 @@ sealed class StatusViewData {
|
|||
*
|
||||
* @return Whether the post is collapsed or fully expanded.
|
||||
*/
|
||||
/** Whether the status meets the requirement to be collapse */
|
||||
val isCollapsed: Boolean,
|
||||
val isDetailed: Boolean = false
|
||||
) : StatusViewData() {
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:menu="@menu/view_thread_toolbar"
|
||||
app:navigationContentDescription="@string/abc_action_bar_up_description"
|
||||
app:navigationIcon="?attr/homeAsUpIndicator"
|
||||
app:title="@string/title_view_thread" />
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:menu="@menu/view_thread_toolbar"
|
||||
app:navigationContentDescription="@string/abc_action_bar_up_description"
|
||||
app:navigationIcon="?attr/homeAsUpIndicator"
|
||||
app:title="@string/title_view_thread" />
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
android:textSize="?attr/status_text_medium"
|
||||
android:textStyle="normal|bold"
|
||||
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_constraintStart_toEndOf="@id/status_avatar"
|
||||
app:layout_constraintTop_toBottomOf="@id/conversation_name"
|
||||
|
@ -106,13 +106,13 @@
|
|||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
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_constraintTop_toTopOf="@id/status_display_name"
|
||||
tools:text="\@Entenhausen@birbsarecooooooooooool.site" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_timestamp_info"
|
||||
android:id="@+id/status_meta_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
android:id="@+id/status_poll_radio_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:buttonTint="@color/compound_button_color"
|
||||
tools:text="Option 1" />
|
||||
|
@ -36,6 +37,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:lines="1"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:buttonTint="@color/compound_button_color"
|
||||
tools:text="Option 1" />
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
android:textSize="?attr/status_text_medium"
|
||||
android:textStyle="normal|bold"
|
||||
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_constraintStart_toEndOf="@id/status_avatar"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_info"
|
||||
|
@ -85,13 +85,13 @@
|
|||
android:maxLines="1"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
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_constraintTop_toTopOf="@id/status_display_name"
|
||||
tools:text="\@Entenhausen@birbsarecooooooooooool.site" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_timestamp_info"
|
||||
android:id="@+id/status_meta_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
|
|
|
@ -266,7 +266,7 @@
|
|||
tools:text="7 votes • 7 hours remaining" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_timestamp_info"
|
||||
android:id="@+id/status_meta_info"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="14dp"
|
||||
|
@ -274,6 +274,7 @@
|
|||
android:layout_marginEnd="14dp"
|
||||
android:drawablePadding="4dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:lineSpacingMultiplier="1.1"
|
||||
android:textColor="?android:textColorTertiary"
|
||||
android:textSize="?attr/status_text_medium"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
|
@ -285,7 +286,7 @@
|
|||
android:id="@+id/status_info_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@id/status_timestamp_info"
|
||||
android:layout_below="@id/status_meta_info"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="14dp"
|
||||
|
@ -293,7 +294,7 @@
|
|||
android:importantForAccessibility="no"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/status_timestamp_info" />
|
||||
app:layout_constraintTop_toBottomOf="@id/status_meta_info" />
|
||||
|
||||
<TextView
|
||||
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:layout_width="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:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
|
@ -60,7 +60,7 @@
|
|||
tools:text="\@Entenhausen" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status_timestamp_info"
|
||||
android:id="@+id/status_meta_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<string name="emoji_shortcode_format" 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">
|
||||
<item>public</item>
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
<string name="title_announcements">Announcements</string>
|
||||
<string name="title_licenses">Licenses</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_boosted_format">%s boosted</string>
|
||||
|
@ -712,4 +713,9 @@
|
|||
|
||||
<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>
|
||||
|
|
Loading…
Reference in a new issue