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 5e45aaa15..8b6e9ae80 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 @@ -5,10 +5,12 @@ import static org.joinmastodon.android.GlobalUserPreferences.trueBlackTheme; import android.annotation.SuppressLint; import android.app.Activity; +import android.app.ProgressDialog; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -50,6 +52,7 @@ import org.joinmastodon.android.api.requests.accounts.SetDomainBlocked; import org.joinmastodon.android.api.requests.accounts.AuthorizeFollowRequest; import org.joinmastodon.android.api.requests.accounts.RejectFollowRequest; import org.joinmastodon.android.api.requests.notifications.DismissNotification; +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.requests.statuses.SetStatusPinned; @@ -69,6 +72,7 @@ import org.joinmastodon.android.model.Emoji; import org.joinmastodon.android.model.ListTimeline; import org.joinmastodon.android.model.Notification; 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; @@ -77,6 +81,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; @@ -697,12 +703,61 @@ public class UiUtils{ return theme==GlobalUserPreferences.ThemePreference.DARK; } - public static void openURL(Context context, @Nullable String accountID, String url){ + // 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.sk_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 @@ -719,7 +774,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 if (looksLikeMastodonUrl(url)) { + 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_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index e044f8fd7..0efb894bb 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -82,4 +82,5 @@ Hide translate button in timeline %s supports translation! %s does not appear to support translation. + Looking it up on the Fediverse… \ No newline at end of file