diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 4e8cfcc4c..d3a6f41a9 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -37,6 +37,7 @@ import android.os.Handler; import android.os.Parcelable; import android.preference.PreferenceManager; import android.util.Patterns; +import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -1704,6 +1705,19 @@ public abstract class BaseMainActivity extends BaseActivity } + + public static boolean canShowActionMode = true; + + + @Override + public void onActionModeStarted(ActionMode mode) { + if (!canShowActionMode) { + mode.finish(); + } + super.onActionModeStarted(mode); + + } + @Override protected void onPause() { super.onPause(); diff --git a/app/src/main/java/app/fedilab/android/activities/LiveNotificationSettingsAccountsActivity.java b/app/src/main/java/app/fedilab/android/activities/LiveNotificationSettingsAccountsActivity.java index 726e40066..37b912e59 100644 --- a/app/src/main/java/app/fedilab/android/activities/LiveNotificationSettingsAccountsActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/LiveNotificationSettingsAccountsActivity.java @@ -63,6 +63,10 @@ public class LiveNotificationSettingsAccountsActivity extends BaseActivity { ArrayList accounts = new ArrayList<>(); SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); List accountStreams = new AccountDAO(getApplicationContext(), db).getAllAccountCrossAction(); + if( accountStreams == null || accountStreams.size() == 0 ){ + finish(); + return; + } for (Account account : accountStreams) { if (account.getSocial() == null || account.getSocial().equals("MASTODON") || account.getSocial().equals("PLEROMA") || account.getSocial().equals("PIXELFED")) { accounts.add(account); diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index 8ea2e7c81..ef8689948 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -16,7 +16,10 @@ package app.fedilab.android.client.Entities; import android.app.Activity; +import android.content.ClipData; +import android.content.ClipboardManager; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; @@ -38,10 +41,14 @@ import android.text.style.ImageSpan; import android.text.style.QuoteSpan; import android.text.style.URLSpan; import android.util.Patterns; +import android.view.MenuItem; import android.view.View; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; @@ -70,6 +77,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import app.fedilab.android.R; +import app.fedilab.android.activities.BaseMainActivity; import app.fedilab.android.activities.GroupActivity; import app.fedilab.android.activities.HashTagActivity; import app.fedilab.android.activities.MainActivity; @@ -77,14 +85,20 @@ import app.fedilab.android.activities.PeertubeActivity; import app.fedilab.android.activities.ShowAccountActivity; import app.fedilab.android.asynctasks.RetrieveFeedsAsyncTask; import app.fedilab.android.asynctasks.UpdateAccountInfoAsyncTask; +import app.fedilab.android.fragments.TabLayoutNotificationsFragment; import app.fedilab.android.helper.CrossActions; import app.fedilab.android.helper.CustomQuoteSpan; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.LongClickLinkMovementMethod; +import app.fedilab.android.helper.LongClickableSpan; import app.fedilab.android.helper.ThemeHelper; import app.fedilab.android.interfaces.OnRetrieveEmojiInterface; import app.fedilab.android.interfaces.OnRetrieveImageInterface; +import app.fedilab.android.sqlite.StatusStoredDAO; +import es.dmoral.toasty.Toasty; import static android.content.Context.MODE_PRIVATE; +import static app.fedilab.android.activities.BaseMainActivity.mPageReferenceMap; import static app.fedilab.android.drawers.StatusListAdapter.COMPACT_STATUS; import static app.fedilab.android.drawers.StatusListAdapter.CONSOLE_STATUS; import static app.fedilab.android.drawers.StatusListAdapter.DISPLAYED_STATUS; @@ -601,58 +615,122 @@ public class Status implements Parcelable { endPosition = startPosition + key.length(); } if (endPosition <= spannableStringT.toString().length() && endPosition >= startPosition) { - spannableStringT.setSpan(new ClickableSpan() { - @Override - public void onClick(@NonNull View textView) { - String finalUrl = url; - Pattern link = Pattern.compile("https?:\\/\\/([\\da-z\\.-]+\\.[a-z\\.]{2,10})\\/(@[\\w._-]*[0-9]*)(\\/[0-9]{1,})?$"); - Matcher matcherLink = link.matcher(url); - if (matcherLink.find() && !url.contains("medium.com")) { - if (matcherLink.group(3) != null && matcherLink.group(3).length() > 0) { //It's a toot - CrossActions.doCrossConversation(context, finalUrl); - } else {//It's an account - Account account = new Account(); - String acct = matcherLink.group(2); - if (acct != null) { - if (acct.startsWith("@")) - acct = acct.substring(1); - account.setAcct(acct); - account.setInstance(matcherLink.group(1)); - CrossActions.doCrossProfile(context, account); - } - } - } else { - link = Pattern.compile("(https?:\\/\\/[\\da-z\\.-]+\\.[a-z\\.]{2,10})\\/videos\\/watch\\/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})$"); - matcherLink = link.matcher(url); - if (matcherLink.find()) { //Peertubee video - Intent intent = new Intent(context, PeertubeActivity.class); - Bundle b = new Bundle(); - String url = matcherLink.group(1) + "/videos/watch/" + matcherLink.group(2); - b.putString("peertubeLinkToFetch", url); - b.putString("peertube_instance", matcherLink.group(1).replace("https://", "").replace("http://", "")); - b.putString("video_id", matcherLink.group(2)); - intent.putExtras(b); - context.startActivity(intent); - } else { - if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) - finalUrl = "http://" + url; - Helper.openBrowser(context, finalUrl); - } + spannableStringT.setSpan(new LongClickableSpan() { + @Override + public void onClick(@NonNull View textView) { + String finalUrl = url; + Pattern link = Pattern.compile("https?:\\/\\/([\\da-z\\.-]+\\.[a-z\\.]{2,10})\\/(@[\\w._-]*[0-9]*)(\\/[0-9]{1,})?$"); + Matcher matcherLink = link.matcher(url); + if (matcherLink.find() && !url.contains("medium.com")) { + if (matcherLink.group(3) != null && matcherLink.group(3).length() > 0) { //It's a toot + CrossActions.doCrossConversation(context, finalUrl); + } else {//It's an account + Account account = new Account(); + String acct = matcherLink.group(2); + if (acct != null) { + if (acct.startsWith("@")) + acct = acct.substring(1); + account.setAcct(acct); + account.setInstance(matcherLink.group(1)); + CrossActions.doCrossProfile(context, account); } + } - @Override - public void updateDrawState(@NonNull TextPaint ds) { - super.updateDrawState(ds); - ds.setUnderlineText(false); - ds.setColor(link_color); + } else { + link = Pattern.compile("(https?:\\/\\/[\\da-z\\.-]+\\.[a-z\\.]{2,10})\\/videos\\/watch\\/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})$"); + matcherLink = link.matcher(url); + if (matcherLink.find()) { //Peertubee video + Intent intent = new Intent(context, PeertubeActivity.class); + Bundle b = new Bundle(); + String url = matcherLink.group(1) + "/videos/watch/" + matcherLink.group(2); + b.putString("peertubeLinkToFetch", url); + b.putString("peertube_instance", matcherLink.group(1).replace("https://", "").replace("http://", "")); + b.putString("video_id", matcherLink.group(2)); + intent.putExtras(b); + context.startActivity(intent); + } else { + if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) + finalUrl = "http://" + url; + Helper.openBrowser(context, finalUrl); } - }, - startPosition, endPosition, - Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + } + } + + @Override + public void onLongClick(@NonNull View textView) { + PopupMenu popup = new PopupMenu(context, textView); + popup.getMenuInflater() + .inflate(R.menu.links_popup, popup.getMenu()); + popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_show_link: + int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); + int style; + if (theme == Helper.THEME_DARK) { + style = R.style.DialogDark; + } else if (theme == Helper.THEME_BLACK) { + style = R.style.DialogBlack; + } else { + style = R.style.Dialog; + } + AlertDialog.Builder builder = new AlertDialog.Builder(context, style); + builder.setMessage(url); + builder.setTitle(context.getString(R.string.display_full_link)); + builder.setPositiveButton(R.string.close, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .show(); + break; + case R.id.action_share_link: + 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))); + break; + case R.id.action_copy_link: + 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(); + } + break; + } + return true; + } + }); + popup.setOnDismissListener(new PopupMenu.OnDismissListener() { + @Override + public void onDismiss(PopupMenu menu) { + BaseMainActivity.canShowActionMode = true; + } + }); + popup.show(); + BaseMainActivity.canShowActionMode = false; + } + + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + ds.setColor(link_color); + } + }, + startPosition, endPosition, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + + } } it.remove(); diff --git a/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java b/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java index 59fd4e059..2cf0940d4 100644 --- a/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/StatusListAdapter.java @@ -152,6 +152,7 @@ import app.fedilab.android.fragments.DisplayStatusFragment; import app.fedilab.android.helper.CrossActions; import app.fedilab.android.helper.CustomTextView; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.LongClickLinkMovementMethod; import app.fedilab.android.helper.MastalabAutoCompleteTextView; import app.fedilab.android.helper.ThemeHelper; import app.fedilab.android.interfaces.OnPollInterface; @@ -1530,7 +1531,7 @@ public class StatusListAdapter extends RecyclerView.Adapter implements OnPostAct } - holder.status_content.setMovementMethod(LinkMovementMethod.getInstance()); + holder.status_content.setMovementMethod(LongClickLinkMovementMethod.getInstance()); holder.status_spoiler.setMovementMethod(LinkMovementMethod.getInstance()); if (truncate_toots_size > 0) { holder.status_content.setMaxLines(truncate_toots_size); diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java index 9e6d35ccc..6cf27fc1f 100644 --- a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java @@ -831,7 +831,9 @@ public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsIn public void scrollToTop() { - mLayoutManager.scrollToPositionWithOffset(0, 0); + if( mLayoutManager != null ) { + mLayoutManager.scrollToPositionWithOffset(0, 0); + } } /** diff --git a/app/src/main/java/app/fedilab/android/helper/LongClickLinkMovementMethod.java b/app/src/main/java/app/fedilab/android/helper/LongClickLinkMovementMethod.java new file mode 100644 index 000000000..1097af0d4 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/LongClickLinkMovementMethod.java @@ -0,0 +1,90 @@ +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 Handler mLongClickHandler; + private static int LONG_CLICK_TIME = 1000; + private boolean mIsLongPressed = false; + + + @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])); + mLongClickHandler.postDelayed(new Runnable() { + @Override + public void run() { + link[0].onLongClick(widget); + mIsLongPressed = true; + widget.invalidate(); + } + },LONG_CLICK_TIME); + } + return true; + } + } + + return super.onTouchEvent(widget, buffer, event); + } + + + public static MovementMethod getInstance() { + if (sInstance == null) { + sInstance = new LongClickLinkMovementMethod(); + sInstance.mLongClickHandler = new Handler(); + } + + return sInstance; + } + private static LongClickLinkMovementMethod sInstance; + + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/helper/LongClickableSpan.java b/app/src/main/java/app/fedilab/android/helper/LongClickableSpan.java new file mode 100644 index 000000000..9d518ec94 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/LongClickableSpan.java @@ -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); + +} \ No newline at end of file diff --git a/app/src/main/res/menu/links_popup.xml b/app/src/main/res/menu/links_popup.xml new file mode 100644 index 000000000..5b4e2bd26 --- /dev/null +++ b/app/src/main/res/menu/links_popup.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ed4365540..0b228bc94 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1190,4 +1190,7 @@ What\'s new in %s You can follow my account for updates This instance is not available on https://instances.social + Display full link + Share link + The URL has been copied to the clipboard \ No newline at end of file