From 5f26878c067dc5a676b73914bdac4e89b1cf5d55 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 21 Dec 2022 23:29:16 +0100 Subject: [PATCH 1/2] resolve fediverse links in app closes sk22#177 closes sk22#96 --- .../android/api/MastodonAPIRequest.java | 6 +++ .../android/ui/utils/UiUtils.java | 49 +++++++++++++++++-- mastodon/src/main/res/values/strings.xml | 1 + 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java index ad9a6254e..44a740401 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIRequest.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import androidx.annotation.CallSuper; import androidx.annotation.StringRes; @@ -101,9 +102,14 @@ public abstract class MastodonAPIRequest extends APIRequest{ } public MastodonAPIRequest wrapProgress(Activity activity, @StringRes int message, boolean cancelable){ + return wrapProgress(activity, message, cancelable, null); + } + + public MastodonAPIRequest wrapProgress(Activity activity, @StringRes int message, boolean cancelable, Consumer transform){ progressDialog=new ProgressDialog(activity); progressDialog.setMessage(activity.getString(message)); progressDialog.setCancelable(cancelable); + if (transform != null) transform.accept(progressDialog); if(cancelable){ progressDialog.setOnCancelListener(dialog->cancel()); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index 2672c749a..a5b150722 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -2,8 +2,10 @@ package org.joinmastodon.android.ui.utils; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.ProgressDialog; import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -41,6 +43,7 @@ import org.joinmastodon.android.api.requests.accounts.SetAccountBlocked; import org.joinmastodon.android.api.requests.accounts.SetAccountFollowed; import org.joinmastodon.android.api.requests.accounts.SetAccountMuted; import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked; +import org.joinmastodon.android.api.requests.search.GetSearchResults; import org.joinmastodon.android.api.requests.statuses.DeleteStatus; import org.joinmastodon.android.api.requests.statuses.GetStatusByID; import org.joinmastodon.android.api.session.AccountSessionManager; @@ -52,6 +55,7 @@ import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.Relationship; +import org.joinmastodon.android.model.SearchResults; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.M3AlertDialogBuilder; import org.joinmastodon.android.ui.text.CustomEmojiSpan; @@ -560,11 +564,19 @@ public class UiUtils{ } public static void openURL(Context context, String accountID, String url){ + Consumer transformDialogForLookup = dialog -> { + dialog.setTitle(R.string.loading_fediverse_resource_title); + dialog.setButton(DialogInterface.BUTTON_NEGATIVE, context.getString(R.string.cancel), (d, which) -> d.cancel()); + dialog.setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.open_in_browser), (d, which) -> { + d.cancel(); + launchWebBrowser(context, url); + }); + }; + Uri uri=Uri.parse(url); - if(accountID!=null && "https".equals(uri.getScheme()) && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){ - List path=uri.getPathSegments(); - // Match URLs like https://mastodon.social/@Gargron/108132679274083591 - if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$")){ + List path=uri.getPathSegments(); + if(accountID!=null && "https".equals(uri.getScheme())){ + if(path.size()==2 && path.get(0).matches("^@[a-zA-Z0-9_]+$") && path.get(1).matches("^[0-9]+$") && AccountSessionManager.getInstance().getAccount(accountID).domain.equalsIgnoreCase(uri.getAuthority())){ new GetStatusByID(path.get(1)) .setCallback(new Callback<>(){ @Override @@ -581,7 +593,34 @@ public class UiUtils{ launchWebBrowser(context, url); } }) - .wrapProgress((Activity)context, R.string.loading, true) + .wrapProgress((Activity)context, R.string.loading, true, transformDialogForLookup) + .exec(accountID); + return; + } else { + new GetSearchResults(url, null, true) + .setCallback(new Callback<>() { + @Override + public void onSuccess(SearchResults results) { + Bundle args=new Bundle(); + args.putString("account", accountID); + if (!results.statuses.isEmpty()) { + args.putParcelable("status", Parcels.wrap(results.statuses.get(0))); + Nav.go((Activity) context, ThreadFragment.class, args); + } else if (!results.accounts.isEmpty()) { + args.putParcelable("profileAccount", Parcels.wrap(results.accounts.get(0))); + Nav.go((Activity) context, ProfileFragment.class, args); + } else { + launchWebBrowser(context, url); + } + } + + @Override + public void onError(ErrorResponse error) { + error.showToast(context); + launchWebBrowser(context, url); + } + }) + .wrapProgress((Activity)context, R.string.loading, true, transformDialogForLookup) .exec(accountID); return; } diff --git a/mastodon/src/main/res/values/strings.xml b/mastodon/src/main/res/values/strings.xml index bde441f68..6a3a59c65 100644 --- a/mastodon/src/main/res/values/strings.xml +++ b/mastodon/src/main/res/values/strings.xml @@ -422,4 +422,5 @@ Not accepting new members Special Interests Passwords don\'t match + Looking it up on the Fediverse… \ No newline at end of file From 14658a2d70f7c728063b82fc64930dd88b8d7f42 Mon Sep 17 00:00:00 2001 From: sk Date: Wed, 21 Dec 2022 23:45:27 +0100 Subject: [PATCH 2/2] only perform fedi lookup if looks like fedi url --- .../android/ui/utils/UiUtils.java | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java index a5b150722..a0ec29192 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/utils/UiUtils.java @@ -64,6 +64,8 @@ import org.parceler.Parcels; import java.io.File; import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -563,6 +565,47 @@ public class UiUtils{ return GlobalUserPreferences.theme==GlobalUserPreferences.ThemePreference.DARK; } + // https://mastodon.foo.bar/@User + // https://mastodon.foo.bar/@User/43456787654678 + // https://pleroma.foo.bar/users/User + // https://pleroma.foo.bar/users/9qTHT2ANWUdXzENqC0 + // https://pleroma.foo.bar/notice/9sBHWIlwwGZi5QGlHc + // https://pleroma.foo.bar/objects/d4643c42-3ae0-4b73-b8b0-c725f5819207 + // https://friendica.foo.bar/profile/user + // https://friendica.foo.bar/display/d4643c42-3ae0-4b73-b8b0-c725f5819207 + // https://misskey.foo.bar/notes/83w6r388br (always lowercase) + // https://pixelfed.social/p/connyduck/391263492998670833 + // https://pixelfed.social/connyduck + // https://gts.foo.bar/@goblin/statuses/01GH9XANCJ0TA8Y95VE9H3Y0Q2 + // https://gts.foo.bar/@goblin + // https://foo.microblog.pub/o/5b64045effd24f48a27d7059f6cb38f5 + // + // COPIED FROM https://github.com/tuskyapp/Tusky/blob/develop/app/src/main/java/com/keylesspalace/tusky/util/LinkHelper.kt + public static boolean looksLikeMastodonUrl(String urlString) { + URI uri; + try { + uri = new URI(urlString); + } catch (URISyntaxException e) { + return false; + } + + if (uri.getQuery() != null || uri.getFragment() != null || uri.getPath() == null) return false; + + String it = uri.getPath(); + return it.matches("^/@[^/]+$") || + it.matches("^/@[^/]+/\\d+$") || + it.matches("^/users/\\w+$") || + it.matches("^/notice/[a-zA-Z0-9]+$") || + it.matches("^/objects/[-a-f0-9]+$") || + it.matches("^/notes/[a-z0-9]+$") || + it.matches("^/display/[-a-f0-9]+$") || + it.matches("^/profile/\\w+$") || + it.matches("^/p/\\w+/\\d+$") || + it.matches("^/\\w+$") || + it.matches("^/@[^/]+/statuses/[a-zA-Z0-9]+$") || + it.matches("^/o/[a-f0-9]+$"); + } + public static void openURL(Context context, String accountID, String url){ Consumer transformDialogForLookup = dialog -> { dialog.setTitle(R.string.loading_fediverse_resource_title); @@ -596,7 +639,7 @@ public class UiUtils{ .wrapProgress((Activity)context, R.string.loading, true, transformDialogForLookup) .exec(accountID); return; - } else { + } else if (looksLikeMastodonUrl(url)) { new GetSearchResults(url, null, true) .setCallback(new Callback<>() { @Override