Display mentions toot hidden (#954)
* WIP: still display mentions when toot content is hidden * WIP: still display mentions when toot content is hidden (2) * Still display mentions when toot content is hidden (3) * Factorize code for setting content and spoiler on a toot * Factorize condition
This commit is contained in:
parent
eb7b000cc1
commit
1c34d21a23
2 changed files with 92 additions and 44 deletions
|
@ -36,6 +36,7 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.lang.CharSequence;
|
||||||
|
|
||||||
import at.connyduck.sparkbutton.SparkButton;
|
import at.connyduck.sparkbutton.SparkButton;
|
||||||
import at.connyduck.sparkbutton.SparkEventListener;
|
import at.connyduck.sparkbutton.SparkEventListener;
|
||||||
|
@ -120,11 +121,46 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
username.setText(usernameText);
|
username.setText(usernameText);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContent(Spanned content, Status.Mention[] mentions, List<Emoji> emojis,
|
private void setSpoilerAndContent(StatusViewData.Concrete status,
|
||||||
StatusActionListener listener) {
|
final StatusActionListener listener) {
|
||||||
Spanned emojifiedText = CustomEmojiHelper.emojifyText(content, emojis, this.content);
|
if (status.getSpoilerText() == null || status.getSpoilerText().isEmpty()) {
|
||||||
|
contentWarningDescription.setVisibility(View.GONE);
|
||||||
|
contentWarningButton.setVisibility(View.GONE);
|
||||||
|
this.setTextVisible(true, status, listener);
|
||||||
|
} else {
|
||||||
|
boolean expanded = status.isExpanded();
|
||||||
|
CharSequence emojiSpoiler = CustomEmojiHelper.emojifyString(
|
||||||
|
status.getSpoilerText(), status.getStatusEmojis(), contentWarningDescription);
|
||||||
|
contentWarningDescription.setText(emojiSpoiler);
|
||||||
|
contentWarningDescription.setVisibility(View.VISIBLE);
|
||||||
|
contentWarningButton.setVisibility(View.VISIBLE);
|
||||||
|
contentWarningButton.setChecked(expanded);
|
||||||
|
contentWarningButton.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||||
|
contentWarningDescription.invalidate();
|
||||||
|
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||||
|
listener.onExpandedChange(isChecked, getAdapterPosition());
|
||||||
|
}
|
||||||
|
this.setTextVisible(isChecked, status, listener);
|
||||||
|
});
|
||||||
|
this.setTextVisible(expanded, status, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener);
|
private void setTextVisible(boolean visible, StatusViewData.Concrete status,
|
||||||
|
final StatusActionListener listener) {
|
||||||
|
Status.Mention[] mentions = status.getMentions();
|
||||||
|
if (visible) {
|
||||||
|
Spanned emojifiedText = CustomEmojiHelper.emojifyText(
|
||||||
|
status.getContent(), status.getStatusEmojis(), this.content);
|
||||||
|
LinkHelper.setClickableText(this.content, emojifiedText, mentions, listener);
|
||||||
|
this.content.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
if (mentions == null || mentions.length == 0) {
|
||||||
|
this.content.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
LinkHelper.setClickableMentions(this.content, mentions, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAvatar(String url, @Nullable String rebloggedUrl) {
|
void setAvatar(String url, @Nullable String rebloggedUrl) {
|
||||||
|
@ -386,32 +422,6 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
sensitiveMediaShow.setVisibility(View.GONE);
|
sensitiveMediaShow.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSpoilerText(String spoilerText, List<Emoji> emojis,
|
|
||||||
final boolean expanded, final StatusActionListener listener) {
|
|
||||||
CharSequence emojiSpoiler =
|
|
||||||
CustomEmojiHelper.emojifyString(spoilerText, emojis, contentWarningDescription);
|
|
||||||
contentWarningDescription.setText(emojiSpoiler);
|
|
||||||
contentWarningDescription.setVisibility(View.VISIBLE);
|
|
||||||
contentWarningButton.setVisibility(View.VISIBLE);
|
|
||||||
contentWarningButton.setChecked(expanded);
|
|
||||||
contentWarningButton.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
|
||||||
contentWarningDescription.invalidate();
|
|
||||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
|
||||||
listener.onExpandedChange(isChecked, getAdapterPosition());
|
|
||||||
}
|
|
||||||
content.setVisibility(isChecked ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
});
|
|
||||||
content.setVisibility(expanded ? View.VISIBLE : View.GONE);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideSpoilerText() {
|
|
||||||
contentWarningDescription.setVisibility(View.GONE);
|
|
||||||
contentWarningButton.setVisibility(View.GONE);
|
|
||||||
content.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupButtons(final StatusActionListener listener, final String accountId) {
|
private void setupButtons(final StatusActionListener listener, final String accountId) {
|
||||||
/* Originally position was passed through to all these listeners, but it caused several
|
/* Originally position was passed through to all these listeners, but it caused several
|
||||||
* bugs where other statuses in the list would be removed or added and cause the position
|
* bugs where other statuses in the list would be removed or added and cause the position
|
||||||
|
@ -509,11 +519,8 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
setupButtons(listener, status.getSenderId());
|
setupButtons(listener, status.getSenderId());
|
||||||
setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
|
setRebloggingEnabled(status.getRebloggingEnabled(), status.getVisibility());
|
||||||
if (status.getSpoilerText() == null || status.getSpoilerText().isEmpty()) {
|
|
||||||
hideSpoilerText();
|
setSpoilerAndContent(status, listener);
|
||||||
} else {
|
|
||||||
setSpoilerText(status.getSpoilerText(), status.getStatusEmojis(), status.isExpanded(), listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When viewing threads this ViewHolder is used and the main post does not have a collapse
|
// When viewing threads this ViewHolder is used and the main post does not have a collapse
|
||||||
// button by design so avoid crashing the app when that happens
|
// button by design so avoid crashing the app when that happens
|
||||||
|
@ -538,7 +545,5 @@ abstract class StatusBaseViewHolder extends RecyclerView.ViewHolder {
|
||||||
content.setFilters(NO_INPUT_FILTER);
|
content.setFilters(NO_INPUT_FILTER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContent(status.getContent(), status.getMentions(), status.getStatusEmojis(), listener);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ import android.widget.TextView;
|
||||||
import com.keylesspalace.tusky.entity.Status;
|
import com.keylesspalace.tusky.entity.Status;
|
||||||
import com.keylesspalace.tusky.interfaces.LinkListener;
|
import com.keylesspalace.tusky.interfaces.LinkListener;
|
||||||
|
|
||||||
|
import java.lang.CharSequence;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
@ -64,7 +65,6 @@ public class LinkHelper {
|
||||||
*/
|
*/
|
||||||
public static void setClickableText(TextView view, Spanned content,
|
public static void setClickableText(TextView view, Spanned content,
|
||||||
@Nullable Status.Mention[] mentions, final LinkListener listener) {
|
@Nullable Status.Mention[] mentions, final LinkListener listener) {
|
||||||
|
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
SpannableStringBuilder builder = new SpannableStringBuilder(content);
|
||||||
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class);
|
||||||
for (URLSpan span : urlSpans) {
|
for (URLSpan span : urlSpans) {
|
||||||
|
@ -117,20 +117,63 @@ public class LinkHelper {
|
||||||
/* Add zero-width space after links in end of line to fix its too large hitbox.
|
/* Add zero-width space after links in end of line to fix its too large hitbox.
|
||||||
* See also : https://github.com/tuskyapp/Tusky/issues/846
|
* See also : https://github.com/tuskyapp/Tusky/issues/846
|
||||||
* https://github.com/tuskyapp/Tusky/pull/916 */
|
* https://github.com/tuskyapp/Tusky/pull/916 */
|
||||||
if(end >= builder.length()){
|
if (end >= builder.length() ||
|
||||||
|
builder.subSequence(end, end + 1).toString().equals("\n")){
|
||||||
builder.insert(end, "\u200B");
|
builder.insert(end, "\u200B");
|
||||||
} else {
|
|
||||||
if(builder.subSequence(end, end + 1).toString().equals("\n")){
|
|
||||||
builder.insert(end, "\u200B");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
view.setText(builder);
|
view.setText(builder);
|
||||||
view.setLinksClickable(true);
|
view.setLinksClickable(true);
|
||||||
view.setMovementMethod(LinkMovementMethod.getInstance());
|
view.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put mentions in a piece of text and makes them clickable, associating them with callbacks to
|
||||||
|
* notify when they're clicked.
|
||||||
|
*
|
||||||
|
* @param view the returned text will be put in
|
||||||
|
* @param mentions any '@' mentions which are known to be in the content
|
||||||
|
* @param listener to notify about particular spans that are clicked
|
||||||
|
*/
|
||||||
|
public static void setClickableMentions(
|
||||||
|
TextView view, @Nullable Status.Mention[] mentions, final LinkListener listener) {
|
||||||
|
if (mentions == null || mentions.length == 0) {
|
||||||
|
view.setText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
|
int start = 0;
|
||||||
|
int end = 0;
|
||||||
|
int flags;
|
||||||
|
boolean firstMention = true;
|
||||||
|
for (Status.Mention mention : mentions) {
|
||||||
|
String accountUsername = mention.getLocalUsername();
|
||||||
|
final String accountId = mention.getId();
|
||||||
|
ClickableSpan customSpan = new ClickableSpanNoUnderline() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View widget) { listener.onViewAccount(accountId); }
|
||||||
|
};
|
||||||
|
|
||||||
|
end += 1 + accountUsername.length(); // length of @ + username
|
||||||
|
flags = builder.getSpanFlags(customSpan);
|
||||||
|
if (firstMention) {
|
||||||
|
firstMention = false;
|
||||||
|
} else {
|
||||||
|
builder.append(" ");
|
||||||
|
start += 1;
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
builder.append("@");
|
||||||
|
builder.append(accountUsername);
|
||||||
|
builder.setSpan(customSpan, start, end, flags);
|
||||||
|
builder.append("\u200B"); // same reasonning than in setClickableText
|
||||||
|
end += 1; // shift position to take the previous character into account
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
view.setText(builder);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a link, depending on the settings, either in the browser or in a custom tab
|
* Opens a link, depending on the settings, either in the browser or in a custom tab
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue