From e887264890586b30489cea82fb7bd481be159266 Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 8 Jun 2022 08:40:29 +0200 Subject: [PATCH] Added search in draft --- .../eu/faircode/email/FragmentCompose.java | 130 ++++++++++++++++++ app/src/main/res/layout/fragment_compose.xml | 19 ++- app/src/main/res/menu/menu_compose.xml | 6 + 3 files changed, 154 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 7d9a358479..48eb9497e9 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -49,6 +49,7 @@ import android.graphics.Color; import android.graphics.ImageDecoder; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.net.ConnectivityManager; @@ -68,7 +69,9 @@ import android.security.KeyChain; import android.system.ErrnoException; import android.text.Editable; import android.text.Html; +import android.text.Layout; import android.text.Spannable; +import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; @@ -96,6 +99,7 @@ import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; import android.webkit.MimeTypeMap; import android.widget.AdapterView; import android.widget.Button; @@ -107,6 +111,7 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.MultiAutoCompleteTextView; import android.widget.RadioGroup; +import android.widget.ScrollView; import android.widget.Spinner; import android.widget.SpinnerAdapter; import android.widget.TextView; @@ -265,6 +270,7 @@ public class FragmentCompose extends FragmentBase { private ImageButton ibReferenceEdit; private ImageButton ibReferenceImages; private View vwAnchor; + private TextViewAutoCompleteAction etSearch; private BottomNavigationView style_bar; private BottomNavigationView media_bar; private BottomNavigationView bottom_navigation; @@ -306,6 +312,8 @@ public class FragmentCompose extends FragmentBase { private long[] pgpKeyIds; private long pgpSignKeyId; + private int searchIndex = 0; + private static final int REDUCED_IMAGE_SIZE = 1440; // pixels private static final int REDUCED_IMAGE_QUALITY = 90; // percent // http://regex.info/blog/lightroom-goodies/jpeg-quality @@ -384,6 +392,7 @@ public class FragmentCompose extends FragmentBase { ibReferenceEdit = view.findViewById(R.id.ibReferenceEdit); ibReferenceImages = view.findViewById(R.id.ibReferenceImages); vwAnchor = view.findViewById(R.id.vwAnchor); + etSearch = view.findViewById(R.id.etSearch); style_bar = view.findViewById(R.id.style_bar); media_bar = view.findViewById(R.id.media_bar); bottom_navigation = view.findViewById(R.id.bottom_navigation); @@ -947,6 +956,49 @@ public class FragmentCompose extends FragmentBase { } }); + etSearch.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) + endSearch(); + } + }); + + etSearch.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) { + endSearch(); + return true; + } else + return false; + } + }); + + etSearch.setActionRunnable(new Runnable() { + @Override + public void run() { + performSearch(true); + } + }); + + etSearch.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Do nothing + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + performSearch(false); + } + + @Override + public void afterTextChanged(Editable s) { + // Do nothing + } + }); + style_bar.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { @@ -1036,6 +1088,7 @@ public class FragmentCompose extends FragmentBase { ibReferenceEdit.setVisibility(View.GONE); ibReferenceImages.setVisibility(View.GONE); tvReference.setVisibility(View.GONE); + etSearch.setVisibility(View.GONE); style_bar.setVisibility(View.GONE); media_bar.setVisibility(View.GONE); bottom_navigation.setVisibility(View.GONE); @@ -1891,6 +1944,9 @@ public class FragmentCompose extends FragmentBase { } else if (itemId == R.id.menu_answer_create) { onMenuAnswerCreate(); return true; + } else if (itemId == R.id.title_search_in_text) { + startSearch(); + return true; } else if (itemId == R.id.menu_clear) { StyleHelper.apply(R.id.menu_clear, getViewLifecycleOwner(), null, etBody); return true; @@ -6925,6 +6981,80 @@ public class FragmentCompose extends FragmentBase { return -1; } + private void startSearch() { + etSearch.setText(null); + etSearch.setVisibility(View.VISIBLE); + etSearch.requestFocus(); + Helper.showKeyboard(etSearch); + } + + private void endSearch() { + Helper.hideKeyboard(etSearch); + etSearch.setVisibility(View.GONE); + clearSearch(); + } + + private void performSearch(boolean next) { + clearSearch(); + + searchIndex = (next ? searchIndex + 1 : 1); + String query = etSearch.getText().toString().toLowerCase(); + String text = etBody.getText().toString().toLowerCase(); + + int pos = -1; + for (int i = 0; i < searchIndex; i++) + pos = (pos < 0 ? text.indexOf(query) : text.indexOf(query, pos + 1)); + + // Wrap around + if (pos < 0 && searchIndex > 1) { + searchIndex = 1; + pos = text.indexOf(query); + } + + // Scroll to found text + if (pos >= 0) { + Context context = etBody.getContext(); + int color = Helper.resolveColor(context, R.attr.colorHighlight); + SpannableString ss = new SpannableString(etBody.getText()); + ss.setSpan(new BackgroundColorSpan(color), + pos, pos + query.length(), Spannable.SPAN_COMPOSING); + ss.setSpan(new RelativeSizeSpan(HtmlHelper.FONT_LARGE), + pos, pos + query.length(), Spannable.SPAN_COMPOSING); + etBody.setText(ss); + + Layout layout = etBody.getLayout(); + if (layout != null) { + int line = layout.getLineForOffset(pos); + int y = layout.getLineTop(line); + int dy = context.getResources().getDimensionPixelSize(R.dimen.search_in_text_margin); + + Rect rect = new Rect(); + etBody.getDrawingRect(rect); + ScrollView scroll = view.findViewById(R.id.scroll); + scroll.offsetDescendantRectToMyCoords(etBody, rect); + scroll.post(new Runnable() { + @Override + public void run() { + try { + scroll.scrollTo(0, rect.top + y - dy); + } catch (Throwable ex) { + Log.e(ex); + } + } + }); + } + } + + boolean hasNext = (pos >= 0 && + (text.indexOf(query) != pos || + text.indexOf(query, pos + 1) >= 0)); + etSearch.setActionEnabled(hasNext); + } + + private void clearSearch() { + etBody.clearComposingText(); + } + private AdapterView.OnItemSelectedListener identitySelected = new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { diff --git a/app/src/main/res/layout/fragment_compose.xml b/app/src/main/res/layout/fragment_compose.xml index 58420e3169..e2a7dc1394 100644 --- a/app/src/main/res/layout/fragment_compose.xml +++ b/app/src/main/res/layout/fragment_compose.xml @@ -19,6 +19,7 @@ app:layout_constraintTop_toTopOf="parent" /> + + + +