Add text formatting send and receive support for conversations.

This commit is contained in:
Cody Henthorne
2023-01-25 10:31:36 -05:00
committed by Greyson Parrelli
parent aa2075c78f
commit cc490f4b73
73 changed files with 1664 additions and 516 deletions

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.text.Annotation;
@@ -14,8 +15,15 @@ import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.text.style.CharacterStyle;
import android.text.style.RelativeSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -35,18 +43,19 @@ import org.thoughtcrime.securesms.components.mention.MentionDeleter;
import org.thoughtcrime.securesms.components.mention.MentionRendererDelegate;
import org.thoughtcrime.securesms.components.mention.MentionValidatorWatcher;
import org.thoughtcrime.securesms.conversation.MessageSendType;
import org.thoughtcrime.securesms.conversation.MessageStyler;
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQuery;
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryChangedListener;
import org.thoughtcrime.securesms.conversation.ui.inlinequery.InlineQueryReplacement;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.thoughtcrime.securesms.database.MentionUtil.MENTION_STARTER;
@@ -276,6 +285,19 @@ public class ComposeText extends EmojiEditText {
return MentionAnnotation.getMentionsFromAnnotations(getText());
}
public boolean hasStyling() {
CharSequence trimmed = getTextTrimmed();
return FeatureFlags.textFormatting() && (trimmed instanceof Spanned) && MessageStyler.hasStyling((Spanned) trimmed);
}
public @Nullable BodyRangeList getStyling() {
if (FeatureFlags.textFormatting()) {
return MessageStyler.getStyling(getTextTrimmed());
} else {
return null;
}
}
private void initialize() {
if (TextSecurePreferences.isIncognitoKeyboardEnabled(getContext())) {
setImeOptions(getImeOptions() | 16777216);
@@ -286,6 +308,80 @@ public class ComposeText extends EmojiEditText {
addTextChangedListener(new MentionDeleter());
mentionValidatorWatcher = new MentionValidatorWatcher();
addTextChangedListener(mentionValidatorWatcher);
if (FeatureFlags.textFormatting()) {
setCustomSelectionActionModeCallback(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuItem copy = menu.findItem(android.R.id.copy);
MenuItem cut = menu.findItem(android.R.id.cut);
MenuItem paste = menu.findItem(android.R.id.paste);
int copyOrder = copy != null ? copy.getOrder() : 0;
int cutOrder = cut != null ? cut.getOrder() : 0;
int pasteOrder = paste != null ? paste.getOrder() : 0;
int largestOrder = Math.max(copyOrder, Math.max(cutOrder, pasteOrder));
menu.add(0, R.id.edittext_bold, largestOrder, getContext().getString(R.string.TextFormatting_bold));
menu.add(0, R.id.edittext_italic, largestOrder, getContext().getString(R.string.TextFormatting_italic));
menu.add(0, R.id.edittext_strikethrough, largestOrder, getContext().getString(R.string.TextFormatting_strikethrough));
menu.add(0, R.id.edittext_monospace, largestOrder, getContext().getString(R.string.TextFormatting_monospace));
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Editable text = getText();
if (text == null) {
return false;
}
if (item.getItemId() != R.id.edittext_bold &&
item.getItemId() != R.id.edittext_italic &&
item.getItemId() != R.id.edittext_strikethrough &&
item.getItemId() != R.id.edittext_monospace) {
return false;
}
int start = getSelectionStart();
int end = getSelectionEnd();
CharSequence charSequence = text.subSequence(start, end);
SpannableString replacement = new SpannableString(charSequence);
CharacterStyle style = null;
if (item.getItemId() == R.id.edittext_bold) {
style = MessageStyler.boldStyle();
} else if (item.getItemId() == R.id.edittext_italic) {
style = MessageStyler.italicStyle();
} else if (item.getItemId() == R.id.edittext_strikethrough) {
style = MessageStyler.strikethroughStyle();
} else if (item.getItemId() == R.id.edittext_monospace) {
style = MessageStyler.monoStyle();
}
if (style != null) {
replacement.setSpan(style, 0, charSequence.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
clearComposingText();
text.replace(start, end, replacement);
mode.finish();
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public void onDestroyActionMode(ActionMode mode) {}
});
}
}
private void setHintWithChecks(@Nullable CharSequence newHint) {

View File

@@ -268,7 +268,14 @@ public class InputPanel extends LinearLayout
public Optional<QuoteModel> getQuote() {
if (quoteView.getQuoteId() > 0 && quoteView.getVisibility() == View.VISIBLE) {
return Optional.of(new QuoteModel(quoteView.getQuoteId(), quoteView.getAuthor().getId(), quoteView.getBody().toString(), false, quoteView.getAttachments(), quoteView.getMentions(), quoteView.getQuoteType()));
return Optional.of(new QuoteModel(quoteView.getQuoteId(),
quoteView.getAuthor().getId(),
quoteView.getBody().toString(),
false,
quoteView.getAttachments(),
quoteView.getMentions(),
quoteView.getQuoteType(),
quoteView.getBodyRanges()));
} else {
return Optional.empty();
}

View File

@@ -29,7 +29,9 @@ import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.components.mention.MentionAnnotation;
import org.thoughtcrime.securesms.components.quotes.QuoteViewColorTheme;
import org.thoughtcrime.securesms.conversation.MessageStyler;
import org.thoughtcrime.securesms.database.model.Mention;
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.QuoteModel;
@@ -437,7 +439,7 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
}
try {
return StoryTextPostModel.parseFrom(body.toString(), id, author.getId());
return StoryTextPostModel.parseFrom(body.toString(), id, author.getId(), MessageStyler.getStyling(body));
} catch (IOException ioException) {
return null;
}
@@ -471,6 +473,10 @@ public class QuoteView extends FrameLayout implements RecipientForeverObserver {
return MentionAnnotation.getMentionsFromAnnotations(body);
}
public @Nullable BodyRangeList getBodyRanges() {
return MessageStyler.getStyling(body);
}
private @NonNull ShapeAppearanceModel buildShapeAppearanceForLayoutDirection() {
int fourDp = (int) DimensionUnit.DP.toPixels(4);
if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {