Allow formatting text from overflow menu.

This commit is contained in:
Cody Henthorne
2023-06-21 16:16:16 -04:00
committed by GitHub
parent 15035f4eb3
commit 882748f080
6 changed files with 160 additions and 69 deletions

View File

@@ -8,6 +8,7 @@ import android.os.Bundle;
import android.text.Annotation;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
@@ -22,6 +23,7 @@ import android.view.MenuItem;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
@@ -338,48 +340,11 @@ public class ComposeText extends EmojiEditText {
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Editable text = getText();
if (text == null) {
return false;
boolean handled = handleFormatText(item.getItemId());
if (handled) {
mode.finish();
}
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 &&
item.getItemId() != R.id.edittext_spoiler &&
item.getItemId() != R.id.edittext_clear_formatting)
{
return false;
}
int start = getSelectionStart();
int end = getSelectionEnd();
BodyRangeList.BodyRange.Style style = null;
if (item.getItemId() == R.id.edittext_bold) {
style = BodyRangeList.BodyRange.Style.BOLD;
} else if (item.getItemId() == R.id.edittext_italic) {
style = BodyRangeList.BodyRange.Style.ITALIC;
} else if (item.getItemId() == R.id.edittext_strikethrough) {
style = BodyRangeList.BodyRange.Style.STRIKETHROUGH;
} else if (item.getItemId() == R.id.edittext_monospace) {
style = BodyRangeList.BodyRange.Style.MONOSPACE;
} else if (item.getItemId() == R.id.edittext_spoiler) {
style = BodyRangeList.BodyRange.Style.SPOILER;
}
clearComposingText();
if (style != null) {
MessageStyler.toggleStyle(style, text, start, end);
} else if (item.getItemId() == R.id.edittext_clear_formatting) {
MessageStyler.clearStyling(text, start, end);
}
mode.finish();
return true;
return handled;
}
@Override
@@ -567,6 +532,56 @@ public class ComposeText extends EmojiEditText {
return TIME_PATTERN.matcher(text.subSequence(startOfToken, endOfToken)).find();
}
public boolean isTextHighlighted() {
return getText() != null && getSelectionStart() < getSelectionEnd();
}
public boolean handleFormatText(@IdRes int id) {
Editable text = getText();
if (text == null) {
return false;
}
if (id != R.id.edittext_bold &&
id != R.id.edittext_italic &&
id != R.id.edittext_strikethrough &&
id != R.id.edittext_monospace &&
id != R.id.edittext_spoiler &&
id != R.id.edittext_clear_formatting)
{
return false;
}
int start = getSelectionStart();
int end = getSelectionEnd();
BodyRangeList.BodyRange.Style style = null;
if (id == R.id.edittext_bold) {
style = BodyRangeList.BodyRange.Style.BOLD;
} else if (id == R.id.edittext_italic) {
style = BodyRangeList.BodyRange.Style.ITALIC;
} else if (id == R.id.edittext_strikethrough) {
style = BodyRangeList.BodyRange.Style.STRIKETHROUGH;
} else if (id == R.id.edittext_monospace) {
style = BodyRangeList.BodyRange.Style.MONOSPACE;
} else if (id == R.id.edittext_spoiler) {
style = BodyRangeList.BodyRange.Style.SPOILER;
}
clearComposingText();
if (style != null) {
MessageStyler.toggleStyle(style, text, start, end);
} else {
MessageStyler.clearStyling(text, start, end);
}
Selection.setSelection(getText(), end);
return true;
}
private static class CommitContentListener implements InputConnectionCompat.OnCommitContentListener {
private static final String TAG = Log.tag(CommitContentListener.class);

View File

@@ -5,6 +5,7 @@
package org.thoughtcrime.securesms.conversation
import android.text.SpannableString
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
@@ -165,9 +166,23 @@ internal object ConversationOptionsMenu {
hideMenuItem(menu, R.id.menu_view_media)
}
menu.findItem(R.id.menu_format_text_submenu).subMenu?.clearHeader()
menu.findItem(R.id.edittext_bold).applyTitleSpan(MessageStyler.boldStyle())
menu.findItem(R.id.edittext_italic).applyTitleSpan(MessageStyler.italicStyle())
menu.findItem(R.id.edittext_strikethrough).applyTitleSpan(MessageStyler.strikethroughStyle())
menu.findItem(R.id.edittext_monospace).applyTitleSpan(MessageStyler.monoStyle())
callback.onOptionsMenuCreated(menu)
}
override fun onPrepareMenu(menu: Menu) {
super.onPrepareMenu(menu)
val formatText = menu.findItem(R.id.menu_format_text_submenu)
if (formatText != null) {
formatText.isVisible = callback.isTextHighlighted()
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.menu_call_secure -> callback.handleDial(true)
@@ -189,6 +204,12 @@ internal object ConversationOptionsMenu {
R.id.menu_expiring_messages_off, R.id.menu_expiring_messages -> callback.handleSelectMessageExpiration()
R.id.menu_create_bubble -> callback.handleCreateBubble()
R.id.home -> callback.handleGoHome()
R.id.edittext_bold,
R.id.edittext_italic,
R.id.edittext_strikethrough,
R.id.edittext_monospace,
R.id.edittext_spoiler,
R.id.edittext_clear_formatting -> callback.handleFormatText(menuItem.itemId)
else -> return false
}
@@ -200,6 +221,10 @@ internal object ConversationOptionsMenu {
menu.findItem(menuItem).isVisible = false
}
}
private fun MenuItem.applyTitleSpan(span: Any) {
title = SpannableString(title).apply { setSpan(span, 0, length, MessageStyler.SPAN_FLAGS) }
}
}
/**
@@ -224,6 +249,7 @@ internal object ConversationOptionsMenu {
*/
interface Callback {
fun getSnapshot(): Snapshot
fun isTextHighlighted(): Boolean
fun onOptionsMenuCreated(menu: Menu)
@@ -248,5 +274,6 @@ internal object ConversationOptionsMenu {
fun showExpiring(recipient: Recipient)
fun clearExpiring()
fun showGroupCallingTooltip()
fun handleFormatText(@IdRes id: Int)
}
}

View File

@@ -472,8 +472,6 @@ public class ConversationParentFragment extends Fragment
private Callback callback;
private RecentEmojiPageModel recentEmojis;
private ConversationOptionsMenu.Provider menuProvider;
private Set<KeyboardPage> previousPages;
public static ConversationParentFragment create(Intent intent) {
@@ -494,7 +492,6 @@ public class ConversationParentFragment extends Fragment
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
disposables.bindTo(getViewLifecycleOwner());
menuProvider = new ConversationOptionsMenu.Provider(this, disposables);
SpoilerAnnotation.resetRevealedSpoilers();
if (requireActivity() instanceof Callback) {
@@ -575,10 +572,6 @@ public class ConversationParentFragment extends Fragment
};
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), backPressedCallback);
if (isSearchRequested && savedInstanceState == null) {
menuProvider.onCreateMenu(toolbar.getMenu(), requireActivity().getMenuInflater());
}
sendButton.post(() -> sendButton.triggerSelectedChangedEvent());
}
@@ -990,7 +983,7 @@ public class ConversationParentFragment extends Fragment
if (!isSearchRequested && getActivity() != null) {
optionsMenuDebouncer.publish(() -> {
if (getActivity() != null) {
menuProvider.onCreateMenu(toolbar.getMenu(), requireActivity().getMenuInflater());
toolbar.invalidateMenu();
}
});
}
@@ -2108,8 +2101,8 @@ public class ConversationParentFragment extends Fragment
}
protected void initializeActionBar() {
toolbar.addMenuProvider(new ConversationOptionsMenu.Provider(this, disposables));
invalidateOptionsMenu();
toolbar.setOnMenuItemClickListener(menuProvider::onMenuItemSelected);
toolbar.setNavigationContentDescription(R.string.ConversationFragment__content_description_back_button);
if (isInBubble()) {
toolbar.setNavigationIcon(DrawableUtil.tint(ContextUtil.requireDrawable(requireContext(), R.drawable.ic_notification),
@@ -2370,6 +2363,11 @@ public class ConversationParentFragment extends Fragment
.show(TooltipPopup.POSITION_BELOW);
}
@Override
public void handleFormatText(@IdRes int id) {
composeText.handleFormatText(id);
}
private void showStickerIntroductionTooltip() {
TextSecurePreferences.setMediaKeyboardMode(requireContext(), MediaKeyboardMode.STICKER);
inputPanel.setMediaKeyboardToggleMode(KeyboardPage.STICKER);
@@ -3679,6 +3677,11 @@ public class ConversationParentFragment extends Fragment
);
}
@Override
public boolean isTextHighlighted() {
return composeText.isTextHighlighted();
}
@Override
public void showExpiring(@NonNull Recipient recipient) {
titleView.showExpiring(recipient);

View File

@@ -330,7 +330,6 @@ class ConversationFragment :
private val colorizer = Colorizer()
private val textDraftSaveDebouncer = Debouncer(500)
private lateinit var conversationOptionsMenuProvider: ConversationOptionsMenu.Provider
private lateinit var layoutManager: LinearLayoutManager
private lateinit var markReadHelper: MarkReadHelper
private lateinit var giphyMp4ProjectionRecycler: GiphyMp4ProjectionRecycler
@@ -396,7 +395,6 @@ class ConversationFragment :
disposables.bindTo(viewLifecycleOwner)
FullscreenHelper(requireActivity()).showSystemUI()
conversationOptionsMenuProvider = ConversationOptionsMenu.Provider(ConversationOptionsMenuCallback(), disposables)
markReadHelper = MarkReadHelper(ConversationId.forConversation(args.threadId), requireContext(), viewLifecycleOwner)
initializeConversationThreadUi()
@@ -777,12 +775,13 @@ class ConversationFragment :
}
private fun invalidateOptionsMenu() {
if (!isSearchRequested && activity != null) {
conversationOptionsMenuProvider.onCreateMenu(binding.toolbar.menu, requireActivity().menuInflater)
if (!isSearchRequested) {
binding.toolbar.invalidateMenu()
}
}
private fun presentActionBarMenu() {
binding.toolbar.addMenuProvider(ConversationOptionsMenu.Provider(ConversationOptionsMenuCallback(), disposables))
invalidateOptionsMenu()
when (args.conversationScreenType) {
@@ -790,8 +789,6 @@ class ConversationFragment :
ConversationScreenType.BUBBLE -> presentNavigationIconForBubble()
ConversationScreenType.POPUP -> Unit
}
binding.toolbar.setOnMenuItemClickListener(conversationOptionsMenuProvider::onMenuItemSelected)
}
private fun presentNavigationIconForNormal() {
@@ -2109,6 +2106,10 @@ class ConversationFragment :
)
}
override fun isTextHighlighted(): Boolean {
return composeText.isTextHighlighted
}
override fun onOptionsMenuCreated(menu: Menu) {
searchMenuItem = menu.findItem(R.id.menu_search)
@@ -2337,6 +2338,10 @@ class ConversationFragment :
override fun showGroupCallingTooltip() {
conversationTooltips.displayGroupCallingTooltip(requireView().findViewById(R.id.menu_video_secure))
}
override fun handleFormatText(id: Int) {
composeText.handleFormatText(id)
}
}
private inner class OnReactionsSelectedListener : ConversationReactionOverlay.OnReactionSelectedListener {