Skip to content

Commit

Permalink
3771: Add "display replied to" functionality (#4834)
Browse files Browse the repository at this point in the history
Earlier PR: #3778

Fixes: #3771
  • Loading branch information
Lakoja authored Jan 6, 2025
1 parent c630c47 commit d6b276d
Show file tree
Hide file tree
Showing 20 changed files with 104 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -404,13 +404,12 @@ private CharSequence getCreatedAtDescription(Date createdAt,
}
}

protected void setIsReply(boolean isReply) {
protected void setReplyButtonImage(boolean isReply) {
if (isReply) {
replyButton.setImageResource(R.drawable.ic_reply_all_24dp);
} else {
replyButton.setImageResource(R.drawable.ic_reply_24dp);
}

}

protected void setReplyCount(int repliesCount, boolean fullStats) {
Expand Down Expand Up @@ -771,21 +770,17 @@ private void showConfirmFavourite(StatusActionListener listener,
popup.show();
}

public void setupWithStatus(@NonNull StatusViewData.Concrete status, final @NonNull StatusActionListener listener,
@NonNull StatusDisplayOptions statusDisplayOptions) {
this.setupWithStatus(status, listener, statusDisplayOptions, null);
}

public void setupWithStatus(@NonNull StatusViewData.Concrete status,
@NonNull final StatusActionListener listener,
@NonNull StatusDisplayOptions statusDisplayOptions,
@Nullable Object payloads) {
@Nullable Object payloads,
final boolean showStatusInfo) {
if (payloads == null) {
Status actionable = status.getActionable();
setDisplayName(actionable.getAccount().getName(), actionable.getAccount().getEmojis(), statusDisplayOptions);
setUsername(actionable.getAccount().getUsername());
setMetaData(status, statusDisplayOptions, listener);
setIsReply(actionable.getInReplyToId() != null);
setReplyButtonImage(actionable.isReply());
setReplyCount(actionable.getRepliesCount(), statusDisplayOptions.showStatsInline());
setAvatar(actionable.getAccount().getAvatar(), status.getRebloggedAvatar(),
actionable.getAccount().getBot(), statusDisplayOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,14 @@ private void setReblogAndFavCount(int reblogCount, int favCount, StatusActionLis
public void setupWithStatus(@NonNull final StatusViewData.Concrete status,
@NonNull final StatusActionListener listener,
@NonNull StatusDisplayOptions statusDisplayOptions,
@Nullable Object payloads) {
@Nullable Object payloads,
final boolean showStatusInfo) {
// We never collapse statuses in the detail view
StatusViewData.Concrete uncollapsedStatus = (status.isCollapsible() && status.isCollapsed()) ?
status.copyWithCollapsed(false) :
status;

super.setupWithStatus(uncollapsedStatus, listener, statusDisplayOptions, payloads);
super.setupWithStatus(uncollapsedStatus, listener, statusDisplayOptions, payloads, showStatusInfo);
setupCard(uncollapsedStatus, status.isExpanded(), CardViewMode.FULL_WIDTH, statusDisplayOptions, listener); // Always show card for detailed status
if (payloads == null) {
Status actionable = uncollapsedStatus.getActionable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import androidx.recyclerview.widget.RecyclerView;

import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.Filter;
import com.keylesspalace.tusky.entity.Status;
import com.keylesspalace.tusky.entity.TimelineAccount;
import com.keylesspalace.tusky.interfaces.StatusActionListener;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.NumberUtils;
Expand All @@ -38,7 +38,8 @@
import com.keylesspalace.tusky.util.StringUtils;
import com.keylesspalace.tusky.viewdata.StatusViewData;

import java.util.List;
import java.util.Collections;
import java.util.Objects;

import at.connyduck.sparkbutton.helpers.Utils;

Expand All @@ -63,48 +64,80 @@ public StatusViewHolder(@NonNull View itemView) {
public void setupWithStatus(@NonNull StatusViewData.Concrete status,
@NonNull final StatusActionListener listener,
@NonNull StatusDisplayOptions statusDisplayOptions,
@Nullable Object payloads) {
@Nullable Object payloads,
final boolean showStatusInfo) {
if (payloads == null) {

boolean sensitive = !TextUtils.isEmpty(status.getActionable().getSpoilerText());
boolean expanded = status.isExpanded();

setupCollapsedState(sensitive, expanded, status, listener);

Status reblogging = status.getRebloggingStatus();
if (reblogging == null || status.getFilterAction() == Filter.Action.WARN) {
if (!showStatusInfo || status.getFilterAction() == Filter.Action.WARN) {
hideStatusInfo();
} else {
String rebloggedByDisplayName = reblogging.getAccount().getName();
setRebloggedByDisplayName(rebloggedByDisplayName,
reblogging.getAccount().getEmojis(), statusDisplayOptions);
statusInfo.setOnClickListener(v -> listener.onOpenReblog(getBindingAdapterPosition()));
}
Status rebloggingStatus = status.getRebloggingStatus();
boolean isReplyOnly = rebloggingStatus == null && status.isReply();
boolean isReplySelf = isReplyOnly && status.isSelfReply();

boolean hasStatusInfo = rebloggingStatus != null | isReplyOnly;

TimelineAccount statusInfoAccount = rebloggingStatus != null ? rebloggingStatus.getAccount() : status.getRepliedToAccount();

if (!hasStatusInfo) {
hideStatusInfo();
} else {
setStatusInfoContent(statusInfoAccount, isReplyOnly, isReplySelf, statusDisplayOptions);
}

if (isReplyOnly) {
statusInfo.setOnClickListener(null);
} else {
statusInfo.setOnClickListener(v -> listener.onOpenReblog(getBindingAdapterPosition()));
}
}
}

reblogsCountLabel.setVisibility(statusDisplayOptions.showStatsInline() ? View.VISIBLE : View.INVISIBLE);
favouritedCountLabel.setVisibility(statusDisplayOptions.showStatsInline() ? View.VISIBLE : View.INVISIBLE);
setFavouritedCount(status.getActionable().getFavouritesCount());
setReblogsCount(status.getActionable().getReblogsCount());

super.setupWithStatus(status, listener, statusDisplayOptions, payloads);
super.setupWithStatus(status, listener, statusDisplayOptions, payloads, showStatusInfo);
}

private void setRebloggedByDisplayName(final CharSequence name,
final List<Emoji> accountEmoji,
final StatusDisplayOptions statusDisplayOptions) {
private void setStatusInfoContent(final TimelineAccount account,
final boolean isReply,
final boolean isSelfReply,
final StatusDisplayOptions statusDisplayOptions) {
Context context = statusInfo.getContext();
CharSequence wrappedName = StringUtils.unicodeWrap(name);
CharSequence boostedText = context.getString(R.string.post_boosted_format, wrappedName);
CharSequence accountName = account != null ? account.getName() : "";
CharSequence wrappedName = StringUtils.unicodeWrap(accountName);
CharSequence translatedText = "";

if (!isReply) {
translatedText = context.getString(R.string.post_boosted_format, wrappedName);
} else if (isSelfReply) {
translatedText = context.getString(R.string.post_replied_self);
} else {
if (account != null && accountName.length() > 0) {
translatedText = context.getString(R.string.post_replied_format, wrappedName);
} else {
translatedText = context.getString(R.string.post_replied);
}
}

CharSequence emojifiedText = CustomEmojiHelper.emojify(
boostedText, accountEmoji, statusInfo, statusDisplayOptions.animateEmojis()
translatedText,
account != null ? account.getEmojis() : Collections.emptyList(),
statusInfo,
statusDisplayOptions.animateEmojis()
);
statusInfo.setText(emojifiedText);
statusInfo.setCompoundDrawablesWithIntrinsicBounds(isReply ? R.drawable.ic_reply_all_18dp : R.drawable.ic_reblog_18dp, 0, 0, 0);
statusInfo.setVisibility(View.VISIBLE);
}

// don't use this on the same ViewHolder as setRebloggedByDisplayName, will cause recycling issues as paddings are changed
// don't use this on the same ViewHolder as setStatusInfoContent, will cause recycling issues as paddings are changed
protected void setPollInfo(final boolean ownPoll) {
statusInfo.setText(ownPoll ? R.string.poll_ended_created : R.string.poll_ended_voted);
statusInfo.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_poll_24dp, 0, 0, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ void setupWithConversation(
setDisplayName(displayName, account.getEmojis(), statusDisplayOptions);
setUsername(account.getUsername());
setMetaData(statusViewData, statusDisplayOptions, listener);
setIsReply(status.getInReplyToId() != null);
setFavourited(status.getFavourited());
setBookmarked(status.getBookmarked());
List<Attachment> attachments = status.getAttachments();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ internal class StatusNotificationViewHolder(
showNotificationContent(false)
} else {
showNotificationContent(true)
val (_, _, account, _, _, _, _, createdAt) = statusViewData.actionable
val account = statusViewData.actionable.account
val createdAt = statusViewData.actionable.createdAt
setDisplayName(account.name, account.emojis, statusDisplayOptions.animateEmojis)
setUsername(account.username)
setCreatedAt(createdAt, statusDisplayOptions.useAbsoluteTime)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ internal class StatusViewHolder(
statusViewData,
statusActionListener,
statusDisplayOptions,
payloads.firstOrNull()
payloads.firstOrNull(),
false
)
}
if (viewData.type == Notification.Type.POLL) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class SearchStatusesAdapter(

override fun onBindViewHolder(holder: StatusViewHolder, position: Int) {
getItem(position)?.let { item ->
holder.setupWithStatus(item, statusListener, statusDisplayOptions)
holder.setupWithStatus(item, statusListener, statusDisplayOptions, null, true)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ class TimelinePagingAdapter(
viewData,
statusListener,
statusDisplayOptions,
if (payloads != null && payloads.isNotEmpty()) payloads[0] else null
if (payloads != null && payloads.isNotEmpty()) payloads[0] else null,
true
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ fun Status.toEntity(
)

fun TimelineStatusEntity.toStatus(
account: TimelineAccountEntity
account: TimelineAccountEntity,
) = Status(
id = serverId,
url = url,
Expand Down Expand Up @@ -192,6 +192,7 @@ fun HomeTimelineData.toViewData(isDetailed: Boolean = false, translation: Transl
isShowingContent = this.status.contentShowing,
isCollapsed = this.status.contentCollapsed,
isDetailed = isDetailed,
repliedToAccount = repliedToAccount?.toAccount(),
translation = translation,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ThreadAdapter(
if (viewHolder is FilteredStatusViewHolder) {
viewHolder.bind(status)
} else if (viewHolder is StatusBaseViewHolder) {
viewHolder.setupWithStatus(status, statusActionListener, statusDisplayOptions)
viewHolder.setupWithStatus(status, statusActionListener, statusDisplayOptions, null, false)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class ViewThreadViewModel @Inject constructor(
isShowingContent = statusAndAccount.first.contentShowing,
isCollapsed = statusAndAccount.first.contentCollapsed,
isDetailed = true,
// NOTE repliedToAccount is null here: this avoids showing "in reply to" over every post
translation = null
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,16 @@ rb.serverId as 'rb_serverId', rb.tuskyAccountId 'rb_tuskyAccountId',
rb.localUsername as 'rb_localUsername', rb.username as 'rb_username',
rb.displayName as 'rb_displayName', rb.url as 'rb_url', rb.avatar as 'rb_avatar',
rb.emojis as 'rb_emojis', rb.bot as 'rb_bot',
replied.serverId as 'replied_serverId', replied.tuskyAccountId 'replied_tuskyAccountId',
replied.localUsername as 'replied_localUsername', replied.username as 'replied_username',
replied.displayName as 'replied_displayName', replied.url as 'replied_url', replied.avatar as 'replied_avatar',
replied.emojis as 'replied_emojis', replied.bot as 'replied_bot',
h.loading
FROM HomeTimelineEntity h
LEFT JOIN TimelineStatusEntity s ON (h.statusId = s.serverId AND s.tuskyAccountId = :tuskyAccountId)
LEFT JOIN TimelineAccountEntity a ON (s.authorServerId = a.serverId AND a.tuskyAccountId = :tuskyAccountId)
LEFT JOIN TimelineAccountEntity rb ON (h.reblogAccountId = rb.serverId AND rb.tuskyAccountId = :tuskyAccountId)
LEFT JOIN TimelineAccountEntity replied ON (s.inReplyToAccountId = replied.serverId)
WHERE h.tuskyAccountId = :tuskyAccountId
ORDER BY LENGTH(h.id) DESC, h.id DESC"""
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@ data class HomeTimelineData(
@Embedded val status: TimelineStatusEntity?,
@Embedded(prefix = "a_") val account: TimelineAccountEntity?,
@Embedded(prefix = "rb_") val reblogAccount: TimelineAccountEntity?,
@Embedded(prefix = "replied_") val repliedToAccount: TimelineAccountEntity?,
val loading: Boolean
)
3 changes: 3 additions & 0 deletions app/src/main/java/com/keylesspalace/tusky/entity/Status.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ data class Status(
val actionableStatus: Status
get() = reblog ?: this

val isReply: Boolean
get() = inReplyToId != null

@JsonClass(generateAdapter = false)
enum class Visibility(val int: Int) {
UNKNOWN(0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import android.text.Spanned
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.entity.TimelineAccount
import com.keylesspalace.tusky.entity.Translation
import com.keylesspalace.tusky.util.parseAsMastodonHtml
import com.keylesspalace.tusky.util.shouldTrimStatus
Expand Down Expand Up @@ -55,6 +56,7 @@ sealed class StatusViewData {
*/
val isCollapsed: Boolean,
val isDetailed: Boolean = false,
val repliedToAccount: TimelineAccount? = null,
val translation: TranslationViewData? = null,
) : StatusViewData() {
override val id: String
Expand Down Expand Up @@ -105,6 +107,12 @@ sealed class StatusViewData {
val rebloggingStatus: Status?
get() = if (status.reblog != null) status else null

val isReply: Boolean
get() = status.inReplyToAccountId != null

val isSelfReply: Boolean
get() = status.inReplyToAccountId == status.account.id

/** Helper for Java */
fun copyWithCollapsed(isCollapsed: Boolean): Concrete {
return copy(isCollapsed = isCollapsed)
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_reply_all_18dp.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:autoMirrored="true"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="?android:attr/textColorTertiary"
android:pathData="M7,8L7,5l-7,7 7,7v-3l-4,-4 4,-4zM13,9L13,5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
</vector>
2 changes: 1 addition & 1 deletion app/src/main/res/drawable/ic_reply_all_24dp.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#fff"
android:fillColor="?android:attr/textColorTertiary"
android:pathData="M7,8L7,5l-7,7 7,7v-3l-4,-4 4,-4zM13,9L13,5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
</vector>
1 change: 1 addition & 0 deletions app/src/main/res/layout/item_status.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
android:textColor="?android:textColorTertiary"
android:textSize="?attr/status_text_medium"
app:drawableStartCompat="@drawable/ic_reblog_18dp"
app:drawableTint="?android:textColorTertiary"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@

<string name="post_username_format">\@%1$s</string>
<string name="post_boosted_format">%1$s boosted</string>
<string name="post_replied">Replied</string>
<string name="post_replied_format">In reply to %1$s</string>
<string name="post_replied_self">Continued thread</string>
<string name="post_sensitive_media_title">Sensitive content</string>
<string name="post_media_hidden_title">Media hidden</string>
<string name="post_media_alt">ALT</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ fun fakeHomeTimelineData(
tuskyAccountId = tuskyAccountId,
)
},
repliedToAccount = null,
loading = false
)
}
Expand All @@ -144,6 +145,7 @@ fun fakePlaceholderHomeTimelineData(
account = null,
status = null,
reblogAccount = null,
repliedToAccount = null,
loading = false
)

Expand Down

0 comments on commit d6b276d

Please sign in to comment.