Yuito-app-android/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java

699 lines
24 KiB
Java

/* Copyright 2017 Andrew Dawson
*
* 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.viewdata;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import androidx.annotation.Nullable;
import com.keylesspalace.tusky.entity.Attachment;
import com.keylesspalace.tusky.entity.Card;
import com.keylesspalace.tusky.entity.Emoji;
import com.keylesspalace.tusky.entity.Poll;
import com.keylesspalace.tusky.entity.Status;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* Created by charlag on 11/07/2017.
* <p>
* Class to represent data required to display either a notification or a placeholder.
* It is either a {@link StatusViewData.Concrete} or a {@link StatusViewData.Placeholder}.
*/
public abstract class StatusViewData {
private StatusViewData() { }
public abstract long getViewDataId();
public abstract boolean deepEquals(StatusViewData other);
public static final class Concrete extends StatusViewData {
private static final char SOFT_HYPHEN = '\u00ad';
private static final char ASCII_HYPHEN = '-';
private final String id;
private final Spanned content;
final boolean reblogged;
final boolean favourited;
final boolean bookmarked;
private final boolean muted;
@Nullable
private final String spoilerText;
private final Status.Visibility visibility;
private final List<Attachment> attachments;
@Nullable
private final String rebloggedByUsername;
@Nullable
private final String rebloggedAvatar;
private final boolean isSensitive;
final boolean isExpanded;
private final boolean isShowingContent;
private final String userFullName;
private final String nickname;
private final String avatar;
private final Date createdAt;
private final int reblogsCount;
private final int favouritesCount;
@Nullable
private final String inReplyToId;
// I would rather have something else but it would be too much of a rewrite
@Nullable
private final Status.Mention[] mentions;
private final String senderId;
private final boolean rebloggingEnabled;
private final Status.Application application;
private final List<Emoji> statusEmojis;
private final List<Emoji> accountEmojis;
@Nullable
private final Card card;
private final boolean isCollapsible; /** Whether the status meets the requirement to be collapse */
final boolean isCollapsed; /** Whether the status is shown partially or fully */
@Nullable
private final PollViewData poll;
private final boolean isBot;
private final Status quote;
private final boolean isNotestock;
public Concrete(String id, Spanned content, boolean reblogged, boolean favourited, boolean bookmarked, boolean muted,
@Nullable String spoilerText, Status.Visibility visibility, List<Attachment> attachments,
@Nullable String rebloggedByUsername, @Nullable String rebloggedAvatar, boolean sensitive, boolean isExpanded,
boolean isShowingContent, String userFullName, String nickname, String avatar,
Date createdAt, int reblogsCount, int favouritesCount, @Nullable String inReplyToId,
@Nullable Status.Mention[] mentions, String senderId, boolean rebloggingEnabled,
Status.Application application, List<Emoji> statusEmojis, List<Emoji> accountEmojis, @Nullable Card card,
boolean isCollapsible, boolean isCollapsed, @Nullable PollViewData poll, boolean isBot, Status quote, boolean isNotestock) {
this.id = id;
if (Build.VERSION.SDK_INT == 23) {
// https://github.com/tuskyapp/Tusky/issues/563
this.content = replaceCrashingCharacters(content);
this.spoilerText = spoilerText == null ? null : replaceCrashingCharacters(spoilerText).toString();
this.nickname = replaceCrashingCharacters(nickname).toString();
} else {
this.content = content;
this.spoilerText = spoilerText;
this.nickname = nickname;
}
this.reblogged = reblogged;
this.favourited = favourited;
this.bookmarked = bookmarked;
this.muted = muted;
this.visibility = visibility;
this.attachments = attachments;
this.rebloggedByUsername = rebloggedByUsername;
this.rebloggedAvatar = rebloggedAvatar;
this.isSensitive = sensitive;
this.isExpanded = isExpanded;
this.isShowingContent = isShowingContent;
this.userFullName = userFullName;
this.avatar = avatar;
this.createdAt = createdAt;
this.reblogsCount = reblogsCount;
this.favouritesCount = favouritesCount;
this.inReplyToId = inReplyToId;
this.mentions = mentions;
this.senderId = senderId;
this.rebloggingEnabled = rebloggingEnabled;
this.application = application;
this.statusEmojis = statusEmojis;
this.accountEmojis = accountEmojis;
this.card = card;
this.isCollapsible = isCollapsible;
this.isCollapsed = isCollapsed;
this.poll = poll;
this.isBot = isBot;
this.quote = quote;
this.isNotestock = isNotestock;
}
public String getId() {
return id;
}
public Spanned getContent() {
return content;
}
public boolean isReblogged() {
return reblogged;
}
public boolean isFavourited() {
return favourited;
}
public boolean isBookmarked() {
return bookmarked;
}
public boolean isMuted() {
return muted;
}
@Nullable
public String getSpoilerText() {
return spoilerText;
}
public Status.Visibility getVisibility() {
if (visibility == null) {
return Status.Visibility.UNKNOWN;
}
return visibility;
}
public List<Attachment> getAttachments() {
return attachments;
}
@Nullable
public String getRebloggedByUsername() {
return rebloggedByUsername;
}
public boolean isSensitive() {
return isSensitive;
}
public boolean isExpanded() {
return isExpanded;
}
public boolean isShowingContent() {
return isShowingContent;
}
public boolean isBot(){ return isBot; }
@Nullable
public String getRebloggedAvatar() {
return rebloggedAvatar;
}
public String getUserFullName() {
return userFullName;
}
public String getNickname() {
return nickname;
}
public String getAvatar() {
return avatar;
}
public Date getCreatedAt() {
return createdAt;
}
public int getReblogsCount() {
return reblogsCount;
}
public int getFavouritesCount() {
return favouritesCount;
}
@Nullable
public String getInReplyToId() {
return inReplyToId;
}
public String getSenderId() {
return senderId;
}
public Boolean getRebloggingEnabled() {
return rebloggingEnabled;
}
@Nullable
public Status.Mention[] getMentions() {
return mentions;
}
public Status.Application getApplication() {
return application;
}
public List<Emoji> getStatusEmojis() {
return statusEmojis;
}
public List<Emoji> getAccountEmojis() {
return accountEmojis;
}
@Nullable
public Card getCard() {
return card;
}
/**
* Specifies whether the content of this post is allowed to be collapsed or if it should show
* all content regardless.
*
* @return Whether the post is collapsible or never collapsed.
*/
public boolean isCollapsible() {
return isCollapsible;
}
/**
* Specifies whether the content of this post is currently limited in visibility to the first
* 500 characters or not.
*
* @return Whether the post is collapsed or fully expanded.
*/
public boolean isCollapsed() {
return isCollapsed;
}
@Nullable
public PollViewData getPoll() {
return poll;
}
public Status getQuote() {
return quote;
}
public boolean isNotestock() {
return isNotestock;
}
@Override public long getViewDataId() {
// Chance of collision is super low and impact of mistake is low as well
return id.hashCode();
}
public boolean deepEquals(StatusViewData o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Concrete concrete = (Concrete) o;
return reblogged == concrete.reblogged &&
favourited == concrete.favourited &&
bookmarked == concrete.bookmarked &&
isSensitive == concrete.isSensitive &&
isExpanded == concrete.isExpanded &&
isShowingContent == concrete.isShowingContent &&
isBot == concrete.isBot &&
reblogsCount == concrete.reblogsCount &&
favouritesCount == concrete.favouritesCount &&
rebloggingEnabled == concrete.rebloggingEnabled &&
Objects.equals(id, concrete.id) &&
Objects.equals(content, concrete.content) &&
Objects.equals(spoilerText, concrete.spoilerText) &&
visibility == concrete.visibility &&
Objects.equals(attachments, concrete.attachments) &&
Objects.equals(rebloggedByUsername, concrete.rebloggedByUsername) &&
Objects.equals(rebloggedAvatar, concrete.rebloggedAvatar) &&
Objects.equals(userFullName, concrete.userFullName) &&
Objects.equals(nickname, concrete.nickname) &&
Objects.equals(avatar, concrete.avatar) &&
Objects.equals(createdAt, concrete.createdAt) &&
Objects.equals(inReplyToId, concrete.inReplyToId) &&
Arrays.equals(mentions, concrete.mentions) &&
Objects.equals(senderId, concrete.senderId) &&
Objects.equals(application, concrete.application) &&
Objects.equals(statusEmojis, concrete.statusEmojis) &&
Objects.equals(accountEmojis, concrete.accountEmojis) &&
Objects.equals(card, concrete.card) &&
Objects.equals(poll, concrete.poll) &&
isCollapsed == concrete.isCollapsed &&
Objects.equals(quote, concrete.quote);
}
static Spanned replaceCrashingCharacters(Spanned content) {
return (Spanned) replaceCrashingCharacters((CharSequence) content);
}
static CharSequence replaceCrashingCharacters(CharSequence content) {
boolean replacing = false;
SpannableStringBuilder builder = null;
int length = content.length();
for (int index = 0; index < length; ++index) {
char character = content.charAt(index);
// If there are more than one or two, switch to a map
if (character == SOFT_HYPHEN) {
if (!replacing) {
replacing = true;
builder = new SpannableStringBuilder(content, 0, index);
}
builder.append(ASCII_HYPHEN);
} else if (replacing) {
builder.append(character);
}
}
return replacing ? builder : content;
}
}
public static final class Placeholder extends StatusViewData {
private final boolean isLoading;
private final String id;
public Placeholder(String id, boolean isLoading) {
this.id = id;
this.isLoading = isLoading;
}
public boolean isLoading() {
return isLoading;
}
public String getId() {
return id;
}
@Override public long getViewDataId() {
return id.hashCode();
}
@Override public boolean deepEquals(StatusViewData other) {
if (!(other instanceof Placeholder)) return false;
Placeholder that = (Placeholder) other;
return isLoading == that.isLoading && id.equals(that.id);
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Placeholder that = (Placeholder) o;
return deepEquals(that);
}
@Override
public int hashCode() {
int result = (isLoading ? 1 : 0);
result = 31 * result + id.hashCode();
return result;
}
}
public static class Builder {
private String id;
private Spanned content;
private boolean reblogged;
private boolean favourited;
private boolean bookmarked;
private boolean muted;
private String spoilerText;
private Status.Visibility visibility;
private List<Attachment> attachments;
private String rebloggedByUsername;
private String rebloggedAvatar;
private boolean isSensitive;
private boolean isExpanded;
private boolean isShowingContent;
private String userFullName;
private String nickname;
private String avatar;
private Date createdAt;
private int reblogsCount;
private int favouritesCount;
private String inReplyToId;
private Status.Mention[] mentions;
private String senderId;
private boolean rebloggingEnabled;
private Status.Application application;
private List<Emoji> statusEmojis;
private List<Emoji> accountEmojis;
private Card card;
private boolean isCollapsible; /** Whether the status meets the requirement to be collapsed */
private boolean isCollapsed; /** Whether the status is shown partially or fully */
private PollViewData poll;
private boolean isBot;
private Status quote;
private boolean isNotestock;
public Builder() {
}
public Builder(final StatusViewData.Concrete viewData) {
id = viewData.id;
content = viewData.content;
reblogged = viewData.reblogged;
favourited = viewData.favourited;
bookmarked = viewData.bookmarked;
muted = viewData.muted;
spoilerText = viewData.spoilerText;
visibility = viewData.visibility;
attachments = viewData.attachments == null ? null : new ArrayList<>(viewData.attachments);
rebloggedByUsername = viewData.rebloggedByUsername;
rebloggedAvatar = viewData.rebloggedAvatar;
isSensitive = viewData.isSensitive;
isExpanded = viewData.isExpanded;
isShowingContent = viewData.isShowingContent;
userFullName = viewData.userFullName;
nickname = viewData.nickname;
avatar = viewData.avatar;
createdAt = new Date(viewData.createdAt.getTime());
reblogsCount = viewData.reblogsCount;
favouritesCount = viewData.favouritesCount;
inReplyToId = viewData.inReplyToId;
mentions = viewData.mentions == null ? null : viewData.mentions.clone();
senderId = viewData.senderId;
rebloggingEnabled = viewData.rebloggingEnabled;
application = viewData.application;
statusEmojis = viewData.getStatusEmojis();
accountEmojis = viewData.getAccountEmojis();
card = viewData.getCard();
isCollapsible = viewData.isCollapsible();
isCollapsed = viewData.isCollapsed();
poll = viewData.poll;
isBot = viewData.isBot();
quote = viewData.getQuote();
isNotestock = viewData.isNotestock;
}
public Builder setId(String id) {
this.id = id;
return this;
}
public Builder setContent(Spanned content) {
this.content = content;
return this;
}
public Builder setReblogged(boolean reblogged) {
this.reblogged = reblogged;
return this;
}
public Builder setFavourited(boolean favourited) {
this.favourited = favourited;
return this;
}
public Builder setBookmarked(boolean bookmarked) {
this.bookmarked = bookmarked;
return this;
}
public Builder setMuted(boolean muted) {
this.muted = muted;
return this;
}
public Builder setSpoilerText(String spoilerText) {
this.spoilerText = spoilerText;
return this;
}
public Builder setVisibility(Status.Visibility visibility) {
this.visibility = visibility;
return this;
}
public Builder setAttachments(List<Attachment> attachments) {
if (attachments == null) {
this.attachments = new ArrayList<>();
return this;
}
this.attachments = attachments;
return this;
}
public Builder setRebloggedByUsername(String rebloggedByUsername) {
this.rebloggedByUsername = rebloggedByUsername;
return this;
}
public Builder setRebloggedAvatar(String rebloggedAvatar) {
this.rebloggedAvatar = rebloggedAvatar;
return this;
}
public Builder setSensitive(boolean sensitive) {
this.isSensitive = sensitive;
return this;
}
public Builder setIsExpanded(boolean isExpanded) {
this.isExpanded = isExpanded;
return this;
}
public Builder setIsShowingSensitiveContent(boolean isShowingSensitiveContent) {
this.isShowingContent = isShowingSensitiveContent;
return this;
}
public Builder setIsBot(boolean isBot) {
this.isBot = isBot;
return this;
}
public Builder setUserFullName(String userFullName) {
this.userFullName = userFullName;
return this;
}
public Builder setNickname(String nickname) {
this.nickname = nickname;
return this;
}
public Builder setAvatar(String avatar) {
this.avatar = avatar;
return this;
}
public Builder setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
return this;
}
public Builder setReblogsCount(int reblogsCount) {
this.reblogsCount = reblogsCount;
return this;
}
public Builder setFavouritesCount(int favouritesCount) {
this.favouritesCount = favouritesCount;
return this;
}
public Builder setInReplyToId(String inReplyToId) {
this.inReplyToId = inReplyToId;
return this;
}
public Builder setMentions(Status.Mention[] mentions) {
this.mentions = mentions;
return this;
}
public Builder setSenderId(String senderId) {
this.senderId = senderId;
return this;
}
public Builder setRebloggingEnabled(boolean rebloggingEnabled) {
this.rebloggingEnabled = rebloggingEnabled;
return this;
}
public Builder setApplication(Status.Application application) {
this.application = application;
return this;
}
public Builder setStatusEmojis(List<Emoji> emojis) {
this.statusEmojis = emojis;
return this;
}
public Builder setAccountEmojis(List<Emoji> emojis) {
this.accountEmojis = emojis;
return this;
}
public Builder setCard(Card card) {
this.card = card;
return this;
}
/**
* Configure the {@link com.keylesspalace.tusky.viewdata.StatusViewData} to support collapsing
* its content limiting the visible length when collapsed at 500 characters,
*
* @param collapsible Whether the status should support being collapsed or not.
* @return This {@link com.keylesspalace.tusky.viewdata.StatusViewData.Builder} instance.
*/
public Builder setCollapsible(boolean collapsible) {
isCollapsible = collapsible;
return this;
}
/**
* Configure the {@link com.keylesspalace.tusky.viewdata.StatusViewData} to start in a collapsed
* state, hiding partially the content of the post if it exceeds a certain amount of characters.
*
* @param collapsed Whether to show the full content of the status or not.
* @return This {@link com.keylesspalace.tusky.viewdata.StatusViewData.Builder} instance.
*/
public Builder setCollapsed(boolean collapsed) {
isCollapsed = collapsed;
return this;
}
public Builder setPoll(Poll poll) {
this.poll = PollViewDataKt.toViewData(poll);
return this;
}
public Builder setQuote(Status quote){
this.quote = quote;
return this;
}
public Builder setIsNotestock(boolean isNotestock){
this.isNotestock = isNotestock;
return this;
}
public StatusViewData.Concrete createStatusViewData() {
if (this.statusEmojis == null) statusEmojis = Collections.emptyList();
if (this.accountEmojis == null) accountEmojis = Collections.emptyList();
if (this.createdAt == null) createdAt = new Date();
return new StatusViewData.Concrete(id, content, reblogged, favourited, bookmarked, muted, spoilerText,
visibility, attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded,
isShowingContent, userFullName, nickname, avatar, createdAt, reblogsCount,
favouritesCount, inReplyToId, mentions, senderId, rebloggingEnabled, application,
statusEmojis, accountEmojis, card, isCollapsible, isCollapsed, poll, isBot, quote, isNotestock);
}
}
}