mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 00:37:47 +00:00
Don't preview links if your cursor is touching them.
This commit is contained in:
parent
1c23603c25
commit
fe4068afce
@ -11,6 +11,7 @@ import android.support.v13.view.inputmethod.EditorInfoCompat;
|
||||
import android.support.v13.view.inputmethod.InputConnectionCompat;
|
||||
import android.support.v13.view.inputmethod.InputContentInfoCompat;
|
||||
import android.support.v4.os.BuildCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
@ -33,7 +34,8 @@ public class ComposeText extends EmojiEditText {
|
||||
private CharSequence hint;
|
||||
private SpannableString subHint;
|
||||
|
||||
@Nullable private InputPanel.MediaListener mediaListener;
|
||||
@Nullable private InputPanel.MediaListener mediaListener;
|
||||
@Nullable private CursorPositionChangedListener cursorPositionChangedListener;
|
||||
|
||||
public ComposeText(Context context) {
|
||||
super(context);
|
||||
@ -69,6 +71,15 @@ public class ComposeText extends EmojiEditText {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSelectionChanged(int selStart, int selEnd) {
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
|
||||
if (cursorPositionChangedListener != null) {
|
||||
cursorPositionChangedListener.onCursorPositionChanged(selStart, selEnd);
|
||||
}
|
||||
}
|
||||
|
||||
private CharSequence ellipsizeToWidth(CharSequence text) {
|
||||
return TextUtils.ellipsize(text,
|
||||
getPaint(),
|
||||
@ -104,6 +115,10 @@ public class ComposeText extends EmojiEditText {
|
||||
setSelection(getText().length());
|
||||
}
|
||||
|
||||
public void setCursorPositionChangedListener(@Nullable CursorPositionChangedListener listener) {
|
||||
this.cursorPositionChangedListener = listener;
|
||||
}
|
||||
|
||||
private boolean isLandscape() {
|
||||
return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
|
||||
}
|
||||
@ -189,4 +204,7 @@ public class ComposeText extends EmojiEditText {
|
||||
}
|
||||
}
|
||||
|
||||
public interface CursorPositionChangedListener {
|
||||
void onCursorPositionChanged(int start, int end);
|
||||
}
|
||||
}
|
||||
|
@ -239,6 +239,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
AttachmentDrawerListener,
|
||||
InputPanel.Listener,
|
||||
InputPanel.MediaListener,
|
||||
ComposeText.CursorPositionChangedListener,
|
||||
ConversationSearchBottomBar.EventListener
|
||||
{
|
||||
private static final String TAG = ConversationActivity.class.getSimpleName();
|
||||
@ -361,6 +362,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
if (TextSecurePreferences.isTypingIndicatorsEnabled(ConversationActivity.this)) {
|
||||
composeText.addTextChangedListener(typingTextWatcher);
|
||||
}
|
||||
composeText.setSelection(composeText.length(), composeText.length());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1527,6 +1529,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
|
||||
|
||||
composeText.setOnEditorActionListener(sendButtonListener);
|
||||
composeText.setCursorPositionChangedListener(this);
|
||||
attachButton.setOnClickListener(new AttachButtonListener());
|
||||
attachButton.setOnLongClickListener(new AttachButtonLongClickListener());
|
||||
sendButton.setOnClickListener(sendButtonListener);
|
||||
@ -2187,7 +2190,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private void updateLinkPreviewState() {
|
||||
if (TextSecurePreferences.isLinkPreviewsEnabled(this) && !sendButton.getSelectedTransport().isSms() && !attachmentManager.isAttachmentPresent()) {
|
||||
linkPreviewViewModel.onEnabled();
|
||||
linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed());
|
||||
linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), composeText.getSelectionStart(), composeText.getSelectionEnd());
|
||||
} else {
|
||||
linkPreviewViewModel.onUserCancel();
|
||||
}
|
||||
@ -2358,6 +2361,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCursorPositionChanged(int start, int end) {
|
||||
linkPreviewViewModel.onTextChanged(this, composeText.getTextTrimmed(), start, end);
|
||||
}
|
||||
|
||||
private void silentlySetComposeText(String text) {
|
||||
typingTextWatcher.setEnabled(false);
|
||||
composeText.setText(text);
|
||||
@ -2461,11 +2469,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
public void afterTextChanged(Editable s) {
|
||||
calculateCharactersRemaining();
|
||||
|
||||
String trimmed = composeText.getTextTrimmed();
|
||||
|
||||
linkPreviewViewModel.onTextChanged(ConversationActivity.this, trimmed);
|
||||
|
||||
if (trimmed.length() == 0 || beforeLength == 0) {
|
||||
if (composeText.getTextTrimmed().length() == 0 || beforeLength == 0) {
|
||||
composeText.postDelayed(ConversationActivity.this::updateToggleButtonState, 50);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.libsignal.metadata.InvalidMetadataMessageException;
|
||||
@ -56,6 +57,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobParameters;
|
||||
import org.thoughtcrime.securesms.jobmanager.SafeData;
|
||||
import org.thoughtcrime.securesms.linkpreview.Link;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
@ -1070,7 +1072,7 @@ public class PushDecryptJob extends ContextJob {
|
||||
Optional<String> url = Optional.fromNullable(preview.getUrl());
|
||||
Optional<String> title = Optional.fromNullable(preview.getTitle());
|
||||
boolean hasContent = !TextUtils.isEmpty(title.or("")) || thumbnail.isPresent();
|
||||
boolean presentInBody = url.isPresent() && LinkPreviewUtil.findWhitelistedUrls(message).contains(url.get());
|
||||
boolean presentInBody = url.isPresent() && Stream.of(LinkPreviewUtil.findWhitelistedUrls(message)).map(Link::getUrl).collect(Collectors.toSet()).contains(url.get());
|
||||
boolean validDomain = url.isPresent() && LinkPreviewUtil.isWhitelistedLinkUrl(url.get());
|
||||
|
||||
if (hasContent && presentInBody && validDomain) {
|
||||
|
20
src/org/thoughtcrime/securesms/linkpreview/Link.java
Normal file
20
src/org/thoughtcrime/securesms/linkpreview/Link.java
Normal file
@ -0,0 +1,20 @@
|
||||
package org.thoughtcrime.securesms.linkpreview;
|
||||
|
||||
public class Link {
|
||||
|
||||
private final String url;
|
||||
private final int position;
|
||||
|
||||
public Link(String url, int position) {
|
||||
this.url = url;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ public final class LinkPreviewUtil {
|
||||
/**
|
||||
* @return All whitelisted URLs in the source text.
|
||||
*/
|
||||
public static @NonNull List<String> findWhitelistedUrls(@NonNull String text) {
|
||||
public static @NonNull List<Link> findWhitelistedUrls(@NonNull String text) {
|
||||
SpannableString spannable = new SpannableString(text);
|
||||
boolean found = Linkify.addLinks(spannable, Linkify.WEB_URLS);
|
||||
|
||||
@ -27,8 +27,8 @@ public final class LinkPreviewUtil {
|
||||
}
|
||||
|
||||
return Stream.of(spannable.getSpans(0, spannable.length(), URLSpan.class))
|
||||
.map(URLSpan::getURL)
|
||||
.filter(LinkPreviewUtil::isWhitelistedLinkUrl)
|
||||
.map(span -> new Link(span.getURL(), spannable.getSpanStart(span)))
|
||||
.filter(link -> isWhitelistedLinkUrl(link.getUrl()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,9 @@ import android.arch.lifecycle.ViewModelProvider;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
@ -81,7 +84,7 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
return Collections.singletonList(new LinkPreview(originalPreview.getUrl(), originalPreview.getTitle(), Optional.of(newAttachment)));
|
||||
}
|
||||
|
||||
public void onTextChanged(@NonNull Context context, @NonNull String text) {
|
||||
public void onTextChanged(@NonNull Context context, @NonNull String text, int cursorStart, int cursorEnd) {
|
||||
debouncer.publish(() -> {
|
||||
if (TextUtils.isEmpty(text)) {
|
||||
userCanceled = false;
|
||||
@ -91,10 +94,10 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> urls = LinkPreviewUtil.findWhitelistedUrls(text);
|
||||
Optional<String> url = urls.isEmpty() ? Optional.absent() : Optional.of(urls.get(0));
|
||||
List<Link> links = LinkPreviewUtil.findWhitelistedUrls(text);
|
||||
Optional<Link> link = links.isEmpty() ? Optional.absent() : Optional.of(links.get(0));
|
||||
|
||||
if (url.isPresent() && url.get().equals(activeUrl)) {
|
||||
if (link.isPresent() && link.get().getUrl().equals(activeUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -103,7 +106,7 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
activeRequest = null;
|
||||
}
|
||||
|
||||
if (!url.isPresent()) {
|
||||
if (!link.isPresent() || !isCursorPositionValid(text, link.get(), cursorStart, cursorEnd)) {
|
||||
activeUrl = null;
|
||||
linkPreviewState.setValue(LinkPreviewState.forEmpty());
|
||||
return;
|
||||
@ -111,8 +114,8 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
|
||||
linkPreviewState.setValue(LinkPreviewState.forLoading());
|
||||
|
||||
activeUrl = url.get();
|
||||
activeRequest = repository.getLinkPreview(context, url.get(), lp -> {
|
||||
activeUrl = link.get().getUrl();
|
||||
activeRequest = repository.getLinkPreview(context, link.get().getUrl(), lp -> {
|
||||
Util.runOnMain(() -> {
|
||||
if (!userCanceled) {
|
||||
linkPreviewState.setValue(LinkPreviewState.forPreview(lp));
|
||||
@ -123,7 +126,6 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void onUserCancel() {
|
||||
if (activeRequest != null) {
|
||||
activeRequest.cancel();
|
||||
@ -150,6 +152,18 @@ public class LinkPreviewViewModel extends ViewModel {
|
||||
debouncer.clear();
|
||||
}
|
||||
|
||||
private boolean isCursorPositionValid(@NonNull String text, @NonNull Link link, int cursorStart, int cursorEnd) {
|
||||
if (cursorStart != cursorEnd) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (text.endsWith(link.getUrl()) && cursorStart == link.getPosition() + link.getUrl().length()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return cursorStart < link.getPosition() || cursorStart > link.getPosition() + link.getUrl().length();
|
||||
}
|
||||
|
||||
public static class LinkPreviewState {
|
||||
private final boolean isLoading;
|
||||
private final Optional<LinkPreview> linkPreview;
|
||||
|
Loading…
x
Reference in New Issue
Block a user