Improve interactions

This commit is contained in:
Thomas 2022-12-31 14:59:23 +01:00
parent 12fae6059a
commit 112701dd86
6 changed files with 297 additions and 742 deletions

View file

@ -115,7 +115,7 @@ public class Account implements Serializable {
if (display_name == null || display_name.isEmpty()) { if (display_name == null || display_name.isEmpty()) {
display_name = username; display_name = username;
} }
return SpannableHelper.convert(context, display_name, null, this, null, false, false, viewWeakReference); return SpannableHelper.convert(context, display_name, null, this, null, viewWeakReference);
} }
public synchronized Spannable getSpanDisplayName(Activity activity, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanDisplayName(Activity activity, WeakReference<View> viewWeakReference) {
@ -126,11 +126,11 @@ public class Account implements Serializable {
} }
public synchronized Spannable getSpanDisplayNameTitle(Context context, WeakReference<View> viewWeakReference, String title) { public synchronized Spannable getSpanDisplayNameTitle(Context context, WeakReference<View> viewWeakReference, String title) {
return SpannableHelper.convert(context, title, null, this, null, false, false, viewWeakReference); return SpannableHelper.convert(context, title, null, this, null, viewWeakReference);
} }
public synchronized Spannable getSpanNote(Context context, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanNote(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, note, null, this, null, true, true, viewWeakReference); return SpannableHelper.convert(context, note, null, this, null, viewWeakReference);
} }
public static class AccountParams implements Serializable { public static class AccountParams implements Serializable {

View file

@ -56,7 +56,7 @@ public class Announcement {
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) {
return SpannableHelper.convert(context, content, null, null, this, true, false, viewWeakReference); return SpannableHelper.convert(context, content, null, null, this, viewWeakReference);
} }
} }

View file

@ -47,7 +47,7 @@ public class Field implements Serializable {
if (verified_at != null && value != null) { if (verified_at != null && value != null) {
value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text)); value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text));
} }
Spannable spannable = SpannableHelper.convert(context, value, null, account, null, true, true, viewWeakReference); Spannable spannable = SpannableHelper.convert(context, value, null, account, null, viewWeakReference);
if (value_span != null && spannable != null) { if (value_span != null && spannable != null) {
spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
@ -57,7 +57,7 @@ public class Field implements Serializable {
public synchronized Spannable getLabelSpan(Context context, Account account, WeakReference<View> viewWeakReference) { public synchronized Spannable getLabelSpan(Context context, Account account, WeakReference<View> viewWeakReference) {
Spannable spannable = SpannableHelper.convert(context, name, null, account, null, true, true, viewWeakReference); Spannable spannable = SpannableHelper.convert(context, name, null, account, null, viewWeakReference);
if (name_span != null && spannable != null) { if (name_span != null && spannable != null) {
spannable.setSpan(name_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(name_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} }

View file

@ -61,7 +61,7 @@ public class Poll implements Serializable {
public transient Spannable span_title; public transient Spannable span_title;
public Spannable getSpanTitle(Context context, Status status, WeakReference<View> viewWeakReference) { public Spannable getSpanTitle(Context context, Status status, WeakReference<View> viewWeakReference) {
span_title = SpannableHelper.convert(context, title, status, null, null, false, false, viewWeakReference); span_title = SpannableHelper.convert(context, title, status, null, null, viewWeakReference);
return span_title; return span_title;
} }
} }

View file

@ -134,21 +134,21 @@ public class Status implements Serializable, Cloneable {
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference, Callback callback) { public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentSpan == null) { if (contentSpan == null) {
contentSpan = SpannableHelper.convert(context, content, this, null, null, true, false, viewWeakReference, callback); contentSpan = SpannableHelper.convert(context, content, this, null, null, viewWeakReference, callback);
} }
return contentSpan; return contentSpan;
} }
public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference, Callback callback) { public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentSpoilerSpan == null) { if (contentSpoilerSpan == null) {
contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, true, false, viewWeakReference, callback); contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, viewWeakReference, callback);
} }
return contentSpoilerSpan; return contentSpoilerSpan;
} }
public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference, Callback callback) { public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentTranslateSpan == null) { if (contentTranslateSpan == null) {
contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, true, false, viewWeakReference, callback); contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, viewWeakReference, callback);
} }
return contentTranslateSpan; return contentTranslateSpan;
} }

View file

@ -52,12 +52,6 @@ import androidx.lifecycle.ViewModelStoreOwner;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@ -96,18 +90,16 @@ public class SpannableHelper {
public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN"; public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN";
public static Spannable convert(Context context, String text, public static Spannable convert(Context context, String text,
Status status, Account account, Announcement announcement, Status status, Account account, Announcement announcement, WeakReference<View> viewWeakReference) {
boolean convertHtml, boolean forceMentions, WeakReference<View> viewWeakReference) { return convert(context, text, status, account, announcement, viewWeakReference, null);
return convert(context, text, status, account, announcement, convertHtml, forceMentions, viewWeakReference, null);
} }
private static int linkColor; private static int linkColor;
public static Spannable convert(Context context, String text, public static Spannable convert(Context context, String text,
Status status, Account account, Announcement announcement, Status status, Account account, Announcement announcement,
boolean convertHtml,
boolean forceMentions,
WeakReference<View> viewWeakReference, Status.Callback callback) { WeakReference<View> viewWeakReference, Status.Callback callback) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
@ -131,24 +123,125 @@ public class SpannableHelper {
if (linkColor == 0) { if (linkColor == 0) {
linkColor = -1; linkColor = -1;
} }
SpannableString initialContent; List<Mention> mentions = new ArrayList<>();
if (text == null) { if (status != null) {
return null; mentions.addAll(status.mentions);
}
text = text.replaceAll("((<\\s?p\\s?>|<\\s?br\\s?/?>)&gt;(((?!([<])).)*))", "$2<blockquote>$3</blockquote>");
text = text.trim().replaceAll("\\s{3}", "&nbsp;&nbsp;&nbsp;");
text = text.trim().replaceAll("\\s{2}", "&nbsp;&nbsp;");
SpannableString initialContent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
initialContent = new SpannableString(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY));
else
initialContent = new SpannableString(Html.fromHtml(text));
//Get all links
SpannableStringBuilder content = new SpannableStringBuilder(initialContent);
URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class);
//Loop through links
for (URLSpan span : urls) {
String url = span.getURL();
int start = content.getSpanStart(span);
int end = content.getSpanEnd(span);
content.removeSpan(span);
//Get the matching word associated to the URL
String word = content.subSequence(start, end).toString();
if (word.startsWith("@") || word.startsWith("#")) {
content.setSpan(new LongClickableSpan() {
@Override
public void onLongClick(View textView) {
textView.setTag(CLICKABLE_SPAN);
if (word.startsWith("#") && BaseMainActivity.filterFetched && MainActivity.mainFilters != null) {
String tag = word.trim();
if (!tag.startsWith("#")) {
tag = "#" + tag;
}
Filter fedilabFilter = null;
for (Filter filter : MainActivity.mainFilters) {
if (filter.title.equals(Helper.FEDILAB_MUTED_HASHTAGS)) {
fedilabFilter = filter;
break;
}
}
//Filter for Fedilab doesn't exist we have to create it
if (fedilabFilter == null) {
Filter.FilterParams filterParams = new Filter.FilterParams();
filterParams.title = Helper.FEDILAB_MUTED_HASHTAGS;
filterParams.filter_action = "hide";
filterParams.context = new ArrayList<>();
filterParams.context.add("home");
filterParams.context.add("public");
filterParams.context.add("thread");
filterParams.context.add("account");
String finalTag = tag;
FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class);
filtersVM.addFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterParams)
.observe((LifecycleOwner) context, filter -> {
if (filter != null) {
MainActivity.mainFilters.add(filter);
addTagToFilter(context, finalTag, status, filter);
}
});
} else {
addTagToFilter(context, tag, status, fedilabFilter);
} }
Document htmlContent = Jsoup.parse(text);
Elements mentionElements = htmlContent.select("a.mention");
//We keep a reference to mentions
HashMap<String, String> mentionsMap = new HashMap<>();
if (mentionElements.size() > 0) {
for (int i = 0; i < mentionElements.size(); i++) {
Element mentionElement = mentionElements.get(i);
String href = mentionElement.attr("href");
String mention = mentionElement.text();
mentionsMap.put(mention, href);
} }
} }
text = text.replaceAll("((<\\s?p\\s?>|<\\s?br\\s?\\/?>)&gt;(((?!([<])).)*))", "$2<blockquote>$3</blockquote>"); @Override
public void onClick(@NonNull View textView) {
textView.setTag(CLICKABLE_SPAN);
Intent intent;
Bundle b;
if (word.startsWith("#")) {
intent = new Intent(context, HashTagActivity.class);
b = new Bundle();
b.putString(Helper.ARG_SEARCH_KEYWORD, word.trim());
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} else if (word.startsWith("@")) {
intent = new Intent(context, ProfileActivity.class);
b = new Bundle();
Mention targetedMention = null;
for (Mention mention : mentions) {
if (word.compareToIgnoreCase("@" + mention.username) == 0) {
targetedMention = mention;
break;
}
}
if (targetedMention != null) {
b.putString(Helper.ARG_USER_ID, targetedMention.id);
} else {
b.putString(Helper.ARG_MENTION, word);
}
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);
if (linkColor != -1) {
ds.setColor(linkColor);
}
}
}, start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
} else {
makeLinks(context, content, url, start, end);
}
replaceQuoteSpans(context, content);
emails(context, content);
}
Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>"); Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>");
Matcher matcherImg = imgPattern.matcher(text); Matcher matcherImg = imgPattern.matcher(text);
HashMap<String, String> imagesToReplace = new LinkedHashMap<>(); HashMap<String, String> imagesToReplace = new LinkedHashMap<>();
@ -160,57 +253,15 @@ public class SpannableHelper {
text = text.replaceAll(Pattern.quote(matcherImg.group()), replacement); text = text.replaceAll(Pattern.quote(matcherImg.group()), replacement);
} }
SpannableStringBuilder content;
View view = viewWeakReference.get(); View view = viewWeakReference.get();
List<Mention> mentionList = null;
List<Emoji> emojiList = null; List<Emoji> emojiList = null;
if (status != null) { if (status != null) {
mentionList = status.mentions;
emojiList = status.emojis; emojiList = status.emojis;
} else if (account != null) { } else if (account != null) {
emojiList = account.emojis; emojiList = account.emojis;
} else if (announcement != null) { } else if (announcement != null) {
emojiList = announcement.emojis; emojiList = announcement.emojis;
} }
//UrlDetails will contain links having a text different from the url
HashMap<String, String> urlDetails = new HashMap<>();
if (convertHtml) {
Matcher matcherALink = Helper.aLink.matcher(text);
//We stock details
while (matcherALink.find()) {
String urlText = matcherALink.group(3);
String url = matcherALink.group(2);
if (urlText != null && urlText.startsWith(">")) {
urlText = urlText.substring(1);
}
if (url != null && urlText != null && !url.equalsIgnoreCase(urlText) && !urlText.contains("<span")) {
urlDetails.put(url, urlText);
}
}
text = text.trim().replaceAll("\\s{3}", "&nbsp;&nbsp;&nbsp;");
text = text.trim().replaceAll("\\s{2}", "&nbsp;&nbsp;");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
initialContent = new SpannableString(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY));
else
initialContent = new SpannableString(Html.fromHtml(text));
content = new SpannableStringBuilder(initialContent);
URLSpan[] urls = content.getSpans(0, (content.length() - 1), URLSpan.class);
for (URLSpan span : urls) {
content.removeSpan(span);
}
//Make tags, mentions, groups
interaction(context, content, status, mentionList, forceMentions, mentionsMap);
//Make all links
linkify(context, content, urlDetails);
linkifyURL(context, content, urlDetails);
emails(context, content);
gemini(context, content);
replaceQuoteSpans(context, content);
} else {
content = new SpannableStringBuilder(text);
}
boolean animate = !sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false); boolean animate = !sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view)); CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
content = customEmoji.makeEmoji(content, emojiList, animate, callback); content = customEmoji.makeEmoji(content, emojiList, animate, callback);
@ -234,307 +285,23 @@ public class SpannableHelper {
return trimSpannable(new SpannableStringBuilder(content)); return trimSpannable(new SpannableStringBuilder(content));
} }
private static void linkify(Context context, SpannableStringBuilder content, HashMap<String, String> urlDetails) {
//--- URLs ----
Matcher matcherLink = Patterns.WEB_URL.matcher(content);
int offSetTruncate = 0; private static void makeLinks(Context context, SpannableStringBuilder content, String url, int start, int end) {
String newUrl = url;
while (matcherLink.find()) {
int matchStart = matcherLink.start() - offSetTruncate;
int matchEnd = matchStart + matcherLink.group().length();
if (matchEnd > content.toString().length()) {
matchEnd = content.toString().length();
}
if (content.toString().length() < matchEnd || matchStart < 0 || matchStart > matchEnd) {
continue;
}
final String url = content.toString().substring(matchStart, matchEnd);
if (urlDetails.containsKey(url)) {
continue;
}
ClickableSpan[] clickableSpans = content.getSpans(matchStart, matchEnd, ClickableSpan.class);
if (clickableSpans != null) {
for (ClickableSpan clickableSpan : clickableSpans) {
content.removeSpan(clickableSpan);
}
}
content.removeSpan(clickableSpans);
String newURL = Helper.transformURL(context, url); String newURL = Helper.transformURL(context, url);
//If URL has been transformed //If URL has been transformed
if (newURL.compareTo(url) != 0) { if (newURL.compareTo(url) != 0) {
content.replace(matchStart, matchEnd, newURL); content.replace(start, end, newURL);
offSetTruncate -= (newURL.length() - url.length()); end = start + newURL.length();
matchEnd = matchStart + newURL.length(); url = newURL;
} }
if (url.length() > 30 && (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("gimini://"))) {
//Truncate URL if needed newUrl = url.substring(0, 30);
//TODO: add an option to disable truncated URLs newUrl += "";
String urlText = newURL; content.replace(start, end, newUrl);
if (newURL.length() > 30 && !urlDetails.containsKey(urlText) && !urlText.startsWith("gemini")) {
urlText = urlText.substring(0, 30);
urlText += "";
content.replace(matchStart, matchEnd, urlText);
matchEnd = matchStart + 31;
offSetTruncate += (newURL.length() - urlText.length());
} }
int matchEnd = start + newUrl.length();
String finalUrl = url;
if (matchEnd <= content.length() && matchEnd >= matchStart) {
content.setSpan(new LongClickableSpan() {
@Override
public void onLongClick(View view) {
Context mContext = view.getContext();
MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(mContext);
PopupLinksBinding popupLinksBinding = PopupLinksBinding.inflate(LayoutInflater.from(context));
materialAlertDialogBuilder.setView(popupLinksBinding.getRoot());
AlertDialog alertDialog = materialAlertDialogBuilder.create();
alertDialog.show();
String finalURl = newURL;
String uniqueUrl = newURL.endsWith("") ? newURL : newURL + "";
if (urlDetails.containsValue(uniqueUrl)) {
finalURl = Helper.getKeyByValue(urlDetails, uniqueUrl);
}
if (finalURl == null) {
return;
}
if (finalURl.startsWith("http://")) {
finalURl = finalURl.replace("http://", "https://");
}
String finalURl1 = finalURl;
popupLinksBinding.displayFullLink.setOnClickListener(v -> {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setMessage(finalURl1);
builder.setTitle(context.getString(R.string.display_full_link));
builder.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss())
.show();
alertDialog.dismiss();
});
popupLinksBinding.shareLink.setOnClickListener(v -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.shared_via));
sendIntent.putExtra(Intent.EXTRA_TEXT, finalURl1);
sendIntent.setType("text/plain");
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent intentChooser = Intent.createChooser(sendIntent, context.getString(R.string.share_with));
intentChooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intentChooser);
alertDialog.dismiss();
});
popupLinksBinding.openOtherApp.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(finalURl1));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(intent);
} catch (Exception e) {
Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show();
}
alertDialog.dismiss();
});
popupLinksBinding.copyLink.setOnClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(Helper.CLIP_BOARD, finalURl1);
if (clipboard != null) {
clipboard.setPrimaryClip(clip);
Toasty.info(context, context.getString(R.string.clipboard_url), Toast.LENGTH_LONG).show();
}
alertDialog.dismiss();
});
popupLinksBinding.checkRedirect.setOnClickListener(v -> {
try {
URL finalUrlCheck = new URL(finalURl1);
new Thread(() -> {
try {
String redirect = null;
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) finalUrlCheck.openConnection();
httpsURLConnection.setConnectTimeout(10 * 1000);
httpsURLConnection.setRequestProperty("http.keepAlive", "false");
// httpsURLConnection.setRequestProperty("User-Agent", USER_AGENT);
httpsURLConnection.setRequestMethod("HEAD");
httpsURLConnection.setInstanceFollowRedirects(false);
if (httpsURLConnection.getResponseCode() == 301 || httpsURLConnection.getResponseCode() == 302) {
Map<String, List<String>> map = httpsURLConnection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
if (entry.toString().toLowerCase().startsWith("location")) {
Matcher matcher = Patterns.WEB_URL.matcher(entry.toString());
if (matcher.find()) {
redirect = matcher.group(1);
}
}
}
}
httpsURLConnection.getInputStream().close();
if (redirect != null && redirect.compareTo(finalURl1) != 0) {
URL redirectURL = new URL(redirect);
String host = redirectURL.getHost();
String protocol = redirectURL.getProtocol();
if (protocol == null || host == null) {
redirect = null;
}
}
Handler mainHandler = new Handler(context.getMainLooper());
String finalRedirect = redirect;
Runnable myRunnable = () -> {
AlertDialog.Builder builder1 = new AlertDialog.Builder(view.getContext());
if (finalRedirect != null) {
builder1.setMessage(context.getString(R.string.redirect_detected, finalURl1, finalRedirect));
builder1.setNegativeButton(R.string.copy_link, (dialog, which) -> {
ClipboardManager clipboard1 = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip1 = ClipData.newPlainText(Helper.CLIP_BOARD, finalRedirect);
if (clipboard1 != null) {
clipboard1.setPrimaryClip(clip1);
Toasty.info(context, context.getString(R.string.clipboard_url), Toast.LENGTH_LONG).show();
}
dialog.dismiss();
});
builder1.setNeutralButton(R.string.share_link, (dialog, which) -> {
Intent sendIntent1 = new Intent(Intent.ACTION_SEND);
sendIntent1.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.shared_via));
sendIntent1.putExtra(Intent.EXTRA_TEXT, finalURl1);
sendIntent1.setType("text/plain");
context.startActivity(Intent.createChooser(sendIntent1, context.getString(R.string.share_with)));
dialog.dismiss();
});
} else {
builder1.setMessage(R.string.no_redirect);
}
builder1.setTitle(context.getString(R.string.check_redirect));
builder1.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss())
.show();
};
mainHandler.post(myRunnable);
} catch (IOException e) {
e.printStackTrace();
}
}).start();
} catch (MalformedURLException e) {
e.printStackTrace();
}
alertDialog.dismiss();
});
}
@Override
public void onClick(@NonNull View textView) {
String finalURl = newURL;
String finalURl2 = url;
String uniqueNewURL = newURL.endsWith("") ? newURL : newURL + "";
if (urlDetails.containsValue(uniqueNewURL)) {
finalURl = Helper.getKeyByValue(urlDetails, uniqueNewURL);
}
String uniqueUrl = url.endsWith("") ? url : url + "";
if (urlDetails.containsValue(uniqueUrl)) {
finalURl2 = Helper.getKeyByValue(urlDetails, uniqueUrl);
}
textView.setTag(CLICKABLE_SPAN);
Pattern link = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w._-]*[0-9]*)(/[0-9]+)?$");
Matcher matcherLink = null;
if (finalURl2 != null) {
matcherLink = link.matcher(finalURl2);
}
if (finalURl2 != null && matcherLink.find() && !finalURl2.contains("medium.com")) {
if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalURl2, new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class);
intent.putExtra(Helper.ARG_STATUS, status);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
@Override
public void federatedAccount(Account account) {
}
});
} else {//It's an account
CrossActionHelper.fetchRemoteAccount(context, currentAccount, matcherLink.group(2) + "@" + matcherLink.group(1), new CrossActionHelper.Callback() {
@Override
public void federatedStatus(Status status) {
}
@Override
public void federatedAccount(Account account) {
Intent intent = new Intent(context, ProfileActivity.class);
Bundle b = new Bundle();
b.putSerializable(Helper.ARG_ACCOUNT, account);
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
});
}
} else {
Helper.openBrowser(context, finalURl);
}
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
if (linkColor != -1) {
ds.setColor(linkColor);
}
}
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
private static void linkifyURL(Context context, SpannableStringBuilder content, HashMap<String, String> urlDetails) {
for (Map.Entry<String, String> entry : urlDetails.entrySet()) {
String value = entry.getValue();
if (value.startsWith("@") || value.startsWith("#")) {
continue;
}
SpannableString contentUrl;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
contentUrl = new SpannableString(Html.fromHtml(value, Html.FROM_HTML_MODE_LEGACY));
else
contentUrl = new SpannableString(Html.fromHtml(value));
if (contentUrl.toString().trim().isEmpty()) {
continue;
}
Pattern word = Pattern.compile(Pattern.quote(contentUrl.toString()));
Matcher matcherLink = word.matcher(content);
while (matcherLink.find()) {
String url = entry.getKey();
int matchStart = matcherLink.start();
int matchEnd = matchStart + matcherLink.group().length();
if (matchEnd > content.toString().length()) {
matchEnd = content.toString().length();
}
if (content.toString().length() < matchEnd || matchStart < 0 || matchStart > matchEnd) {
continue;
}
ClickableSpan[] clickableSpans = content.getSpans(matchStart, matchEnd, ClickableSpan.class);
if (clickableSpans != null) {
for (ClickableSpan clickableSpan : clickableSpans) {
content.removeSpan(clickableSpan);
}
}
content.removeSpan(clickableSpans);
if (matchEnd <= content.length()) {
content.setSpan(new LongClickableSpan() { content.setSpan(new LongClickableSpan() {
@Override @Override
public void onLongClick(View view) { public void onLongClick(View view) {
@ -544,14 +311,9 @@ public class SpannableHelper {
dialogBuilder.setView(popupLinksBinding.getRoot()); dialogBuilder.setView(popupLinksBinding.getRoot());
AlertDialog alertDialog = dialogBuilder.create(); AlertDialog alertDialog = dialogBuilder.create();
alertDialog.show(); alertDialog.show();
String finalURl = url;
if (urlDetails.containsValue(url)) {
finalURl = Helper.getKeyByValue(urlDetails, url);
}
String finalURl1 = finalURl;
popupLinksBinding.displayFullLink.setOnClickListener(v -> { popupLinksBinding.displayFullLink.setOnClickListener(v -> {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext); AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setMessage(finalURl1); builder.setMessage(finalUrl);
builder.setTitle(context.getString(R.string.display_full_link)); builder.setTitle(context.getString(R.string.display_full_link));
builder.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss()) builder.setPositiveButton(R.string.close, (dialog, which) -> dialog.dismiss())
.show(); .show();
@ -560,7 +322,7 @@ public class SpannableHelper {
popupLinksBinding.shareLink.setOnClickListener(v -> { popupLinksBinding.shareLink.setOnClickListener(v -> {
Intent sendIntent = new Intent(Intent.ACTION_SEND); Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.shared_via)); sendIntent.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.shared_via));
sendIntent.putExtra(Intent.EXTRA_TEXT, finalURl1); sendIntent.putExtra(Intent.EXTRA_TEXT, finalUrl);
sendIntent.setType("text/plain"); sendIntent.setType("text/plain");
sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent intentChooser = Intent.createChooser(sendIntent, context.getString(R.string.share_with)); Intent intentChooser = Intent.createChooser(sendIntent, context.getString(R.string.share_with));
@ -571,7 +333,7 @@ public class SpannableHelper {
popupLinksBinding.openOtherApp.setOnClickListener(v -> { popupLinksBinding.openOtherApp.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(finalURl1)); intent.setData(Uri.parse(finalUrl));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try { try {
context.startActivity(intent); context.startActivity(intent);
@ -583,7 +345,7 @@ public class SpannableHelper {
popupLinksBinding.copyLink.setOnClickListener(v -> { popupLinksBinding.copyLink.setOnClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(Helper.CLIP_BOARD, finalURl1); ClipData clip = ClipData.newPlainText(Helper.CLIP_BOARD, finalUrl);
if (clipboard != null) { if (clipboard != null) {
clipboard.setPrimaryClip(clip); clipboard.setPrimaryClip(clip);
Toasty.info(context, context.getString(R.string.clipboard_url), Toast.LENGTH_LONG).show(); Toasty.info(context, context.getString(R.string.clipboard_url), Toast.LENGTH_LONG).show();
@ -594,7 +356,7 @@ public class SpannableHelper {
popupLinksBinding.checkRedirect.setOnClickListener(v -> { popupLinksBinding.checkRedirect.setOnClickListener(v -> {
try { try {
URL finalUrlCheck = new URL(finalURl1); URL finalUrlCheck = new URL(finalUrl);
new Thread(() -> { new Thread(() -> {
try { try {
String redirect = null; String redirect = null;
@ -616,7 +378,7 @@ public class SpannableHelper {
} }
} }
httpsURLConnection.getInputStream().close(); httpsURLConnection.getInputStream().close();
if (redirect != null && finalURl1 != null && redirect.compareTo(finalURl1) != 0) { if (redirect != null && redirect.compareTo(finalUrl) != 0) {
URL redirectURL = new URL(redirect); URL redirectURL = new URL(redirect);
String host = redirectURL.getHost(); String host = redirectURL.getHost();
String protocol = redirectURL.getProtocol(); String protocol = redirectURL.getProtocol();
@ -629,7 +391,7 @@ public class SpannableHelper {
Runnable myRunnable = () -> { Runnable myRunnable = () -> {
AlertDialog.Builder builder1 = new AlertDialog.Builder(view.getContext()); AlertDialog.Builder builder1 = new AlertDialog.Builder(view.getContext());
if (finalRedirect != null) { if (finalRedirect != null) {
builder1.setMessage(context.getString(R.string.redirect_detected, finalURl1, finalRedirect)); builder1.setMessage(context.getString(R.string.redirect_detected, finalUrl, finalRedirect));
builder1.setNegativeButton(R.string.copy_link, (dialog, which) -> { builder1.setNegativeButton(R.string.copy_link, (dialog, which) -> {
ClipboardManager clipboard1 = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard1 = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip1 = ClipData.newPlainText(Helper.CLIP_BOARD, finalRedirect); ClipData clip1 = ClipData.newPlainText(Helper.CLIP_BOARD, finalRedirect);
@ -642,7 +404,7 @@ public class SpannableHelper {
builder1.setNeutralButton(R.string.share_link, (dialog, which) -> { builder1.setNeutralButton(R.string.share_link, (dialog, which) -> {
Intent sendIntent1 = new Intent(Intent.ACTION_SEND); Intent sendIntent1 = new Intent(Intent.ACTION_SEND);
sendIntent1.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.shared_via)); sendIntent1.putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.shared_via));
sendIntent1.putExtra(Intent.EXTRA_TEXT, finalURl1); sendIntent1.putExtra(Intent.EXTRA_TEXT, finalUrl);
sendIntent1.setType("text/plain"); sendIntent1.setType("text/plain");
context.startActivity(Intent.createChooser(sendIntent1, context.getString(R.string.share_with))); context.startActivity(Intent.createChooser(sendIntent1, context.getString(R.string.share_with)));
dialog.dismiss(); dialog.dismiss();
@ -672,20 +434,13 @@ public class SpannableHelper {
@Override @Override
public void onClick(@NonNull View textView) { public void onClick(@NonNull View textView) {
String finalURl = url;
if (urlDetails.containsValue(url)) {
finalURl = Helper.getKeyByValue(urlDetails, url);
}
textView.setTag(CLICKABLE_SPAN); textView.setTag(CLICKABLE_SPAN);
Pattern link = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w._-]*[0-9]*)(/[0-9]+)?$"); Pattern link = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w._-]*[0-9]*)(/[0-9]+)?$");
Matcher matcherLink = null; Matcher matcherLink = link.matcher(content);
if (finalURl != null) { if (matcherLink.find() && !finalUrl.contains("medium.com")) {
matcherLink = link.matcher(finalURl);
}
if (finalURl != null && matcherLink.find() && !finalURl.contains("medium.com")) {
if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalURl, new CrossActionHelper.Callback() { CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalUrl, new CrossActionHelper.Callback() {
@Override @Override
public void federatedStatus(Status status) { public void federatedStatus(Status status) {
Intent intent = new Intent(context, ContextActivity.class); Intent intent = new Intent(context, ContextActivity.class);
@ -716,9 +471,8 @@ public class SpannableHelper {
}); });
} }
} else { } else {
Helper.openBrowser(context, finalURl); Helper.openBrowser(context, finalUrl);
} }
} }
@Override @Override
@ -729,46 +483,9 @@ public class SpannableHelper {
ds.setColor(linkColor); ds.setColor(linkColor);
} }
} }
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); }, start, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
} }
private static void gemini(Context context, Spannable content) {
// --- For all patterns defined in Helper class ---
Pattern pattern = Helper.geminiPattern;
Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
int matchStart = matcher.start();
int matchEnd = matcher.end();
String geminiLink = content.toString().substring(matchStart, matchEnd);
if (matchStart >= 0 && matchEnd <= content.toString().length() && matchEnd >= matchStart) {
ClickableSpan[] clickableSpans = content.getSpans(matchStart, matchEnd, ClickableSpan.class);
if (clickableSpans != null) {
for (ClickableSpan clickableSpan : clickableSpans) {
content.removeSpan(clickableSpan);
}
}
content.removeSpan(clickableSpans);
content.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull View textView) {
Helper.openBrowser(context, geminiLink);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
if (linkColor != -1) {
ds.setColor(linkColor);
}
}
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
private static void emails(Context context, Spannable content) { private static void emails(Context context, Spannable content) {
// --- For all patterns defined in Helper class --- // --- For all patterns defined in Helper class ---
@ -809,167 +526,6 @@ public class SpannableHelper {
} }
} }
private static void interaction(Context context, Spannable content, Status status, List<Mention> mentions, boolean forceMentions, HashMap<String, String> mentionsMap) {
// --- For all patterns defined in Helper class ---
for (Map.Entry<Helper.PatternType, Pattern> entry : Helper.patternHashMap.entrySet()) {
Helper.PatternType patternType = entry.getKey();
Pattern pattern = entry.getValue();
Matcher matcher = pattern.matcher(content);
if (pattern == Helper.mentionPattern && mentions == null && !forceMentions) {
continue;
} else if (pattern == Helper.mentionLongPattern && mentions == null && !forceMentions) {
continue;
}
while (matcher.find()) {
int matchStart = matcher.start();
int matchEnd = matcher.end();
String word = content.toString().substring(matchStart, matchEnd);
if (matchStart >= 0 && matchEnd <= content.toString().length() && matchEnd >= matchStart) {
URLSpan[] span = content.getSpans(matchStart, matchEnd, URLSpan.class);
content.removeSpan(span);
content.setSpan(new LongClickableSpan() {
@Override
public void onLongClick(View textView) {
textView.setTag(CLICKABLE_SPAN);
if (patternType == Helper.PatternType.TAG && BaseMainActivity.filterFetched && MainActivity.mainFilters != null) {
String tag = word.trim();
if (!tag.startsWith("#")) {
tag = "#" + tag;
}
Filter fedilabFilter = null;
for (Filter filter : MainActivity.mainFilters) {
if (filter.title.equals(Helper.FEDILAB_MUTED_HASHTAGS)) {
fedilabFilter = filter;
break;
}
}
//Filter for Fedilab doesn't exist we have to create it
if (fedilabFilter == null) {
Filter.FilterParams filterParams = new Filter.FilterParams();
filterParams.title = Helper.FEDILAB_MUTED_HASHTAGS;
filterParams.filter_action = "hide";
filterParams.context = new ArrayList<>();
filterParams.context.add("home");
filterParams.context.add("public");
filterParams.context.add("thread");
filterParams.context.add("account");
String finalTag = tag;
FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class);
filtersVM.addFilter(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, filterParams)
.observe((LifecycleOwner) context, filter -> {
if (filter != null) {
MainActivity.mainFilters.add(filter);
addTagToFilter(context, finalTag, status, filter);
}
});
} else {
addTagToFilter(context, tag, status, fedilabFilter);
}
}
}
@Override
public void onClick(@NonNull View textView) {
textView.setTag(CLICKABLE_SPAN);
switch (patternType) {
case TAG:
Intent intent = new Intent(context, HashTagActivity.class);
Bundle b = new Bundle();
b.putString(Helper.ARG_SEARCH_KEYWORD, word.trim());
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
break;
case GROUP:
break;
case MENTION:
intent = new Intent(context, ProfileActivity.class);
b = new Bundle();
Mention targetedMention = null;
String acct = null;
HashMap<String, Integer> countUsername = new HashMap<>();
//Mentions is retrieved with associated Mentions array
if (mentions != null) {
for (Mention mention : mentions) {
Integer count = countUsername.get(mention.username);
if (count == null) {
count = 0;
}
if (countUsername.containsKey(mention.username)) {
countUsername.put(mention.username, count + 1);
} else {
countUsername.put(mention.username, 1);
}
}
for (Mention mention : mentions) {
Integer count = countUsername.get(mention.username);
if (count == null) {
count = 0;
}
if (word.trim().compareToIgnoreCase("@" + mention.username) == 0 && count == 1) {
targetedMention = mention;
break;
}
}
} else if (mentionsMap.containsKey(word.trim())) {//Mentions will be find through its URL
URL url;
try {
url = new URL(mentionsMap.get(word.trim()));
acct = word.trim() + "@" + url.getHost();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
if (targetedMention != null) {
b.putString(Helper.ARG_USER_ID, targetedMention.id);
} else {
b.putString(Helper.ARG_MENTION, acct != null ? acct : word.trim());
}
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
break;
case MENTION_LONG:
intent = new Intent(context, ProfileActivity.class);
b = new Bundle();
targetedMention = null;
if (mentions != null) {
for (Mention mention : mentions) {
if (word.trim().substring(1).compareToIgnoreCase("@" + mention.acct) == 0) {
targetedMention = mention;
break;
}
}
}
if (targetedMention != null) {
b.putString(Helper.ARG_USER_ID, targetedMention.id);
} else {
b.putString(Helper.ARG_MENTION, word.trim());
}
intent.putExtras(b);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
break;
}
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
if (linkColor != -1) {
ds.setColor(linkColor);
}
}
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
}
public static void addTagToFilter(Context context, String tag, Status status, Filter filter) { public static void addTagToFilter(Context context, String tag, Status status, Filter filter) {
for (Filter.KeywordsAttributes keywords : filter.keywords) { for (Filter.KeywordsAttributes keywords : filter.keywords) {
if (keywords.keyword.equalsIgnoreCase(tag)) { if (keywords.keyword.equalsIgnoreCase(tag)) {
@ -1006,7 +562,6 @@ public class SpannableHelper {
* *
* @param status {@link Status} - Status concerned by the spannable transformation * @param status {@link Status} - Status concerned by the spannable transformation
* @param content String - text to convert, it can be content, spoiler, poll items, etc. * @param content String - text to convert, it can be content, spoiler, poll items, etc.
* @return Spannable string
*/ */
private static void convertOuich(@NonNull Status status, SpannableStringBuilder content) { private static void convertOuich(@NonNull Status status, SpannableStringBuilder content) {