mirror of https://github.com/M66B/FairEmail.git
Improved text search
This commit is contained in:
parent
ef71112629
commit
b6b1b2a582
|
@ -55,18 +55,14 @@ import android.os.Parcelable;
|
|||
import android.provider.CalendarContract;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.Settings;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.method.ArrowKeyMovementMethod;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.DynamicDrawableSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ImageSpan;
|
||||
|
@ -75,7 +71,6 @@ import android.text.style.StyleSpan;
|
|||
import android.text.style.URLSpan;
|
||||
import android.util.Pair;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
|
@ -93,7 +88,6 @@ import android.view.accessibility.AccessibilityEvent;
|
|||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.textclassifier.ConversationAction;
|
||||
import android.view.textclassifier.ConversationActions;
|
||||
import android.webkit.WebSettings;
|
||||
|
@ -106,7 +100,6 @@ import android.widget.CompoundButton;
|
|||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
@ -196,7 +189,7 @@ import biweekly.property.RawProperty;
|
|||
import biweekly.util.ICalDate;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static androidx.webkit.WebSettingsCompat.FORCE_DARK_OFF;
|
||||
import static androidx.webkit.WebSettingsCompat.FORCE_DARK_ON;
|
||||
|
||||
|
@ -289,7 +282,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private boolean gotoTop = false;
|
||||
private Integer gotoPos = null;
|
||||
private boolean firstClick = false;
|
||||
private int searchResult = 0;
|
||||
private AsyncPagedListDiffer<TupleMessageEx> differ;
|
||||
private Map<Long, Integer> keyPosition = new HashMap<>();
|
||||
private Map<Integer, Long> positionKey = new HashMap<>();
|
||||
|
@ -2043,6 +2035,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
tvBody.setTag(message.id);
|
||||
tvBody.setText(null);
|
||||
}
|
||||
properties.endSearch();
|
||||
clearActions();
|
||||
|
||||
ibSeenBottom.setImageResource(message.ui_seen
|
||||
|
@ -4867,108 +4860,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
}
|
||||
|
||||
private void onSearchText(TupleMessageEx message) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
View dview = inflater.inflate(R.layout.popup_search_in_text, null, false);
|
||||
EditText etSearch = dview.findViewById(R.id.etSearch);
|
||||
ImageButton ibNext = dview.findViewById(R.id.ibNext);
|
||||
|
||||
etSearch.setText(null);
|
||||
ibNext.setEnabled(false);
|
||||
|
||||
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) {
|
||||
searchResult = find(s.toString(), 1);
|
||||
ibNext.setEnabled(searchResult > 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// Do nothing
|
||||
}
|
||||
});
|
||||
|
||||
ibNext.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
searchResult = find(etSearch.getText().toString(), ++searchResult);
|
||||
}
|
||||
});
|
||||
|
||||
PopupWindow pw = new PopupWindow(dview, WRAP_CONTENT, WRAP_CONTENT);
|
||||
pw.setFocusable(true);
|
||||
pw.setOnDismissListener(new PopupWindow.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
SpannableString ss = new SpannableString(tvBody.getText());
|
||||
for (BackgroundColorSpan span : ss.getSpans(0, ss.length(), BackgroundColorSpan.class))
|
||||
ss.removeSpan(span);
|
||||
tvBody.setText(ss);
|
||||
}
|
||||
});
|
||||
pw.showAtLocation(parentFragment.getView(), Gravity.TOP | Gravity.END, 0, 0);
|
||||
|
||||
final InputMethodManager imm =
|
||||
(InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null)
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
|
||||
}
|
||||
|
||||
private int find(String query, int result) {
|
||||
query = query.toLowerCase();
|
||||
|
||||
SpannableString ss = new SpannableString(tvBody.getText());
|
||||
for (BackgroundColorSpan span : ss.getSpans(0, ss.length(), BackgroundColorSpan.class))
|
||||
ss.removeSpan(span);
|
||||
|
||||
int p = -1;
|
||||
String text = tvBody.getText().toString().toLowerCase();
|
||||
for (int i = 0; i < result; i++)
|
||||
p = (p < 0 ? text.indexOf(query) : text.indexOf(query, p + 1));
|
||||
|
||||
if (p < 0 && result > 1) {
|
||||
result = 1;
|
||||
p = text.indexOf(query);
|
||||
}
|
||||
if (p < 0)
|
||||
result = 0;
|
||||
|
||||
final int pos = p;
|
||||
if (pos > 0) {
|
||||
int color = Helper.resolveColor(context, R.attr.colorHighlight);
|
||||
ss.setSpan(new BackgroundColorSpan(color), pos, pos + query.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
tvBody.setText(ss);
|
||||
|
||||
final int apos = getAdapterPosition();
|
||||
|
||||
tvBody.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
int line = tvBody.getLayout().getLineForOffset(pos);
|
||||
int y = tvBody.getLayout().getLineTop(line);
|
||||
|
||||
int dy = Helper.dp2pixels(context, 48);
|
||||
|
||||
Rect rect = new Rect();
|
||||
tvBody.getDrawingRect(rect);
|
||||
((ViewGroup) itemView).offsetDescendantRectToMyCoords(tvBody, rect);
|
||||
|
||||
properties.scrollTo(apos, rect.top + y - dy);
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else
|
||||
tvBody.setText(ss, TextView.BufferType.SPANNABLE);
|
||||
|
||||
return result;
|
||||
properties.startSearch(tvBody);
|
||||
}
|
||||
|
||||
private void onMenuCreateRule(TupleMessageEx message) {
|
||||
|
@ -6496,6 +6388,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
|
||||
void reply(TupleMessageEx message, String selected, View anchor);
|
||||
|
||||
void startSearch(TextView view);
|
||||
|
||||
void endSearch();
|
||||
|
||||
void lock(long id);
|
||||
|
||||
void refresh();
|
||||
|
@ -7040,9 +6936,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
super.onStart();
|
||||
Dialog dialog = getDialog();
|
||||
if (dialog != null)
|
||||
dialog.getWindow().setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
dialog.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -59,10 +59,14 @@ import android.provider.ContactsContract;
|
|||
import android.provider.Settings;
|
||||
import android.security.KeyChain;
|
||||
import android.security.KeyChainException;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.format.DateUtils;
|
||||
import android.text.style.BackgroundColorSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
|
@ -82,6 +86,8 @@ import android.view.ViewGroup;
|
|||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
@ -243,6 +249,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
private ImageButton ibHintSupport;
|
||||
private ImageButton ibHintSwipe;
|
||||
private ImageButton ibHintSelect;
|
||||
private TextViewAutoCompleteAction etSearch;
|
||||
private TextView tvNoEmail;
|
||||
private TextView tvNoEmailHint;
|
||||
private FixedRecyclerView rvMessage;
|
||||
|
@ -283,6 +290,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
private BoundaryCallbackMessages.SearchCriteria criteria = null;
|
||||
private boolean pane;
|
||||
|
||||
private int searchIndex = 0;
|
||||
private TextView searchView = null;
|
||||
|
||||
private WebView printWebView = null;
|
||||
|
||||
private OpenPgpServiceConnection pgpService;
|
||||
|
@ -462,6 +472,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
ibHintSupport = view.findViewById(R.id.ibHintSupport);
|
||||
ibHintSwipe = view.findViewById(R.id.ibHintSwipe);
|
||||
ibHintSelect = view.findViewById(R.id.ibHintSelect);
|
||||
etSearch = view.findViewById(R.id.etSearch);
|
||||
tvNoEmail = view.findViewById(R.id.tvNoEmail);
|
||||
tvNoEmailHint = view.findViewById(R.id.tvNoEmailHint);
|
||||
rvMessage = view.findViewById(R.id.rvMessage);
|
||||
|
@ -537,6 +548,49 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
}
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
});
|
||||
|
||||
rvMessage.setHasFixedSize(false);
|
||||
|
||||
int threads = prefs.getInt("query_threads", 4);
|
||||
|
@ -1147,6 +1201,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
FragmentDialogTheme.setBackground(getContext(), view, false);
|
||||
tvNoEmail.setVisibility(View.GONE);
|
||||
tvNoEmailHint.setVisibility(View.GONE);
|
||||
etSearch.setVisibility(View.GONE);
|
||||
sbThread.setVisibility(View.GONE);
|
||||
ibDown.setVisibility(View.GONE);
|
||||
ibUp.setVisibility(View.GONE);
|
||||
|
@ -1857,6 +1912,14 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
onReply(message, selected, anchor);
|
||||
}
|
||||
|
||||
public void startSearch(TextView view) {
|
||||
FragmentMessages.this.startSearch(view);
|
||||
}
|
||||
|
||||
public void endSearch() {
|
||||
FragmentMessages.this.endSearch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lock(long id) {
|
||||
Bundle args = new Bundle();
|
||||
|
@ -5784,6 +5847,89 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
}
|
||||
}
|
||||
|
||||
private void startSearch(TextView view) {
|
||||
searchView = view;
|
||||
|
||||
etSearch.setText(null);
|
||||
etSearch.setVisibility(View.VISIBLE);
|
||||
etSearch.requestFocus();
|
||||
|
||||
InputMethodManager imm =
|
||||
(InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null)
|
||||
imm.showSoftInput(etSearch, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
private void endSearch() {
|
||||
Helper.hideKeyboard(etSearch);
|
||||
etSearch.setVisibility(View.GONE);
|
||||
clearSearch();
|
||||
searchView = null;
|
||||
}
|
||||
|
||||
private void performSearch(boolean next) {
|
||||
clearSearch();
|
||||
|
||||
if (searchView == null)
|
||||
return;
|
||||
|
||||
searchIndex = (next ? searchIndex + 1 : 1);
|
||||
String query = etSearch.getText().toString().toLowerCase();
|
||||
String text = searchView.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) {
|
||||
int color = Helper.resolveColor(searchView.getContext(), R.attr.colorHighlight);
|
||||
SpannableString ss = new SpannableString(searchView.getText());
|
||||
ss.setSpan(new BackgroundColorSpan(color),
|
||||
pos, pos + query.length(),
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE | Spannable.SPAN_COMPOSING);
|
||||
searchView.setText(ss);
|
||||
|
||||
int line = searchView.getLayout().getLineForOffset(pos);
|
||||
int y = searchView.getLayout().getLineTop(line);
|
||||
int dy = searchView.getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.search_in_text_margin);
|
||||
|
||||
View itemView = rvMessage.findContainingItemView(searchView);
|
||||
if (itemView != null) {
|
||||
Rect rect = new Rect();
|
||||
searchView.getDrawingRect(rect);
|
||||
|
||||
RecyclerView.ViewHolder holder = rvMessage.getChildViewHolder(itemView);
|
||||
((ViewGroup) itemView).offsetDescendantRectToMyCoords(searchView, rect);
|
||||
|
||||
iProperties.scrollTo(holder.getAdapterPosition(), rect.top + y - dy);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasNext = (pos >= 0 &&
|
||||
(text.indexOf(query) != pos ||
|
||||
text.indexOf(query, pos + 1) >= 0));
|
||||
etSearch.setActionEnabled(hasNext);
|
||||
}
|
||||
|
||||
private void clearSearch() {
|
||||
if (searchView == null)
|
||||
return;
|
||||
|
||||
SpannableString ss = new SpannableString(searchView.getText());
|
||||
for (BackgroundColorSpan span : ss.getSpans(0, ss.length(), BackgroundColorSpan.class))
|
||||
if ((ss.getSpanFlags(span) & Spannable.SPAN_COMPOSING) != 0)
|
||||
ss.removeSpan(span);
|
||||
searchView.setText(ss);
|
||||
}
|
||||
|
||||
private ActivityBase.IKeyPressedListener onBackPressedListener = new ActivityBase.IKeyPressedListener() {
|
||||
@Override
|
||||
public boolean onKeyPressed(KeyEvent event) {
|
||||
|
|
|
@ -135,6 +135,22 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvHintSelect" />
|
||||
|
||||
<eu.faircode.email.TextViewAutoCompleteAction
|
||||
android:id="@+id/etSearch"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSeparator"
|
||||
android:completionThreshold="2"
|
||||
android:hint="@string/title_search_for_hint"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:padding="6dp"
|
||||
app:end_drawable="@drawable/twotone_fast_forward_24"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorHintSelect" />
|
||||
|
||||
<eu.faircode.email.ViewTextDelayed
|
||||
android:id="@+id/tvNoEmail"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -173,7 +189,7 @@
|
|||
app:layout_constraintBottom_toTopOf="@+id/sbThread"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorHintSelect" />
|
||||
app:layout_constraintTop_toBottomOf="@id/etSearch" />
|
||||
|
||||
<View
|
||||
android:id="@+id/vwAnchor"
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSeparator"
|
||||
android:padding="12dp">
|
||||
|
||||
<eu.faircode.email.EditTextPlain
|
||||
android:id="@+id/etSearch"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:text="Search"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<requestFocus />
|
||||
</eu.faircode.email.EditTextPlain>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ibNext"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintBottom_toBottomOf="@id/etSearch"
|
||||
app:layout_constraintStart_toEndOf="@+id/etSearch"
|
||||
app:layout_constraintTop_toTopOf="@+id/etSearch"
|
||||
app:srcCompat="@drawable/twotone_fast_forward_24" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -11,4 +11,5 @@
|
|||
<dimen name="quote_gap_size">6dp</dimen>
|
||||
<dimen name="quote_stripe_width">3dp</dimen>
|
||||
<dimen name="line_dash_length">3dp</dimen>
|
||||
<dimen name="search_in_text_margin">48dp</dimen>
|
||||
</resources>
|
Loading…
Reference in New Issue