2022-04-27 15:20:42 +02:00
|
|
|
package app.fedilab.android.helper;
|
|
|
|
/* Copyright 2021 Thomas Schneider
|
|
|
|
*
|
|
|
|
* This file is a part of Fedilab
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* Fedilab 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 Fedilab; if not,
|
|
|
|
* see <http://www.gnu.org/licenses>. */
|
|
|
|
|
|
|
|
|
2022-06-21 17:09:34 +02:00
|
|
|
import static app.fedilab.android.BaseMainActivity.currentAccount;
|
2022-05-14 19:24:58 +02:00
|
|
|
import static app.fedilab.android.helper.Helper.USER_AGENT;
|
|
|
|
import static app.fedilab.android.helper.Helper.urlPattern;
|
2022-04-27 15:20:42 +02:00
|
|
|
import static app.fedilab.android.helper.ThemeHelper.linkColor;
|
|
|
|
|
2022-05-14 19:24:58 +02:00
|
|
|
import android.content.ClipData;
|
|
|
|
import android.content.ClipboardManager;
|
2022-04-27 15:20:42 +02:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
2022-07-18 11:43:23 +02:00
|
|
|
import android.content.SharedPreferences;
|
2022-05-14 19:24:58 +02:00
|
|
|
import android.net.Uri;
|
2022-04-27 15:20:42 +02:00
|
|
|
import android.os.Build;
|
|
|
|
import android.os.Bundle;
|
2022-05-14 19:24:58 +02:00
|
|
|
import android.os.Handler;
|
2022-04-27 15:20:42 +02:00
|
|
|
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.style.ClickableSpan;
|
|
|
|
import android.text.style.URLSpan;
|
2022-11-04 19:02:48 +01:00
|
|
|
import android.util.Patterns;
|
2022-05-14 19:24:58 +02:00
|
|
|
import android.view.LayoutInflater;
|
2022-04-27 15:20:42 +02:00
|
|
|
import android.view.View;
|
2022-05-14 19:24:58 +02:00
|
|
|
import android.widget.Toast;
|
2022-04-27 15:20:42 +02:00
|
|
|
|
|
|
|
import androidx.annotation.NonNull;
|
2022-05-14 19:24:58 +02:00
|
|
|
import androidx.appcompat.app.AlertDialog;
|
2022-07-18 11:43:23 +02:00
|
|
|
import androidx.preference.PreferenceManager;
|
|
|
|
|
|
|
|
import com.bumptech.glide.Glide;
|
2022-04-27 15:20:42 +02:00
|
|
|
|
2022-05-14 19:24:58 +02:00
|
|
|
import java.io.IOException;
|
2022-07-18 11:43:23 +02:00
|
|
|
import java.lang.ref.WeakReference;
|
2022-05-14 19:24:58 +02:00
|
|
|
import java.net.MalformedURLException;
|
|
|
|
import java.net.URL;
|
2022-04-27 15:20:42 +02:00
|
|
|
import java.util.ArrayList;
|
2022-05-15 10:38:52 +02:00
|
|
|
import java.util.HashMap;
|
2022-07-20 17:13:07 +02:00
|
|
|
import java.util.LinkedHashMap;
|
2022-04-27 15:20:42 +02:00
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2022-05-14 19:24:58 +02:00
|
|
|
import java.util.Objects;
|
2022-04-27 15:20:42 +02:00
|
|
|
import java.util.regex.Matcher;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
2022-05-14 19:24:58 +02:00
|
|
|
import javax.net.ssl.HttpsURLConnection;
|
|
|
|
|
2022-04-27 15:20:42 +02:00
|
|
|
import app.fedilab.android.R;
|
2022-05-14 19:24:58 +02:00
|
|
|
import app.fedilab.android.activities.ContextActivity;
|
2022-04-27 15:20:42 +02:00
|
|
|
import app.fedilab.android.activities.HashTagActivity;
|
|
|
|
import app.fedilab.android.activities.ProfileActivity;
|
2022-05-24 10:12:04 +02:00
|
|
|
import app.fedilab.android.client.entities.api.Account;
|
2022-07-18 14:39:47 +02:00
|
|
|
import app.fedilab.android.client.entities.api.Announcement;
|
2022-05-24 10:12:04 +02:00
|
|
|
import app.fedilab.android.client.entities.api.Attachment;
|
2022-07-18 11:43:23 +02:00
|
|
|
import app.fedilab.android.client.entities.api.Emoji;
|
2022-05-24 10:12:04 +02:00
|
|
|
import app.fedilab.android.client.entities.api.Mention;
|
|
|
|
import app.fedilab.android.client.entities.api.Status;
|
2022-05-14 19:24:58 +02:00
|
|
|
import app.fedilab.android.databinding.PopupLinksBinding;
|
|
|
|
import es.dmoral.toasty.Toasty;
|
2022-04-27 15:20:42 +02:00
|
|
|
|
|
|
|
public class SpannableHelper {
|
|
|
|
|
|
|
|
public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN";
|
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
public static Spannable convert(Context context, String text,
|
2022-07-18 14:39:47 +02:00
|
|
|
Status status, Account account, Announcement announcement,
|
2022-07-18 11:43:23 +02:00
|
|
|
boolean convertHtml,
|
2022-07-18 14:13:12 +02:00
|
|
|
WeakReference<View> viewWeakReference) {
|
2022-07-02 11:09:35 +02:00
|
|
|
|
2022-07-18 17:46:55 +02:00
|
|
|
|
2022-04-27 15:20:42 +02:00
|
|
|
SpannableString initialContent;
|
2022-05-23 14:46:46 +02:00
|
|
|
if (text == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2022-07-20 17:13:07 +02:00
|
|
|
Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>");
|
|
|
|
Matcher matcherImg = imgPattern.matcher(text);
|
|
|
|
HashMap<String, String> imagesToReplace = new LinkedHashMap<>();
|
|
|
|
int inc = 0;
|
|
|
|
while (matcherImg.find()) {
|
|
|
|
String replacement = "[FEDI_IMG_" + inc + "]";
|
|
|
|
imagesToReplace.put(replacement, matcherImg.group(1));
|
|
|
|
inc++;
|
|
|
|
text = text.replaceAll(Pattern.quote(matcherImg.group()), replacement);
|
|
|
|
}
|
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
SpannableStringBuilder content;
|
|
|
|
View view = viewWeakReference.get();
|
|
|
|
List<Mention> mentionList = null;
|
|
|
|
List<Emoji> emojiList = null;
|
|
|
|
if (status != null) {
|
|
|
|
mentionList = status.mentions;
|
|
|
|
emojiList = status.emojis;
|
|
|
|
} else if (account != null) {
|
|
|
|
emojiList = account.emojis;
|
2022-07-18 17:46:55 +02:00
|
|
|
} else if (announcement != null) {
|
|
|
|
emojiList = announcement.emojis;
|
2022-07-13 18:55:24 +02:00
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
HashMap<String, String> urlDetails = new HashMap<>();
|
|
|
|
if (convertHtml) {
|
|
|
|
Matcher matcherALink = Helper.aLink.matcher(text);
|
2022-11-04 16:48:16 +01:00
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
//We stock details
|
|
|
|
while (matcherALink.find()) {
|
|
|
|
String urlText = matcherALink.group(3);
|
|
|
|
String url = matcherALink.group(2);
|
2022-09-08 12:16:50 +02:00
|
|
|
if (urlText != null && urlText.startsWith(">")) {
|
2022-07-18 11:43:23 +02:00
|
|
|
urlText = urlText.substring(1);
|
|
|
|
}
|
|
|
|
if (url != null && urlText != null && !url.equals(urlText) && !urlText.contains("<span")) {
|
|
|
|
urlDetails.put(url, urlText);
|
2022-06-07 17:47:44 +02:00
|
|
|
}
|
2022-04-27 15:20:42 +02:00
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
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);
|
2022-06-16 18:30:16 +02:00
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
//Make tags, mentions, groups
|
|
|
|
interaction(context, content, mentionList);
|
|
|
|
//Make all links
|
|
|
|
linkify(context, content, urlDetails);
|
2022-11-04 16:48:16 +01:00
|
|
|
linkifyURL(context, content, urlDetails);
|
2022-11-04 19:02:48 +01:00
|
|
|
emails(context, content);
|
2022-07-18 11:43:23 +02:00
|
|
|
} else {
|
|
|
|
content = new SpannableStringBuilder(text);
|
2022-06-16 18:30:16 +02:00
|
|
|
}
|
2022-07-21 09:01:17 +02:00
|
|
|
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
|
|
|
boolean animate = !sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
|
2022-07-18 11:43:23 +02:00
|
|
|
if (emojiList != null && emojiList.size() > 0) {
|
|
|
|
for (Emoji emoji : emojiList) {
|
2022-07-18 14:39:47 +02:00
|
|
|
Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
|
|
|
|
.matcher(content);
|
|
|
|
while (matcher.find()) {
|
|
|
|
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
|
|
|
|
content.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
|
|
|
|
Glide.with(view)
|
|
|
|
.asDrawable()
|
|
|
|
.load(animate ? emoji.url : emoji.static_url)
|
|
|
|
.into(customEmoji.getTarget(animate));
|
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
}
|
2022-07-20 17:13:07 +02:00
|
|
|
|
|
|
|
if (imagesToReplace.size() > 0) {
|
|
|
|
for (Map.Entry<String, String> entry : imagesToReplace.entrySet()) {
|
|
|
|
String key = entry.getKey();
|
|
|
|
String url = entry.getValue();
|
|
|
|
Matcher matcher = Pattern.compile(key, Pattern.LITERAL)
|
|
|
|
.matcher(content);
|
|
|
|
while (matcher.find()) {
|
|
|
|
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
|
|
|
|
content.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
|
|
|
|
Glide.with(view)
|
|
|
|
.asDrawable()
|
|
|
|
.load(url)
|
2022-07-21 09:01:17 +02:00
|
|
|
.into(customEmoji.getTarget(animate));
|
2022-07-20 17:13:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
return trimSpannable(new SpannableStringBuilder(content));
|
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
private static void linkify(Context context, SpannableStringBuilder content, HashMap<String, String> urlDetails) {
|
2022-06-16 18:30:16 +02:00
|
|
|
//--- URLs ----
|
2022-09-09 15:19:37 +02:00
|
|
|
Matcher matcherLink = urlPattern.matcher(content);
|
2022-09-08 12:16:50 +02:00
|
|
|
|
2022-06-16 18:30:16 +02:00
|
|
|
int offSetTruncate = 0;
|
2022-11-04 16:48:16 +01:00
|
|
|
|
|
|
|
|
2022-06-16 18:30:16 +02:00
|
|
|
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;
|
|
|
|
}
|
2022-09-08 12:16:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
final String url = content.toString().substring(matchStart, matchEnd);
|
2022-06-16 18:30:16 +02:00
|
|
|
String newURL = Helper.transformURL(context, url);
|
|
|
|
//If URL has been transformed
|
|
|
|
if (newURL.compareTo(url) != 0) {
|
|
|
|
content.replace(matchStart, matchEnd, newURL);
|
2022-10-16 16:35:32 +02:00
|
|
|
offSetTruncate -= (newURL.length() - url.length());
|
2022-06-16 18:30:16 +02:00
|
|
|
matchEnd = matchStart + newURL.length();
|
|
|
|
//The transformed URL was in the list of URLs having a different names
|
|
|
|
if (urlDetails.containsKey(url)) {
|
|
|
|
urlDetails.put(newURL, urlDetails.get(url));
|
|
|
|
}
|
|
|
|
}
|
2022-07-18 17:46:55 +02:00
|
|
|
|
2022-06-16 18:30:16 +02:00
|
|
|
//Truncate URL if needed
|
|
|
|
//TODO: add an option to disable truncated URLs
|
|
|
|
String urlText = newURL;
|
|
|
|
if (newURL.length() > 30 && !urlDetails.containsKey(urlText)) {
|
|
|
|
urlText = urlText.substring(0, 30);
|
|
|
|
urlText += "…";
|
|
|
|
content.replace(matchStart, matchEnd, urlText);
|
|
|
|
matchEnd = matchStart + 31;
|
|
|
|
offSetTruncate += (newURL.length() - urlText.length());
|
2022-09-08 12:16:50 +02:00
|
|
|
} /*else if (urlDetails.containsKey(urlText) && urlDetails.get(urlText) != null) {
|
2022-06-16 18:30:16 +02:00
|
|
|
urlText = urlDetails.get(urlText);
|
|
|
|
if (urlText != null) {
|
|
|
|
content.replace(matchStart, matchEnd, urlText);
|
|
|
|
matchEnd = matchStart + urlText.length();
|
|
|
|
offSetTruncate += (newURL.length() - urlText.length());
|
|
|
|
}
|
2022-09-08 12:16:50 +02:00
|
|
|
}*/
|
2022-06-16 18:30:16 +02:00
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
|
2022-06-16 18:30:16 +02:00
|
|
|
if (matchEnd <= content.length() && matchEnd >= matchStart) {
|
|
|
|
content.setSpan(new LongClickableSpan() {
|
|
|
|
@Override
|
|
|
|
public void onLongClick(View view) {
|
2022-07-05 07:44:43 +02:00
|
|
|
Context mContext = view.getContext();
|
|
|
|
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext, Helper.dialogStyle());
|
2022-06-16 18:30:16 +02:00
|
|
|
PopupLinksBinding popupLinksBinding = PopupLinksBinding.inflate(LayoutInflater.from(context));
|
|
|
|
dialogBuilder.setView(popupLinksBinding.getRoot());
|
|
|
|
AlertDialog alertDialog = dialogBuilder.create();
|
|
|
|
alertDialog.show();
|
2022-09-08 12:16:50 +02:00
|
|
|
String finalURl = url;
|
2022-09-09 15:19:37 +02:00
|
|
|
String uniqueUrl = url.endsWith("…") ? url : url + "…";
|
|
|
|
if (urlDetails.containsValue(uniqueUrl)) {
|
|
|
|
finalURl = Helper.getKeyByValue(urlDetails, uniqueUrl);
|
2022-09-08 12:16:50 +02:00
|
|
|
}
|
|
|
|
String finalURl1 = finalURl;
|
2022-06-16 18:30:16 +02:00
|
|
|
popupLinksBinding.displayFullLink.setOnClickListener(v -> {
|
2022-07-05 07:44:43 +02:00
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(mContext, Helper.dialogStyle());
|
2022-09-08 12:16:50 +02:00
|
|
|
builder.setMessage(finalURl1);
|
2022-06-16 18:30:16 +02:00
|
|
|
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));
|
2022-09-08 12:16:50 +02:00
|
|
|
sendIntent.putExtra(Intent.EXTRA_TEXT, finalURl1);
|
2022-06-16 18:30:16 +02:00
|
|
|
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);
|
2022-09-08 12:16:50 +02:00
|
|
|
intent.setData(Uri.parse(finalURl1));
|
2022-06-16 18:30:16 +02:00
|
|
|
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);
|
2022-09-08 12:16:50 +02:00
|
|
|
ClipData clip = ClipData.newPlainText(Helper.CLIP_BOARD, finalURl1);
|
2022-06-16 18:30:16 +02:00
|
|
|
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 {
|
|
|
|
|
2022-09-08 12:16:50 +02:00
|
|
|
URL finalUrlCheck = new URL(finalURl1);
|
2022-06-16 18:30:16 +02:00
|
|
|
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");
|
2022-07-22 11:05:24 +02:00
|
|
|
httpsURLConnection.setInstanceFollowRedirects(false);
|
2022-06-16 18:30:16 +02:00
|
|
|
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 = urlPattern.matcher(entry.toString());
|
|
|
|
if (matcher.find()) {
|
|
|
|
redirect = matcher.group(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
httpsURLConnection.getInputStream().close();
|
2022-09-09 17:24:57 +02:00
|
|
|
if (redirect != null && finalURl1 != null && redirect.compareTo(finalURl1) != 0) {
|
2022-06-16 18:30:16 +02:00
|
|
|
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(), Helper.dialogStyle());
|
|
|
|
if (finalRedirect != null) {
|
2022-09-08 12:16:50 +02:00
|
|
|
builder1.setMessage(context.getString(R.string.redirect_detected, finalURl1, finalRedirect));
|
2022-06-16 18:30:16 +02:00
|
|
|
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));
|
2022-09-08 12:16:50 +02:00
|
|
|
sendIntent1.putExtra(Intent.EXTRA_TEXT, finalURl1);
|
2022-06-16 18:30:16 +02:00
|
|
|
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) {
|
2022-09-08 12:16:50 +02:00
|
|
|
String finalURl = newURL;
|
|
|
|
String finalURl2 = url;
|
2022-09-09 15:19:37 +02:00
|
|
|
String uniqueNewURL = newURL.endsWith("…") ? newURL : newURL + "…";
|
|
|
|
if (urlDetails.containsValue(uniqueNewURL)) {
|
|
|
|
finalURl = Helper.getKeyByValue(urlDetails, uniqueNewURL);
|
2022-09-08 12:16:50 +02:00
|
|
|
}
|
2022-09-09 15:19:37 +02:00
|
|
|
String uniqueUrl = url.endsWith("…") ? url : url + "…";
|
|
|
|
if (urlDetails.containsValue(uniqueUrl)) {
|
|
|
|
finalURl2 = Helper.getKeyByValue(urlDetails, uniqueUrl);
|
2022-09-08 12:16:50 +02:00
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
textView.setTag(CLICKABLE_SPAN);
|
|
|
|
Pattern link = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w._-]*[0-9]*)(/[0-9]+)?$");
|
2022-09-09 17:24:57 +02:00
|
|
|
Matcher matcherLink = null;
|
|
|
|
if (finalURl2 != null) {
|
|
|
|
matcherLink = link.matcher(finalURl2);
|
|
|
|
}
|
|
|
|
if (finalURl2 != null && matcherLink.find() && !finalURl2.contains("medium.com")) {
|
2022-06-16 18:30:16 +02:00
|
|
|
if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot
|
2022-09-08 12:16:50 +02:00
|
|
|
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalURl2, new CrossActionHelper.Callback() {
|
2022-06-16 18:30:16 +02:00
|
|
|
@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) {
|
2022-07-18 11:43:23 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {//It's an account
|
|
|
|
CrossActionHelper.fetchRemoteAccount(context, currentAccount, matcherLink.group(2) + "@" + matcherLink.group(1), new CrossActionHelper.Callback() {
|
|
|
|
@Override
|
|
|
|
public void federatedStatus(Status status) {
|
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
@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);
|
2022-06-16 18:30:16 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
2022-09-08 12:16:50 +02:00
|
|
|
Helper.openBrowser(context, finalURl);
|
2022-06-16 18:30:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void updateDrawState(@NonNull TextPaint ds) {
|
|
|
|
super.updateDrawState(ds);
|
|
|
|
ds.setUnderlineText(false);
|
|
|
|
ds.setColor(linkColor);
|
|
|
|
}
|
|
|
|
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
|
|
|
}
|
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
|
2022-11-04 16:48:16 +01:00
|
|
|
private static void linkifyURL(Context context, SpannableStringBuilder content, HashMap<String, String> urlDetails) {
|
|
|
|
|
|
|
|
for (Map.Entry<String, String> entry : urlDetails.entrySet()) {
|
|
|
|
String value = entry.getValue();
|
2022-11-04 19:02:48 +01:00
|
|
|
if (value.startsWith("@") || value.startsWith("#")) {
|
|
|
|
continue;
|
|
|
|
}
|
2022-11-04 16:48:16 +01:00
|
|
|
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));
|
|
|
|
|
|
|
|
Pattern word = Pattern.compile(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;
|
|
|
|
}
|
|
|
|
if (matchEnd <= content.length()) {
|
|
|
|
content.setSpan(new LongClickableSpan() {
|
|
|
|
@Override
|
|
|
|
public void onLongClick(View view) {
|
|
|
|
Context mContext = view.getContext();
|
|
|
|
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext, Helper.dialogStyle());
|
|
|
|
PopupLinksBinding popupLinksBinding = PopupLinksBinding.inflate(LayoutInflater.from(context));
|
|
|
|
dialogBuilder.setView(popupLinksBinding.getRoot());
|
|
|
|
AlertDialog alertDialog = dialogBuilder.create();
|
|
|
|
alertDialog.show();
|
|
|
|
String finalURl = url;
|
|
|
|
if (urlDetails.containsValue(url)) {
|
|
|
|
finalURl = Helper.getKeyByValue(urlDetails, url);
|
|
|
|
}
|
|
|
|
String finalURl1 = finalURl;
|
|
|
|
popupLinksBinding.displayFullLink.setOnClickListener(v -> {
|
|
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(mContext, Helper.dialogStyle());
|
|
|
|
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 = urlPattern.matcher(entry.toString());
|
|
|
|
if (matcher.find()) {
|
|
|
|
redirect = matcher.group(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
httpsURLConnection.getInputStream().close();
|
|
|
|
if (redirect != null && finalURl1 != 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(), Helper.dialogStyle());
|
|
|
|
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 = url;
|
|
|
|
if (urlDetails.containsValue(url)) {
|
|
|
|
finalURl = Helper.getKeyByValue(urlDetails, url);
|
|
|
|
}
|
|
|
|
|
|
|
|
textView.setTag(CLICKABLE_SPAN);
|
|
|
|
Pattern link = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w._-]*[0-9]*)(/[0-9]+)?$");
|
|
|
|
Matcher matcherLink = null;
|
|
|
|
if (finalURl != null) {
|
|
|
|
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
|
|
|
|
CrossActionHelper.fetchRemoteStatus(context, currentAccount, finalURl, 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);
|
|
|
|
ds.setUnderlineText(false);
|
|
|
|
ds.setColor(linkColor);
|
|
|
|
}
|
|
|
|
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-11-04 19:02:48 +01:00
|
|
|
private static void emails(Context context, Spannable content) {
|
|
|
|
// --- For all patterns defined in Helper class ---
|
|
|
|
Pattern pattern = Patterns.EMAIL_ADDRESS;
|
|
|
|
Matcher matcher = pattern.matcher(content);
|
|
|
|
|
|
|
|
while (matcher.find()) {
|
|
|
|
int matchStart = matcher.start();
|
|
|
|
int matchEnd = matcher.end();
|
|
|
|
String email = content.toString().substring(matchStart, matchEnd);
|
|
|
|
if (matchStart >= 0 && matchEnd <= content.toString().length() && matchEnd >= matchStart) {
|
|
|
|
content.setSpan(new ClickableSpan() {
|
|
|
|
@Override
|
|
|
|
public void onClick(@NonNull View textView) {
|
|
|
|
Intent intent = new Intent(Intent.ACTION_SEND);
|
|
|
|
intent.setType("plain/text");
|
|
|
|
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{email});
|
|
|
|
context.startActivity(Intent.createChooser(intent, null));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void updateDrawState(@NonNull TextPaint ds) {
|
|
|
|
super.updateDrawState(ds);
|
|
|
|
ds.setUnderlineText(false);
|
|
|
|
ds.setColor(linkColor);
|
|
|
|
}
|
|
|
|
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
private static void interaction(Context context, Spannable content, List<Mention> mentions) {
|
2022-06-16 18:30:16 +02:00
|
|
|
// --- 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);
|
2022-07-18 11:43:23 +02:00
|
|
|
if (pattern == Helper.mentionPattern && mentions == null) {
|
|
|
|
continue;
|
|
|
|
} else if (pattern == Helper.mentionLongPattern && mentions == null) {
|
|
|
|
continue;
|
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
while (matcher.find()) {
|
2022-06-16 18:30:16 +02:00
|
|
|
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 ClickableSpan() {
|
|
|
|
@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;
|
|
|
|
HashMap<String, Integer> countUsername = new HashMap<>();
|
2022-07-18 11:43:23 +02:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
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;
|
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (targetedMention != null) {
|
|
|
|
b.putString(Helper.ARG_USER_ID, targetedMention.id);
|
|
|
|
} else {
|
|
|
|
b.putString(Helper.ARG_MENTION, word.trim());
|
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
|
2022-06-16 18:30:16 +02:00
|
|
|
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;
|
2022-07-18 11:43:23 +02:00
|
|
|
if (mentions != null) {
|
|
|
|
for (Mention mention : mentions) {
|
|
|
|
if (word.trim().substring(1).compareToIgnoreCase("@" + mention.acct) == 0) {
|
|
|
|
targetedMention = mention;
|
|
|
|
break;
|
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
ds.setColor(linkColor);
|
|
|
|
}
|
|
|
|
}, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-27 15:20:42 +02:00
|
|
|
/**
|
2022-07-18 11:43:23 +02:00
|
|
|
* Convert HTML content to text. Also, it handles click on link
|
|
|
|
* This needs to be run asynchronously
|
2022-04-27 15:20:42 +02:00
|
|
|
*
|
2022-07-18 11:43:23 +02:00
|
|
|
* @param status {@link Status} - Status concerned by the spannable transformation
|
|
|
|
* @param content String - text to convert, it can be content, spoiler, poll items, etc.
|
|
|
|
* @return Spannable string
|
2022-04-27 15:20:42 +02:00
|
|
|
*/
|
2022-07-18 11:43:23 +02:00
|
|
|
private static void convertOuich(@NonNull Status status, SpannableStringBuilder content) {
|
2022-04-27 15:20:42 +02:00
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
Matcher matcher = Helper.ouichesPattern.matcher(content);
|
|
|
|
while (matcher.find()) {
|
|
|
|
Attachment attachment = new Attachment();
|
|
|
|
attachment.type = "audio";
|
|
|
|
String tag = matcher.group(1);
|
|
|
|
attachment.id = tag;
|
|
|
|
if (tag == null) {
|
|
|
|
continue;
|
2022-04-27 15:20:42 +02:00
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
attachment.remote_url = "http://ouich.es/mp3/" + tag + ".mp3";
|
|
|
|
attachment.url = "http://ouich.es/mp3/" + tag + ".mp3";
|
|
|
|
if (status.media_attachments == null) {
|
|
|
|
status.media_attachments = new ArrayList<>();
|
|
|
|
}
|
|
|
|
boolean alreadyAdded = false;
|
|
|
|
for (Attachment at : status.media_attachments) {
|
|
|
|
if (tag.compareTo(at.id) == 0) {
|
|
|
|
alreadyAdded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!alreadyAdded) {
|
|
|
|
status.media_attachments.add(attachment);
|
2022-06-29 16:33:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-07-11 16:40:50 +02:00
|
|
|
* Convert HTML content to text. Also, it handles click on link
|
2022-06-29 16:33:11 +02:00
|
|
|
* This needs to be run asynchronously
|
|
|
|
*
|
2022-07-13 12:50:48 +02:00
|
|
|
* @param text String - text to convert, it can be content, spoiler, poll items, etc.
|
2022-06-29 16:33:11 +02:00
|
|
|
* @return Spannable string
|
|
|
|
*/
|
2022-07-18 11:43:23 +02:00
|
|
|
public static Spannable convertNitter(String text) {
|
2022-06-29 16:33:11 +02:00
|
|
|
SpannableString initialContent;
|
|
|
|
if (text == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
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));
|
|
|
|
return initialContent;
|
|
|
|
}
|
2022-06-16 18:30:16 +02:00
|
|
|
|
2022-04-27 15:20:42 +02:00
|
|
|
|
|
|
|
/**
|
2022-07-18 11:43:23 +02:00
|
|
|
* Remove extra carriage returns at the bottom due to <p> tags in toots
|
2022-04-27 15:20:42 +02:00
|
|
|
*
|
2022-07-18 11:43:23 +02:00
|
|
|
* @param spannable SpannableStringBuilder
|
|
|
|
* @return SpannableStringBuilder
|
2022-04-27 15:20:42 +02:00
|
|
|
*/
|
2022-07-18 11:43:23 +02:00
|
|
|
private static SpannableStringBuilder trimSpannable(SpannableStringBuilder spannable) {
|
2022-04-27 15:20:42 +02:00
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
int trimStart = 0;
|
|
|
|
int trimEnd = 0;
|
|
|
|
String text = spannable.toString();
|
2022-04-27 15:20:42 +02:00
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
while (text.length() > 0 && text.startsWith("\n")) {
|
|
|
|
text = text.substring(1);
|
|
|
|
trimStart += 1;
|
2022-04-27 15:20:42 +02:00
|
|
|
}
|
|
|
|
|
2022-07-18 11:43:23 +02:00
|
|
|
while (text.length() > 0 && text.endsWith("\n")) {
|
|
|
|
text = text.substring(0, text.length() - 1);
|
|
|
|
trimEnd += 1;
|
2022-04-27 15:20:42 +02:00
|
|
|
}
|
2022-07-18 11:43:23 +02:00
|
|
|
return spannable.delete(0, trimStart).delete(spannable.length() - trimEnd, spannable.length());
|
2022-04-27 15:20:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes the move to account clickable
|
|
|
|
*
|
|
|
|
* @param context Context
|
|
|
|
* @return SpannableString
|
|
|
|
*/
|
|
|
|
public static SpannableString moveToText(final Context context, Account account) {
|
|
|
|
SpannableString spannableString = null;
|
|
|
|
if (account.moved != null) {
|
|
|
|
spannableString = new SpannableString(context.getString(R.string.account_moved_to, account.acct, "@" + account.moved.acct));
|
|
|
|
int startPosition = spannableString.toString().indexOf("@" + account.moved.acct);
|
|
|
|
int endPosition = startPosition + ("@" + account.moved.acct).length();
|
|
|
|
if (startPosition >= 0 && endPosition <= spannableString.toString().length() && endPosition >= startPosition)
|
|
|
|
spannableString.setSpan(new ClickableSpan() {
|
|
|
|
@Override
|
|
|
|
public void onClick(@NonNull View textView) {
|
|
|
|
Intent intent = new Intent(context, ProfileActivity.class);
|
|
|
|
Bundle b = new Bundle();
|
|
|
|
b.putSerializable(Helper.ARG_ACCOUNT, account.moved);
|
|
|
|
intent.putExtras(b);
|
|
|
|
context.startActivity(intent);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void updateDrawState(@NonNull TextPaint ds) {
|
|
|
|
super.updateDrawState(ds);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
startPosition, endPosition,
|
|
|
|
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
|
|
|
}
|
|
|
|
return spannableString;
|
|
|
|
}
|
|
|
|
}
|