mirror of
				https://codeberg.org/tom79/Fedilab.git
				synced 2025-10-20 11:20:16 +03:00 
			
		
		
		
	Long press links
This commit is contained in:
		
							parent
							
								
									a8afc99401
								
							
						
					
					
						commit
						27ef0003d7
					
				
					 7 changed files with 475 additions and 5 deletions
				
			
		|  | @ -27,13 +27,18 @@ import androidx.lifecycle.ViewModelProvider; | ||||||
| import androidx.lifecycle.ViewModelStoreOwner; | import androidx.lifecycle.ViewModelStoreOwner; | ||||||
| import androidx.preference.PreferenceManager; | import androidx.preference.PreferenceManager; | ||||||
| 
 | 
 | ||||||
|  | import java.io.IOException; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
| 
 | 
 | ||||||
| import app.fedilab.android.BaseMainActivity; | import app.fedilab.android.BaseMainActivity; | ||||||
| import app.fedilab.android.R; | import app.fedilab.android.R; | ||||||
| import app.fedilab.android.activities.ComposeActivity; | import app.fedilab.android.activities.ComposeActivity; | ||||||
|  | import app.fedilab.android.activities.MainActivity; | ||||||
| import app.fedilab.android.client.entities.Account; | import app.fedilab.android.client.entities.Account; | ||||||
|  | import app.fedilab.android.client.mastodon.MastodonSearchService; | ||||||
|  | import app.fedilab.android.client.mastodon.entities.Results; | ||||||
| import app.fedilab.android.client.mastodon.entities.Status; | import app.fedilab.android.client.mastodon.entities.Status; | ||||||
| import app.fedilab.android.exception.DBException; | import app.fedilab.android.exception.DBException; | ||||||
| import app.fedilab.android.ui.drawer.AccountsSearchAdapter; | import app.fedilab.android.ui.drawer.AccountsSearchAdapter; | ||||||
|  | @ -41,6 +46,11 @@ import app.fedilab.android.viewmodel.mastodon.AccountsVM; | ||||||
| import app.fedilab.android.viewmodel.mastodon.SearchVM; | import app.fedilab.android.viewmodel.mastodon.SearchVM; | ||||||
| import app.fedilab.android.viewmodel.mastodon.StatusesVM; | import app.fedilab.android.viewmodel.mastodon.StatusesVM; | ||||||
| import es.dmoral.toasty.Toasty; | import es.dmoral.toasty.Toasty; | ||||||
|  | import okhttp3.OkHttpClient; | ||||||
|  | import retrofit2.Call; | ||||||
|  | import retrofit2.Response; | ||||||
|  | import retrofit2.Retrofit; | ||||||
|  | import retrofit2.converter.gson.GsonConverterFactory; | ||||||
| 
 | 
 | ||||||
| public class CrossActionHelper { | public class CrossActionHelper { | ||||||
| 
 | 
 | ||||||
|  | @ -235,6 +245,122 @@ public class CrossActionHelper { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     private static MastodonSearchService init(Context context, @NonNull String instance) { | ||||||
|  |         final OkHttpClient okHttpClient = new OkHttpClient.Builder() | ||||||
|  |                 .readTimeout(60, TimeUnit.SECONDS) | ||||||
|  |                 .connectTimeout(60, TimeUnit.SECONDS) | ||||||
|  |                 .proxy(Helper.getProxy(context)) | ||||||
|  |                 .build(); | ||||||
|  |         Retrofit retrofit = new Retrofit.Builder() | ||||||
|  |                 .baseUrl("https://" + instance + "/api/v2/") | ||||||
|  |                 .addConverterFactory(GsonConverterFactory.create()) | ||||||
|  |                 .client(okHttpClient) | ||||||
|  |                 .build(); | ||||||
|  |         return retrofit.create(MastodonSearchService.class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch and federate the remote status | ||||||
|  |      */ | ||||||
|  |     public static void fetchRemoteStatus(@NonNull Context context, @NonNull Account ownerAccount, Status targetedStatus, Callback callback) { | ||||||
|  |         MastodonSearchService mastodonSearchService = init(context, MainActivity.currentInstance); | ||||||
|  |         new Thread(() -> { | ||||||
|  |             Call<Results> resultsCall = mastodonSearchService.search(ownerAccount.token, targetedStatus.url, null, "statuses", false, true, false, 0, null, null, 1); | ||||||
|  |             Results results = null; | ||||||
|  |             if (resultsCall != null) { | ||||||
|  |                 try { | ||||||
|  |                     Response<Results> resultsResponse = resultsCall.execute(); | ||||||
|  |                     if (resultsResponse.isSuccessful()) { | ||||||
|  |                         results = resultsResponse.body(); | ||||||
|  |                         if (results != null) { | ||||||
|  |                             if (results.statuses == null) { | ||||||
|  |                                 results.statuses = new ArrayList<>(); | ||||||
|  |                             } else { | ||||||
|  |                                 results.statuses = SpannableHelper.convertStatus(context, results.statuses); | ||||||
|  |                             } | ||||||
|  |                             if (results.accounts == null) { | ||||||
|  |                                 results.accounts = new ArrayList<>(); | ||||||
|  |                             } | ||||||
|  |                             if (results.hashtags == null) { | ||||||
|  |                                 results.hashtags = new ArrayList<>(); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } catch (IOException e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Handler mainHandler = new Handler(Looper.getMainLooper()); | ||||||
|  |             Results finalResults = results; | ||||||
|  |             Runnable myRunnable = () -> { | ||||||
|  |                 if (finalResults != null && finalResults.statuses != null && finalResults.statuses.size() > 0) { | ||||||
|  |                     callback.federatedStatus(finalResults.statuses.get(0)); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             mainHandler.post(myRunnable); | ||||||
|  | 
 | ||||||
|  |         }).start(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Fetch and federate the remote status | ||||||
|  |      */ | ||||||
|  |     public static void fetchRemoteAccount(@NonNull Context context, @NonNull Account ownerAccount, app.fedilab.android.client.mastodon.entities.Account targetedAccount, Callback callback) { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         MastodonSearchService mastodonSearchService = init(context, MainActivity.currentInstance); | ||||||
|  |         String search; | ||||||
|  |         if (targetedAccount.acct.contains("@")) { //Not from same instance | ||||||
|  |             search = targetedAccount.acct; | ||||||
|  |         } else { | ||||||
|  |             search = targetedAccount.acct + "@" + BaseMainActivity.currentInstance; | ||||||
|  |         } | ||||||
|  |         new Thread(() -> { | ||||||
|  |             Call<Results> resultsCall = mastodonSearchService.search(ownerAccount.token, search, null, "accounts", false, true, false, 0, null, null, 1); | ||||||
|  |             Results results = null; | ||||||
|  |             if (resultsCall != null) { | ||||||
|  |                 try { | ||||||
|  |                     Response<Results> resultsResponse = resultsCall.execute(); | ||||||
|  |                     if (resultsResponse.isSuccessful()) { | ||||||
|  |                         results = resultsResponse.body(); | ||||||
|  |                         if (results != null) { | ||||||
|  |                             if (results.statuses == null) { | ||||||
|  |                                 results.statuses = new ArrayList<>(); | ||||||
|  |                             } else { | ||||||
|  |                                 results.statuses = SpannableHelper.convertStatus(context, results.statuses); | ||||||
|  |                             } | ||||||
|  |                             if (results.accounts == null) { | ||||||
|  |                                 results.accounts = new ArrayList<>(); | ||||||
|  |                             } | ||||||
|  |                             if (results.hashtags == null) { | ||||||
|  |                                 results.hashtags = new ArrayList<>(); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } catch (IOException e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Handler mainHandler = new Handler(Looper.getMainLooper()); | ||||||
|  |             Results finalResults = results; | ||||||
|  |             Runnable myRunnable = () -> { | ||||||
|  |                 if (finalResults != null && finalResults.accounts != null && finalResults.accounts.size() > 0) { | ||||||
|  |                     callback.federatedAccount(finalResults.accounts.get(0)); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             mainHandler.post(myRunnable); | ||||||
|  | 
 | ||||||
|  |         }).start(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public interface Callback { | ||||||
|  |         void federatedStatus(Status status); | ||||||
|  | 
 | ||||||
|  |         void federatedAccount(app.fedilab.android.client.mastodon.entities.Account account); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     public enum TypeOfCrossAction { |     public enum TypeOfCrossAction { | ||||||
|         FOLLOW_ACTION, |         FOLLOW_ACTION, | ||||||
|         UNFOLLOW_ACTION, |         UNFOLLOW_ACTION, | ||||||
|  |  | ||||||
|  | @ -270,7 +270,10 @@ public class Helper { | ||||||
|     public static final Pattern mediumPattern = Pattern.compile("([\\w@-]*)?\\.?medium.com/@?([/\\w-]+)"); |     public static final Pattern mediumPattern = Pattern.compile("([\\w@-]*)?\\.?medium.com/@?([/\\w-]+)"); | ||||||
|     public static final Pattern wikipediaPattern = Pattern.compile("([\\w_-]+)\\.wikipedia.org/(((?!([\"'<])).)*)"); |     public static final Pattern wikipediaPattern = Pattern.compile("([\\w_-]+)\\.wikipedia.org/(((?!([\"'<])).)*)"); | ||||||
|     public static final Pattern codePattern = Pattern.compile("code=([\\w-]+)"); |     public static final Pattern codePattern = Pattern.compile("code=([\\w-]+)"); | ||||||
|  |     public static final Pattern urlPattern = Pattern.compile( | ||||||
|  |             "(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,10}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?«»“”‘’]))", | ||||||
| 
 | 
 | ||||||
|  |             Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL); | ||||||
|     /* |     /* | ||||||
|      * List from ClearUrls |      * List from ClearUrls | ||||||
|      * https://gitlab.com/KevinRoebert/ClearUrls/blob/master/data/data.min.json#L106 |      * https://gitlab.com/KevinRoebert/ClearUrls/blob/master/data/data.min.json#L106 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,82 @@ | ||||||
|  | package app.fedilab.android.helper; | ||||||
|  | 
 | ||||||
|  | import android.os.Handler; | ||||||
|  | import android.text.Layout; | ||||||
|  | import android.text.Selection; | ||||||
|  | import android.text.Spannable; | ||||||
|  | import android.text.method.LinkMovementMethod; | ||||||
|  | import android.text.method.MovementMethod; | ||||||
|  | import android.view.MotionEvent; | ||||||
|  | import android.widget.TextView; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | //https://stackoverflow.com/a/20435892 | ||||||
|  | public class LongClickLinkMovementMethod extends LinkMovementMethod { | ||||||
|  | 
 | ||||||
|  |     private static LongClickLinkMovementMethod sInstance; | ||||||
|  |     private Handler mLongClickHandler; | ||||||
|  |     private boolean mIsLongPressed = false; | ||||||
|  | 
 | ||||||
|  |     public static MovementMethod getInstance() { | ||||||
|  |         if (sInstance == null) { | ||||||
|  |             sInstance = new LongClickLinkMovementMethod(); | ||||||
|  |             sInstance.mLongClickHandler = new Handler(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return sInstance; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean onTouchEvent(final TextView widget, Spannable buffer, | ||||||
|  |                                 MotionEvent event) { | ||||||
|  |         int action = event.getAction(); | ||||||
|  |         if (action == MotionEvent.ACTION_CANCEL) { | ||||||
|  |             if (mLongClickHandler != null) { | ||||||
|  |                 mLongClickHandler.removeCallbacksAndMessages(null); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (action == MotionEvent.ACTION_UP || | ||||||
|  |                 action == MotionEvent.ACTION_DOWN) { | ||||||
|  |             int x = (int) event.getX(); | ||||||
|  |             int y = (int) event.getY(); | ||||||
|  | 
 | ||||||
|  |             x -= widget.getTotalPaddingLeft(); | ||||||
|  |             y -= widget.getTotalPaddingTop(); | ||||||
|  | 
 | ||||||
|  |             x += widget.getScrollX(); | ||||||
|  |             y += widget.getScrollY(); | ||||||
|  | 
 | ||||||
|  |             Layout layout = widget.getLayout(); | ||||||
|  |             int line = layout.getLineForVertical(y); | ||||||
|  |             int off = layout.getOffsetForHorizontal(line, x); | ||||||
|  | 
 | ||||||
|  |             final LongClickableSpan[] link = buffer.getSpans(off, off, LongClickableSpan.class); | ||||||
|  | 
 | ||||||
|  |             if (link.length != 0) { | ||||||
|  |                 if (action == MotionEvent.ACTION_UP) { | ||||||
|  |                     if (mLongClickHandler != null) { | ||||||
|  |                         mLongClickHandler.removeCallbacksAndMessages(null); | ||||||
|  |                     } | ||||||
|  |                     if (!mIsLongPressed) { | ||||||
|  |                         link[0].onClick(widget); | ||||||
|  |                     } | ||||||
|  |                     mIsLongPressed = false; | ||||||
|  |                 } else { | ||||||
|  |                     Selection.setSelection(buffer, | ||||||
|  |                             buffer.getSpanStart(link[0]), | ||||||
|  |                             buffer.getSpanEnd(link[0])); | ||||||
|  |                     int LONG_CLICK_TIME = 1000; | ||||||
|  |                     mLongClickHandler.postDelayed(() -> { | ||||||
|  |                         link[0].onLongClick(widget); | ||||||
|  |                         mIsLongPressed = true; | ||||||
|  |                         widget.invalidate(); | ||||||
|  |                     }, LONG_CLICK_TIME); | ||||||
|  |                 } | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return super.onTouchEvent(widget, buffer, event); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,10 @@ | ||||||
|  | package app.fedilab.android.helper; | ||||||
|  | 
 | ||||||
|  | import android.text.style.ClickableSpan; | ||||||
|  | import android.view.View; | ||||||
|  | 
 | ||||||
|  | public abstract class LongClickableSpan extends ClickableSpan { | ||||||
|  | 
 | ||||||
|  |     abstract public void onLongClick(View view); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -15,15 +15,21 @@ package app.fedilab.android.helper; | ||||||
|  * see <http://www.gnu.org/licenses>. */ |  * see <http://www.gnu.org/licenses>. */ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | import static app.fedilab.android.helper.Helper.USER_AGENT; | ||||||
| import static app.fedilab.android.helper.Helper.convertDpToPixel; | import static app.fedilab.android.helper.Helper.convertDpToPixel; | ||||||
|  | import static app.fedilab.android.helper.Helper.urlPattern; | ||||||
| import static app.fedilab.android.helper.ThemeHelper.linkColor; | import static app.fedilab.android.helper.ThemeHelper.linkColor; | ||||||
| 
 | 
 | ||||||
|  | import android.content.ClipData; | ||||||
|  | import android.content.ClipboardManager; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.graphics.drawable.Drawable; | import android.graphics.drawable.Drawable; | ||||||
|  | import android.net.Uri; | ||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
|  | import android.os.Handler; | ||||||
| import android.text.Html; | import android.text.Html; | ||||||
| import android.text.Spannable; | import android.text.Spannable; | ||||||
| import android.text.SpannableString; | import android.text.SpannableString; | ||||||
|  | @ -34,9 +40,12 @@ import android.text.style.ClickableSpan; | ||||||
| import android.text.style.ImageSpan; | import android.text.style.ImageSpan; | ||||||
| import android.text.style.URLSpan; | import android.text.style.URLSpan; | ||||||
| import android.util.Patterns; | import android.util.Patterns; | ||||||
|  | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
|  | import android.widget.Toast; | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
|  | import androidx.appcompat.app.AlertDialog; | ||||||
| import androidx.preference.PreferenceManager; | import androidx.preference.PreferenceManager; | ||||||
| 
 | 
 | ||||||
| import com.bumptech.glide.Glide; | import com.bumptech.glide.Glide; | ||||||
|  | @ -47,15 +56,23 @@ import com.github.penfeizhou.animation.gif.GifDrawable; | ||||||
| import com.github.penfeizhou.animation.gif.decode.GifParser; | import com.github.penfeizhou.animation.gif.decode.GifParser; | ||||||
| 
 | 
 | ||||||
| import java.io.File; | import java.io.File; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.net.MalformedURLException; | ||||||
|  | import java.net.URL; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
| import java.util.concurrent.ExecutionException; | import java.util.concurrent.ExecutionException; | ||||||
| import java.util.regex.Matcher; | import java.util.regex.Matcher; | ||||||
| import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||||
| 
 | 
 | ||||||
|  | import javax.net.ssl.HttpsURLConnection; | ||||||
|  | 
 | ||||||
| import app.fedilab.android.R; | import app.fedilab.android.R; | ||||||
|  | import app.fedilab.android.activities.ContextActivity; | ||||||
| import app.fedilab.android.activities.HashTagActivity; | import app.fedilab.android.activities.HashTagActivity; | ||||||
|  | import app.fedilab.android.activities.MainActivity; | ||||||
| import app.fedilab.android.activities.ProfileActivity; | import app.fedilab.android.activities.ProfileActivity; | ||||||
| import app.fedilab.android.client.mastodon.entities.Account; | import app.fedilab.android.client.mastodon.entities.Account; | ||||||
| import app.fedilab.android.client.mastodon.entities.Attachment; | import app.fedilab.android.client.mastodon.entities.Attachment; | ||||||
|  | @ -64,6 +81,8 @@ import app.fedilab.android.client.mastodon.entities.Field; | ||||||
| import app.fedilab.android.client.mastodon.entities.Mention; | import app.fedilab.android.client.mastodon.entities.Mention; | ||||||
| import app.fedilab.android.client.mastodon.entities.Poll; | import app.fedilab.android.client.mastodon.entities.Poll; | ||||||
| import app.fedilab.android.client.mastodon.entities.Status; | import app.fedilab.android.client.mastodon.entities.Status; | ||||||
|  | import app.fedilab.android.databinding.PopupLinksBinding; | ||||||
|  | import es.dmoral.toasty.Toasty; | ||||||
| 
 | 
 | ||||||
| public class SpannableHelper { | public class SpannableHelper { | ||||||
| 
 | 
 | ||||||
|  | @ -188,11 +207,172 @@ public class SpannableHelper { | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (matchStart >= 0 && matchEnd <= content.length() && matchEnd >= matchStart) { |             if (matchStart >= 0 && matchEnd <= content.length() && matchEnd >= matchStart) { | ||||||
|                 content.setSpan(new ClickableSpan() { |                 content.setSpan(new LongClickableSpan() { | ||||||
|  |                     @Override | ||||||
|  |                     public void onLongClick(View view) { | ||||||
|  |                         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(view.getContext(), Helper.dialogStyle()); | ||||||
|  |                         PopupLinksBinding popupLinksBinding = PopupLinksBinding.inflate(LayoutInflater.from(context)); | ||||||
|  |                         dialogBuilder.setView(popupLinksBinding.getRoot()); | ||||||
|  |                         AlertDialog alertDialog = dialogBuilder.create(); | ||||||
|  |                         alertDialog.show(); | ||||||
|  | 
 | ||||||
|  |                         popupLinksBinding.displayFullLink.setOnClickListener(v -> { | ||||||
|  |                             AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle()); | ||||||
|  |                             builder.setMessage(url); | ||||||
|  |                             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, url); | ||||||
|  |                             sendIntent.setType("text/plain"); | ||||||
|  |                             context.startActivity(Intent.createChooser(sendIntent, context.getString(R.string.share_with))); | ||||||
|  |                             alertDialog.dismiss(); | ||||||
|  |                         }); | ||||||
|  | 
 | ||||||
|  |                         popupLinksBinding.openOtherApp.setOnClickListener(v -> { | ||||||
|  |                             Intent intent = new Intent(Intent.ACTION_VIEW); | ||||||
|  |                             intent.setData(Uri.parse(url)); | ||||||
|  |                             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, url); | ||||||
|  |                             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(url); | ||||||
|  |                                 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"); | ||||||
|  |                                         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 && redirect.compareTo(url) != 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(context, Helper.dialogStyle()); | ||||||
|  |                                             if (finalRedirect != null) { | ||||||
|  |                                                 builder1.setMessage(context.getString(R.string.redirect_detected, url, 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, url); | ||||||
|  |                                                     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 |                     @Override | ||||||
|                     public void onClick(@NonNull View textView) { |                     public void onClick(@NonNull View textView) { | ||||||
|                         textView.setTag(CLICKABLE_SPAN); |                         textView.setTag(CLICKABLE_SPAN); | ||||||
|                         Helper.openBrowser(context, newURL); |                         Pattern link = Pattern.compile("https?://([\\da-z.-]+\\.[a-z.]{2,10})/(@[\\w._-]*[0-9]*)(/[0-9]+)?$"); | ||||||
|  |                         Matcher matcherLink = link.matcher(url); | ||||||
|  |                         if (matcherLink.find() && !url.contains("medium.com")) { | ||||||
|  |                             if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot | ||||||
|  |                                 CrossActionHelper.fetchRemoteStatus(context, MainActivity.accountWeakReference.get(), status, new CrossActionHelper.Callback() { | ||||||
|  |                                     @Override | ||||||
|  |                                     public void federatedStatus(Status status) { | ||||||
|  |                                         Intent intent = new Intent(context, ContextActivity.class); | ||||||
|  |                                         intent.putExtra(Helper.ARG_STATUS, status); | ||||||
|  |                                         context.startActivity(intent); | ||||||
|  |                                     } | ||||||
|  | 
 | ||||||
|  |                                     @Override | ||||||
|  |                                     public void federatedAccount(Account account) { | ||||||
|  | 
 | ||||||
|  |                                     } | ||||||
|  |                                 }); | ||||||
|  |                             } else {//It's an account | ||||||
|  |                                 CrossActionHelper.fetchRemoteAccount(context, MainActivity.accountWeakReference.get(), status.account, 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); | ||||||
|  |                                         context.startActivity(intent); | ||||||
|  |                                     } | ||||||
|  |                                 }); | ||||||
|  |                             } | ||||||
|  |                         } else { | ||||||
|  |                             Helper.openBrowser(context, newURL); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     @Override |                     @Override | ||||||
|  |  | ||||||
|  | @ -42,7 +42,6 @@ import android.text.Layout; | ||||||
| import android.text.SpannableString; | import android.text.SpannableString; | ||||||
| import android.text.Spanned; | import android.text.Spanned; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| import android.text.method.LinkMovementMethod; |  | ||||||
| import android.text.style.ForegroundColorSpan; | import android.text.style.ForegroundColorSpan; | ||||||
| import android.util.TypedValue; | import android.util.TypedValue; | ||||||
| import android.view.ContextThemeWrapper; | import android.view.ContextThemeWrapper; | ||||||
|  | @ -116,6 +115,7 @@ import app.fedilab.android.databinding.LayoutMediaBinding; | ||||||
| import app.fedilab.android.databinding.LayoutPollItemBinding; | import app.fedilab.android.databinding.LayoutPollItemBinding; | ||||||
| import app.fedilab.android.helper.CrossActionHelper; | import app.fedilab.android.helper.CrossActionHelper; | ||||||
| import app.fedilab.android.helper.Helper; | import app.fedilab.android.helper.Helper; | ||||||
|  | import app.fedilab.android.helper.LongClickLinkMovementMethod; | ||||||
| import app.fedilab.android.helper.MastodonHelper; | import app.fedilab.android.helper.MastodonHelper; | ||||||
| import app.fedilab.android.helper.SpannableHelper; | import app.fedilab.android.helper.SpannableHelper; | ||||||
| import app.fedilab.android.helper.ThemeHelper; | import app.fedilab.android.helper.ThemeHelper; | ||||||
|  | @ -940,8 +940,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | ||||||
|             holder.binding.mediaContainer.setVisibility(View.GONE); |             holder.binding.mediaContainer.setVisibility(View.GONE); | ||||||
|             holder.binding.attachmentsListContainer.setVisibility(View.GONE); |             holder.binding.attachmentsListContainer.setVisibility(View.GONE); | ||||||
|         } |         } | ||||||
|         holder.binding.statusContent.setMovementMethod(LinkMovementMethod.getInstance()); |         holder.binding.statusContent.setMovementMethod(LongClickLinkMovementMethod.getInstance()); | ||||||
| 
 |         //holder.binding.statusContent.setMovementMethod(LinkMovementMethod.getInstance()); | ||||||
|         holder.binding.reblogInfo.setOnClickListener(v -> { |         holder.binding.reblogInfo.setOnClickListener(v -> { | ||||||
|             if (remote) { |             if (remote) { | ||||||
|                 Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show(); |                 Toasty.info(context, context.getString(R.string.retrieve_remote_status), Toasty.LENGTH_SHORT).show(); | ||||||
|  |  | ||||||
							
								
								
									
										69
									
								
								app/src/main/res/layout/popup_links.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/src/main/res/layout/popup_links.xml
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,69 @@ | ||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:layout_margin="@dimen/fab_margin" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:padding="@dimen/fab_margin"> | ||||||
|  | 
 | ||||||
|  |     <androidx.appcompat.widget.AppCompatTextView | ||||||
|  |         android:id="@+id/display_full_link" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginTop="10dp" | ||||||
|  |         android:layout_marginBottom="10dp" | ||||||
|  |         android:drawableEnd="@drawable/ic_baseline_navigate_next_24" | ||||||
|  |         android:text="@string/display_full_link" | ||||||
|  |         android:textColor="@color/cyanea_accent_reference" | ||||||
|  |         android:textSize="16sp" | ||||||
|  |         app:drawableTint="@color/cyanea_accent_reference" /> | ||||||
|  | 
 | ||||||
|  |     <androidx.appcompat.widget.AppCompatTextView | ||||||
|  |         android:id="@+id/share_link" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginTop="10dp" | ||||||
|  |         android:layout_marginBottom="10dp" | ||||||
|  |         android:drawableEnd="@drawable/ic_baseline_navigate_next_24" | ||||||
|  |         android:text="@string/share_link" | ||||||
|  |         android:textColor="@color/cyanea_accent_reference" | ||||||
|  |         android:textSize="16sp" | ||||||
|  |         app:drawableTint="@color/cyanea_accent_reference" /> | ||||||
|  | 
 | ||||||
|  |     <androidx.appcompat.widget.AppCompatTextView | ||||||
|  |         android:id="@+id/open_other_app" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginTop="10dp" | ||||||
|  |         android:layout_marginBottom="10dp" | ||||||
|  |         android:drawableEnd="@drawable/ic_baseline_navigate_next_24" | ||||||
|  |         android:text="@string/open_other_app" | ||||||
|  |         android:textColor="@color/cyanea_accent_reference" | ||||||
|  |         android:textSize="16sp" | ||||||
|  |         app:drawableTint="@color/cyanea_accent_reference" /> | ||||||
|  | 
 | ||||||
|  |     <androidx.appcompat.widget.AppCompatTextView | ||||||
|  |         android:id="@+id/copy_link" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginTop="10dp" | ||||||
|  |         android:layout_marginBottom="10dp" | ||||||
|  |         android:drawableEnd="@drawable/ic_baseline_navigate_next_24" | ||||||
|  |         android:text="@string/copy_link" | ||||||
|  |         android:textColor="@color/cyanea_accent_reference" | ||||||
|  |         android:textSize="16sp" | ||||||
|  |         app:drawableTint="@color/cyanea_accent_reference" /> | ||||||
|  | 
 | ||||||
|  |     <androidx.appcompat.widget.AppCompatTextView | ||||||
|  |         android:id="@+id/check_redirect" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginTop="10dp" | ||||||
|  |         android:layout_marginBottom="10dp" | ||||||
|  |         android:drawableEnd="@drawable/ic_baseline_navigate_next_24" | ||||||
|  |         android:text="@string/check_redirect" | ||||||
|  |         android:textColor="@color/cyanea_accent_reference" | ||||||
|  |         android:textSize="16sp" | ||||||
|  |         app:drawableTint="@color/cyanea_accent_reference" /> | ||||||
|  | </LinearLayout> | ||||||
		Loading…
	
		Reference in a new issue