TubeLab-App-Android/app/src/main/java/app/fedilab/fedilabtube/client/entities/Status.java

657 lines
24 KiB
Java

package app.fedilab.fedilabtube.client.entities;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.text.style.QuoteSpan;
import android.text.style.URLSpan;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.fedilab.fedilabtube.R;
import app.fedilab.fedilabtube.asynctasks.RetrieveFeedsAsyncTask;
import app.fedilab.fedilabtube.helper.CustomQuoteSpan;
import app.fedilab.fedilabtube.helper.Helper;
@SuppressWarnings("unused")
public class Status implements Parcelable {
public static final Creator<Status> CREATOR = new Creator<Status>() {
@Override
public Status createFromParcel(Parcel source) {
return new Status(source);
}
@Override
public Status[] newArray(int size) {
return new Status[size];
}
};
private String id;
private String uri;
private String url;
private Account account;
private String in_reply_to_id;
private String in_reply_to_account_id;
private Date created_at;
private int favourites_count;
private int replies_count;
private boolean favourited;
private boolean muted;
private boolean pinned;
private boolean sensitive;
private boolean bookmarked;
private String visibility;
private List<Mention> mentions;
private List<Tag> tags;
private String language;
private String content;
private SpannableString contentSpan;
private transient RetrieveFeedsAsyncTask.Type type;
private String conversationId;
private String contentType;
public void setContent(String content) {
this.content = content;
}
public Status() {
}
protected Status(Parcel in) {
this.id = in.readString();
this.uri = in.readString();
this.url = in.readString();
this.account = in.readParcelable(Account.class.getClassLoader());
this.in_reply_to_id = in.readString();
this.in_reply_to_account_id = in.readString();
long tmpCreated_at = in.readLong();
this.created_at = tmpCreated_at == -1 ? null : new Date(tmpCreated_at);
this.favourites_count = in.readInt();
this.replies_count = in.readInt();
this.favourited = in.readByte() != 0;
this.muted = in.readByte() != 0;
this.pinned = in.readByte() != 0;
this.sensitive = in.readByte() != 0;
this.bookmarked = in.readByte() != 0;
this.visibility = in.readString();
this.mentions = in.createTypedArrayList(Mention.CREATOR);
this.tags = in.createTypedArrayList(Tag.CREATOR);
this.language = in.readString();
this.content = in.readString();
this.contentSpan = (SpannableString) TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
int tmpType = in.readInt();
this.type = tmpType == -1 ? null : RetrieveFeedsAsyncTask.Type.values()[tmpType];
this.conversationId = in.readString();
this.contentType = in.readString();
}
public static void fillSpan(WeakReference<Context> contextWeakReference, Status status) {
Status.transform(contextWeakReference, status);
}
private static void transform(WeakReference<Context> contextWeakReference, Status status) {
Context context = contextWeakReference.get();
if (status == null)
return;
SpannableString spannableStringContent, spannableStringCW;
if (status.getContent() == null)
return;
String content = status.getContent();
Pattern aLink = Pattern.compile("<a((?!href).)*href=\"([^\"]*)\"[^>]*(((?!</a).)*)</a>");
Matcher matcherALink = aLink.matcher(content);
int count = 0;
while (matcherALink.find()) {
String beforemodification;
String urlText = matcherALink.group(3);
assert urlText != null;
urlText = urlText.substring(1);
beforemodification = urlText;
if (!beforemodification.startsWith("http")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
urlText = new SpannableString(Html.fromHtml(urlText, Html.FROM_HTML_MODE_LEGACY)).toString();
else
urlText = new SpannableString(Html.fromHtml(urlText)).toString();
if (urlText.startsWith("http")) {
urlText = urlText.replace("http://", "").replace("https://", "").replace("www.", "");
if (urlText.length() > 31) {
urlText = urlText.substring(0, 30);
urlText += "" + count;
count++;
}
} else if (urlText.startsWith("@")) {
urlText += "|" + count;
count++;
}
content = content.replaceFirst(Pattern.quote(beforemodification), Matcher.quoteReplacement(urlText));
}
}
content = content.replaceAll("(<\\s?p\\s?>)&gt;(((?!(</p>)|(<br)).){5,})(<\\s?/p\\s?><\\s?p\\s?>|<\\s?br\\s?/?>|<\\s?/p\\s?>$)", "<blockquote>$2</blockquote><p>");
content = content.replaceAll("^<\\s?p\\s?>(.*)<\\s?/p\\s?>$", "$1");
spannableStringContent = new SpannableString(content);
if (spannableStringContent.length() > 0)
status.setContentSpan(treatment(context, spannableStringContent, status));
}
private static SpannableString treatment(final Context context, SpannableString spannableString, Status status) {
URLSpan[] urls = spannableString.getSpans(0, spannableString.length(), URLSpan.class);
for (URLSpan span : urls)
spannableString.removeSpan(span);
List<Mention> mentions = status.getMentions();
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
Matcher matcher;
Pattern linkPattern = Pattern.compile("<a((?!href).)*href=\"([^\"]*)\"[^>]*(((?!</a).)*)</a>");
matcher = linkPattern.matcher(spannableString);
LinkedHashMap<String, String> targetedURL = new LinkedHashMap<>();
HashMap<String, Account> accountsMentionUnknown = new HashMap<>();
String liveInstance = Helper.getLiveInstance(context);
int i = 1;
while (matcher.find()) {
String key;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
key = new SpannableString(Html.fromHtml(matcher.group(3), Html.FROM_HTML_MODE_LEGACY)).toString();
else
key = new SpannableString(Html.fromHtml(matcher.group(3))).toString();
key = key.substring(1);
if (!key.startsWith("#") && !key.startsWith("@") && !key.trim().equals("") && !Objects.requireNonNull(matcher.group(2)).contains("search?tag=") && !Objects.requireNonNull(matcher.group(2)).contains(liveInstance + "/users/")) {
String url;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
url = Html.fromHtml(matcher.group(2), Html.FROM_HTML_MODE_LEGACY).toString();
} else {
url = Html.fromHtml(matcher.group(2)).toString();
}
targetedURL.put(key + "|" + i, url);
i++;
} else if (key.startsWith("@") || Objects.requireNonNull(matcher.group(2)).contains(liveInstance + "/users/")) {
String acct;
String url;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
url = Html.fromHtml(matcher.group(2), Html.FROM_HTML_MODE_LEGACY).toString();
} else {
url = Html.fromHtml(matcher.group(2)).toString();
}
URI uri;
String instance = null;
try {
uri = new URI(url);
instance = uri.getHost();
} catch (URISyntaxException e) {
if (url.contains("|")) {
try {
uri = new URI(url.split("\\|")[0]);
instance = uri.getHost();
} catch (URISyntaxException ex) {
ex.printStackTrace();
}
}
}
if (key.startsWith("@"))
acct = key.substring(1).split("\\|")[0];
else
acct = key.split("\\|")[0];
Account account = new Account();
account.setAcct(acct);
account.setInstance(instance);
account.setUrl(url);
String accountId = null;
if (mentions != null) {
for (Mention mention : mentions) {
String[] accountMentionAcct = mention.getAcct().split("@");
//Different isntance
if (accountMentionAcct.length > 1) {
if (mention.getAcct().equals(account.getAcct() + "@" + account.getInstance())) {
accountId = mention.getId();
break;
}
} else {
if (mention.getAcct().equals(account.getAcct())) {
accountId = mention.getId();
break;
}
}
}
}
if (accountId != null) {
account.setId(accountId);
}
accountsMentionUnknown.put(key, account);
}
}
SpannableStringBuilder spannableStringT;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
spannableStringT = new SpannableStringBuilder(Html.fromHtml(spannableString.toString().replaceAll("[\\s]{2}", "&nbsp;&nbsp;"), Html.FROM_HTML_MODE_LEGACY));
else
spannableStringT = new SpannableStringBuilder(Html.fromHtml(spannableString.toString().replaceAll("[\\s]{2}", "&nbsp;&nbsp;")));
replaceQuoteSpans(context, spannableStringT);
URLSpan[] spans = spannableStringT.getSpans(0, spannableStringT.length(), URLSpan.class);
for (URLSpan span : spans) {
spannableStringT.removeSpan(span);
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (accountsMentionUnknown.size() > 0) {
Iterator<Map.Entry<String, Account>> it = accountsMentionUnknown.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Account> pair = it.next();
String key = pair.getKey();
Account account = pair.getValue();
String targetedAccount = "@" + account.getAcct();
if (spannableStringT.toString().toLowerCase().contains(targetedAccount.toLowerCase())) {
int startPosition = spannableStringT.toString().toLowerCase().indexOf(key.toLowerCase());
int endPosition = startPosition + key.length();
if (key.contains("|")) {
key = key.split("\\|")[0];
SpannableStringBuilder ssb = new SpannableStringBuilder();
ssb.append(spannableStringT, 0, spannableStringT.length());
if (ssb.length() >= endPosition) {
ssb.replace(startPosition, endPosition, key);
}
spannableStringT = SpannableStringBuilder.valueOf(ssb);
endPosition = startPosition + key.length();
}
//Accounts can be mentioned several times so we have to loop
if (startPosition >= 0 && endPosition <= spannableStringT.toString().length() && endPosition >= startPosition)
spannableStringT.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View textView) {
if (account.getId() != null) {
/* Intent intent = new Intent(context, ShowAccountActivity.class);
Bundle b = new Bundle();
b.putString("accountId", account.getId());
intent.putExtras(b);
context.startActivity(intent);*/
}
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
}
},
startPosition, endPosition,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
it.remove();
}
}
if (targetedURL.size() > 0) {
Iterator<Map.Entry<String, String>> it = targetedURL.entrySet().iterator();
int endPosition = 0;
while (it.hasNext()) {
Map.Entry<String, String> pair = it.next();
String key = (pair.getKey()).split("\\|")[0];
String url = pair.getValue();
if (spannableStringT.toString().toLowerCase().contains(key.toLowerCase())) {
//Accounts can be mentioned several times so we have to loop
int startPosition = spannableStringT.toString().toLowerCase().indexOf(key.toLowerCase(), endPosition);
if (startPosition >= 0) {
endPosition = startPosition + key.length();
if (key.contains("") && !key.endsWith("")) {
key = key.split("")[0] + "";
SpannableStringBuilder ssb = new SpannableStringBuilder();
ssb.append(spannableStringT, 0, spannableStringT.length());
if (ssb.length() >= endPosition) {
ssb.replace(startPosition, endPosition, key);
}
spannableStringT = SpannableStringBuilder.valueOf(ssb);
endPosition = startPosition + key.length();
}
}
}
it.remove();
}
}
matcher = Helper.hashtagPattern.matcher(spannableStringT);
while (matcher.find()) {
int matchStart = matcher.start(1);
int matchEnd = matcher.end();
final String tag = spannableStringT.toString().substring(matchStart, matchEnd);
if (matchStart >= 0 && matchEnd <= spannableStringT.toString().length() && matchEnd >= matchStart)
spannableStringT.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View textView) {
/* Intent intent = new Intent(context, HashTagActivity.class);
Bundle b = new Bundle();
b.putString("tag", tag.substring(1));
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);*/
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
Pattern carriagePattern = Pattern.compile("(\\n)+$");
matcher = carriagePattern.matcher(spannableStringT);
while (matcher.find()) {
int matchStart = matcher.start();
int matchEnd = matcher.end();
if (matchStart >= 0 && matchEnd <= spannableStringT.toString().length() && matchEnd >= matchStart) {
spannableStringT.delete(matchStart, matchEnd);
}
}
return SpannableString.valueOf(spannableStringT);
}
private static void replaceQuoteSpans(Context context, Spannable spannable) {
QuoteSpan[] quoteSpans = spannable.getSpans(0, spannable.length(), QuoteSpan.class);
for (QuoteSpan quoteSpan : quoteSpans) {
int start = spannable.getSpanStart(quoteSpan);
int end = spannable.getSpanEnd(quoteSpan);
int flags = spannable.getSpanFlags(quoteSpan);
spannable.removeSpan(quoteSpan);
spannable.setSpan(new CustomQuoteSpan(
ContextCompat.getColor(context, android.R.color.transparent),
R.color.colorAccent,
10,
20),
start,
end,
flags);
}
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.id);
dest.writeString(this.uri);
dest.writeString(this.url);
dest.writeParcelable(this.account, flags);
dest.writeString(this.in_reply_to_id);
dest.writeString(this.in_reply_to_account_id);
dest.writeLong(this.created_at != null ? this.created_at.getTime() : -1);
dest.writeInt(this.favourites_count);
dest.writeInt(this.replies_count);
dest.writeByte(this.favourited ? (byte) 1 : (byte) 0);
dest.writeByte(this.muted ? (byte) 1 : (byte) 0);
dest.writeByte(this.pinned ? (byte) 1 : (byte) 0);
dest.writeByte(this.sensitive ? (byte) 1 : (byte) 0);
dest.writeByte(this.bookmarked ? (byte) 1 : (byte) 0);
dest.writeString(this.visibility);
dest.writeTypedList(this.mentions);
dest.writeTypedList(this.tags);
dest.writeString(this.language);
dest.writeString(this.content);
TextUtils.writeToParcel(this.contentSpan, dest, flags);
dest.writeInt(this.type == null ? -1 : this.type.ordinal());
dest.writeString(this.conversationId);
dest.writeString(this.contentType);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public String getIn_reply_to_id() {
return in_reply_to_id;
}
public void setIn_reply_to_id(String in_reply_to_id) {
this.in_reply_to_id = in_reply_to_id;
}
public String getIn_reply_to_account_id() {
return in_reply_to_account_id;
}
public void setIn_reply_to_account_id(String in_reply_to_account_id) {
this.in_reply_to_account_id = in_reply_to_account_id;
}
public String getContent() {
return content;
}
public Date getCreated_at() {
return created_at;
}
public void setCreated_at(Date created_at) {
this.created_at = created_at;
}
public int getFavourites_count() {
return favourites_count;
}
public void setFavourites_count(int favourites_count) {
this.favourites_count = favourites_count;
}
public boolean isFavourited() {
return favourited;
}
public void setFavourited(boolean favourited) {
this.favourited = favourited;
}
public boolean isPinned() {
return pinned;
}
public void setPinned(boolean pinned) {
this.pinned = pinned;
}
public boolean isSensitive() {
return sensitive;
}
public void setSensitive(boolean sensitive) {
this.sensitive = sensitive;
}
public List<Mention> getMentions() {
return mentions;
}
public void setMentions(List<Mention> mentions) {
this.mentions = mentions;
}
public List<Tag> getTags() {
return tags;
}
public void setTags(List<Tag> tags) {
this.tags = tags;
}
public String getTagsString() {
//iterate through tags and create comma delimited string of tag names
StringBuilder tag_names = new StringBuilder();
for (Tag t : tags) {
if (tag_names.toString().equals("")) {
tag_names = new StringBuilder(t.getName());
} else {
tag_names.append(", ").append(t.getName());
}
}
return tag_names.toString();
}
public String getVisibility() {
return visibility;
}
public void setVisibility(String visibility) {
this.visibility = visibility;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public SpannableString getContentSpan() {
return contentSpan;
}
public void setContentSpan(SpannableString contentSpan) {
this.contentSpan = contentSpan;
}
@Override
public boolean equals(Object otherStatus) {
return otherStatus != null && (otherStatus == this || otherStatus instanceof Status && this.getId().equals(((Status) otherStatus).getId()));
}
public boolean isMuted() {
return muted;
}
public void setMuted(boolean muted) {
this.muted = muted;
}
public boolean isBookmarked() {
return bookmarked;
}
public void setBookmarked(boolean bookmarked) {
this.bookmarked = bookmarked;
}
public int getReplies_count() {
return replies_count;
}
public void setReplies_count(int replies_count) {
this.replies_count = replies_count;
}
public RetrieveFeedsAsyncTask.Type getType() {
return type;
}
public void setType(RetrieveFeedsAsyncTask.Type type) {
this.type = type;
}
public String getConversationId() {
return conversationId;
}
public void setConversationId(String conversationId) {
this.conversationId = conversationId;
}
@Override
public int describeContents() {
return 0;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
}