mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-11-23 13:26:15 +03:00
#550 In-note-search doesn't jump to occurrence of searchstring
Divide et impera
This commit is contained in:
parent
2b9cfc2704
commit
610eab570d
4 changed files with 230 additions and 206 deletions
|
@ -10,24 +10,16 @@ import android.graphics.drawable.Icon;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.SearchView;
|
|
||||||
import androidx.appcompat.widget.ShareActionProvider;
|
import androidx.appcompat.widget.ShareActionProvider;
|
||||||
import androidx.core.view.MenuItemCompat;
|
import androidx.core.view.MenuItemCompat;
|
||||||
import androidx.core.view.ViewCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
|
||||||
|
@ -45,7 +37,6 @@ import it.niedermann.owncloud.notes.model.CloudNote;
|
||||||
import it.niedermann.owncloud.notes.model.DBNote;
|
import it.niedermann.owncloud.notes.model.DBNote;
|
||||||
import it.niedermann.owncloud.notes.model.LocalAccount;
|
import it.niedermann.owncloud.notes.model.LocalAccount;
|
||||||
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
|
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
|
||||||
import it.niedermann.owncloud.notes.util.DisplayUtils;
|
|
||||||
import it.niedermann.owncloud.notes.util.ICallback;
|
import it.niedermann.owncloud.notes.util.ICallback;
|
||||||
|
|
||||||
import static androidx.core.content.pm.ShortcutManagerCompat.isRequestPinShortcutSupported;
|
import static androidx.core.content.pm.ShortcutManagerCompat.isRequestPinShortcutSupported;
|
||||||
|
@ -62,11 +53,6 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
|
||||||
private static final String SAVEDKEY_NOTE = "note";
|
private static final String SAVEDKEY_NOTE = "note";
|
||||||
private static final String SAVEDKEY_ORIGINAL_NOTE = "original_note";
|
private static final String SAVEDKEY_ORIGINAL_NOTE = "original_note";
|
||||||
|
|
||||||
protected SearchView searchView;
|
|
||||||
protected MenuItem searchMenuItem;
|
|
||||||
|
|
||||||
protected String searchQuery = null;
|
|
||||||
|
|
||||||
private LocalAccount localAccount;
|
private LocalAccount localAccount;
|
||||||
|
|
||||||
protected DBNote note;
|
protected DBNote note;
|
||||||
|
@ -75,21 +61,7 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
|
||||||
protected NoteSQLiteOpenHelper db;
|
protected NoteSQLiteOpenHelper db;
|
||||||
private NoteFragmentListener listener;
|
private NoteFragmentListener listener;
|
||||||
|
|
||||||
private TextView activeTextView;
|
boolean isNew = true;
|
||||||
private boolean isNew = true;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
searchQuery = savedInstanceState.getString("searchQuery", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setActiveTextView(TextView textView) {
|
|
||||||
activeTextView = textView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
@ -167,18 +139,6 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
|
||||||
saveNote(null);
|
saveNote(null);
|
||||||
outState.putSerializable(SAVEDKEY_NOTE, note);
|
outState.putSerializable(SAVEDKEY_NOTE, note);
|
||||||
outState.putSerializable(SAVEDKEY_ORIGINAL_NOTE, originalNote);
|
outState.putSerializable(SAVEDKEY_ORIGINAL_NOTE, originalNote);
|
||||||
|
|
||||||
if (searchView != null && !TextUtils.isEmpty(searchView.getQuery().toString())) {
|
|
||||||
outState.putString("searchQuery", searchView.getQuery().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void colorWithText(String newText) {
|
|
||||||
if (activeTextView != null && ViewCompat.isAttachedToWindow(activeTextView)) {
|
|
||||||
activeTextView.setText(DisplayUtils.searchAndColor(activeTextView.getText().toString(), new SpannableString
|
|
||||||
(activeTextView.getText()), newText, getResources().getColor(R.color.primary)),
|
|
||||||
TextView.BufferType.SPANNABLE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -190,9 +150,6 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int currentOccurrence = 1;
|
|
||||||
private int occurrenceCount = 0;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||||
super.onPrepareOptionsMenu(menu);
|
super.onPrepareOptionsMenu(menu);
|
||||||
|
@ -200,166 +157,6 @@ public abstract class BaseNoteFragment extends Fragment implements CategoryDialo
|
||||||
prepareFavoriteOption(itemFavorite);
|
prepareFavoriteOption(itemFavorite);
|
||||||
|
|
||||||
menu.findItem(R.id.menu_delete).setVisible(!isNew);
|
menu.findItem(R.id.menu_delete).setVisible(!isNew);
|
||||||
|
|
||||||
searchMenuItem = menu.findItem(R.id.search);
|
|
||||||
searchView = (SearchView) searchMenuItem.getActionView();
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(searchQuery) && isNew) {
|
|
||||||
searchMenuItem.expandActionView();
|
|
||||||
searchView.setQuery(searchQuery, true);
|
|
||||||
searchView.clearFocus();
|
|
||||||
} else {
|
|
||||||
searchMenuItem.collapseActionView();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final LinearLayout searchEditFrame = searchView.findViewById(R.id
|
|
||||||
.search_edit_frame);
|
|
||||||
|
|
||||||
searchEditFrame.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
|
||||||
int oldVisibility = -1;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onGlobalLayout() {
|
|
||||||
int currentVisibility = searchEditFrame.getVisibility();
|
|
||||||
|
|
||||||
if (currentVisibility != oldVisibility) {
|
|
||||||
if (currentVisibility != View.VISIBLE) {
|
|
||||||
colorWithText("");
|
|
||||||
searchQuery = "";
|
|
||||||
hideSearchFabs();
|
|
||||||
} else {
|
|
||||||
showSearchFabs();
|
|
||||||
}
|
|
||||||
|
|
||||||
oldVisibility = currentVisibility;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
FloatingActionButton next = getSearchNextButton();
|
|
||||||
FloatingActionButton prev = getSearchPrevButton();
|
|
||||||
|
|
||||||
if (next != null) {
|
|
||||||
next.setOnClickListener(v -> {
|
|
||||||
currentOccurrence++;
|
|
||||||
jumpToOccurrence();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prev != null) {
|
|
||||||
prev.setOnClickListener(v -> {
|
|
||||||
currentOccurrence--;
|
|
||||||
jumpToOccurrence();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(String query) {
|
|
||||||
currentOccurrence++;
|
|
||||||
jumpToOccurrence();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(String newText) {
|
|
||||||
searchQuery = newText;
|
|
||||||
colorWithText(newText);
|
|
||||||
occurrenceCount = countOccurrences(getContent(), searchQuery);
|
|
||||||
if(occurrenceCount > 1) {
|
|
||||||
showSearchFabs();
|
|
||||||
} else {
|
|
||||||
hideSearchFabs();
|
|
||||||
}
|
|
||||||
currentOccurrence = 1;
|
|
||||||
jumpToOccurrence();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void hideSearchFabs() {
|
|
||||||
FloatingActionButton next = getSearchNextButton();
|
|
||||||
FloatingActionButton prev = getSearchPrevButton();
|
|
||||||
if (prev != null) {
|
|
||||||
prev.hide();
|
|
||||||
}
|
|
||||||
if (next != null) {
|
|
||||||
next.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showSearchFabs() {
|
|
||||||
FloatingActionButton next = getSearchNextButton();
|
|
||||||
FloatingActionButton prev = getSearchPrevButton();
|
|
||||||
if (prev != null) {
|
|
||||||
prev.show();
|
|
||||||
}
|
|
||||||
if (next != null) {
|
|
||||||
next.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void jumpToOccurrence() {
|
|
||||||
if (searchQuery == null || searchQuery.isEmpty()) {
|
|
||||||
// No search term
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (currentOccurrence < 1) {
|
|
||||||
// if currentOccurrence is lower than 1, jump to last occurrence
|
|
||||||
currentOccurrence = occurrenceCount;
|
|
||||||
jumpToOccurrence();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String currentContent = getContent().toLowerCase();
|
|
||||||
int indexOfNewText = indexOfNth(currentContent, searchQuery.toLowerCase(), 0, currentOccurrence);
|
|
||||||
if (indexOfNewText <= 0) {
|
|
||||||
// Search term is not n times in text
|
|
||||||
// Go back to first search result
|
|
||||||
if (currentOccurrence != 1) {
|
|
||||||
currentOccurrence = 1;
|
|
||||||
jumpToOccurrence();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String textUntilFirstOccurrence = currentContent.substring(0, indexOfNewText);
|
|
||||||
int numberLine = getLayout().getLineForOffset(textUntilFirstOccurrence.length());
|
|
||||||
|
|
||||||
if (numberLine >= 0) {
|
|
||||||
getScrollView().smoothScrollTo(0, getLayout().getLineTop(numberLine));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int indexOfNth(String input, String value, int startIndex, int nth) {
|
|
||||||
if (nth < 1)
|
|
||||||
throw new IllegalArgumentException("Param 'nth' must be greater than 0!");
|
|
||||||
if (nth == 1)
|
|
||||||
return input.indexOf(value, startIndex);
|
|
||||||
int idx = input.indexOf(value, startIndex);
|
|
||||||
if (idx == -1)
|
|
||||||
return -1;
|
|
||||||
return indexOfNth(input, value, idx + 1, --nth);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int countOccurrences(String haystack, String needle) {
|
|
||||||
if(haystack == null || haystack.isEmpty() || needle == null || needle.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
haystack = haystack.toLowerCase();
|
|
||||||
needle = needle.toLowerCase();
|
|
||||||
int lastIndex = 0;
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
while (lastIndex != -1) {
|
|
||||||
lastIndex = haystack.indexOf(needle, lastIndex);
|
|
||||||
if (lastIndex != -1) {
|
|
||||||
count++;
|
|
||||||
lastIndex += needle.length();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract ScrollView getScrollView();
|
protected abstract ScrollView getScrollView();
|
||||||
|
|
|
@ -39,7 +39,7 @@ import it.niedermann.owncloud.notes.util.MarkDownUtil;
|
||||||
import it.niedermann.owncloud.notes.util.NotesTextWatcher;
|
import it.niedermann.owncloud.notes.util.NotesTextWatcher;
|
||||||
import it.niedermann.owncloud.notes.util.StyleCallback;
|
import it.niedermann.owncloud.notes.util.StyleCallback;
|
||||||
|
|
||||||
public class NoteEditFragment extends BaseNoteFragment {
|
public class NoteEditFragment extends SearchableBaseNoteFragment {
|
||||||
|
|
||||||
private static final String LOG_TAG_AUTOSAVE = "AutoSave";
|
private static final String LOG_TAG_AUTOSAVE = "AutoSave";
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ import it.niedermann.owncloud.notes.util.ICallback;
|
||||||
import it.niedermann.owncloud.notes.util.MarkDownUtil;
|
import it.niedermann.owncloud.notes.util.MarkDownUtil;
|
||||||
import it.niedermann.owncloud.notes.util.NoteLinksUtils;
|
import it.niedermann.owncloud.notes.util.NoteLinksUtils;
|
||||||
|
|
||||||
public class NotePreviewFragment extends BaseNoteFragment {
|
public class NotePreviewFragment extends SearchableBaseNoteFragment {
|
||||||
|
|
||||||
private String changedText;
|
private String changedText;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
package it.niedermann.owncloud.notes.android.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.SearchView;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
|
import it.niedermann.owncloud.notes.R;
|
||||||
|
import it.niedermann.owncloud.notes.util.DisplayUtils;
|
||||||
|
|
||||||
|
public abstract class SearchableBaseNoteFragment extends BaseNoteFragment {
|
||||||
|
|
||||||
|
private TextView activeTextView;
|
||||||
|
private int currentOccurrence = 1;
|
||||||
|
private int occurrenceCount = 0;
|
||||||
|
|
||||||
|
private SearchView searchView;
|
||||||
|
|
||||||
|
private String searchQuery = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
searchQuery = savedInstanceState.getString("searchQuery", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||||
|
super.onPrepareOptionsMenu(menu);
|
||||||
|
|
||||||
|
MenuItem searchMenuItem = menu.findItem(R.id.search);
|
||||||
|
searchView = (SearchView) searchMenuItem.getActionView();
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(searchQuery) && isNew) {
|
||||||
|
searchMenuItem.expandActionView();
|
||||||
|
searchView.setQuery(searchQuery, true);
|
||||||
|
searchView.clearFocus();
|
||||||
|
} else {
|
||||||
|
searchMenuItem.collapseActionView();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final LinearLayout searchEditFrame = searchView.findViewById(R.id
|
||||||
|
.search_edit_frame);
|
||||||
|
|
||||||
|
searchEditFrame.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||||
|
int oldVisibility = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
int currentVisibility = searchEditFrame.getVisibility();
|
||||||
|
|
||||||
|
if (currentVisibility != oldVisibility) {
|
||||||
|
if (currentVisibility != View.VISIBLE) {
|
||||||
|
colorWithText("");
|
||||||
|
searchQuery = "";
|
||||||
|
hideSearchFabs();
|
||||||
|
} else {
|
||||||
|
showSearchFabs();
|
||||||
|
}
|
||||||
|
|
||||||
|
oldVisibility = currentVisibility;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
FloatingActionButton next = getSearchNextButton();
|
||||||
|
FloatingActionButton prev = getSearchPrevButton();
|
||||||
|
|
||||||
|
if (next != null) {
|
||||||
|
next.setOnClickListener(v -> {
|
||||||
|
currentOccurrence++;
|
||||||
|
jumpToOccurrence();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev != null) {
|
||||||
|
prev.setOnClickListener(v -> {
|
||||||
|
currentOccurrence--;
|
||||||
|
jumpToOccurrence();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onQueryTextSubmit(String query) {
|
||||||
|
currentOccurrence++;
|
||||||
|
jumpToOccurrence();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onQueryTextChange(String newText) {
|
||||||
|
searchQuery = newText;
|
||||||
|
colorWithText(newText);
|
||||||
|
occurrenceCount = countOccurrences(getContent(), searchQuery);
|
||||||
|
if (occurrenceCount > 1) {
|
||||||
|
showSearchFabs();
|
||||||
|
} else {
|
||||||
|
hideSearchFabs();
|
||||||
|
}
|
||||||
|
currentOccurrence = 1;
|
||||||
|
jumpToOccurrence();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
if (searchView != null && !TextUtils.isEmpty(searchView.getQuery().toString())) {
|
||||||
|
outState.putString("searchQuery", searchView.getQuery().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setActiveTextView(TextView textView) {
|
||||||
|
activeTextView = textView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void colorWithText(String newText) {
|
||||||
|
if (activeTextView != null && ViewCompat.isAttachedToWindow(activeTextView)) {
|
||||||
|
activeTextView.setText(DisplayUtils.searchAndColor(activeTextView.getText().toString(), new SpannableString
|
||||||
|
(activeTextView.getText()), newText, getResources().getColor(R.color.primary)),
|
||||||
|
TextView.BufferType.SPANNABLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSearchFabs() {
|
||||||
|
FloatingActionButton next = getSearchNextButton();
|
||||||
|
FloatingActionButton prev = getSearchPrevButton();
|
||||||
|
if (prev != null) {
|
||||||
|
prev.show();
|
||||||
|
}
|
||||||
|
if (next != null) {
|
||||||
|
next.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideSearchFabs() {
|
||||||
|
FloatingActionButton next = getSearchNextButton();
|
||||||
|
FloatingActionButton prev = getSearchPrevButton();
|
||||||
|
if (prev != null) {
|
||||||
|
prev.hide();
|
||||||
|
}
|
||||||
|
if (next != null) {
|
||||||
|
next.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void jumpToOccurrence() {
|
||||||
|
if (searchQuery == null || searchQuery.isEmpty()) {
|
||||||
|
// No search term
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentOccurrence < 1) {
|
||||||
|
// if currentOccurrence is lower than 1, jump to last occurrence
|
||||||
|
currentOccurrence = occurrenceCount;
|
||||||
|
jumpToOccurrence();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String currentContent = getContent().toLowerCase();
|
||||||
|
int indexOfNewText = indexOfNth(currentContent, searchQuery.toLowerCase(), 0, currentOccurrence);
|
||||||
|
if (indexOfNewText <= 0) {
|
||||||
|
// Search term is not n times in text
|
||||||
|
// Go back to first search result
|
||||||
|
if (currentOccurrence != 1) {
|
||||||
|
currentOccurrence = 1;
|
||||||
|
jumpToOccurrence();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String textUntilFirstOccurrence = currentContent.substring(0, indexOfNewText);
|
||||||
|
int numberLine = getLayout().getLineForOffset(textUntilFirstOccurrence.length());
|
||||||
|
|
||||||
|
if (numberLine >= 0) {
|
||||||
|
getScrollView().smoothScrollTo(0, getLayout().getLineTop(numberLine));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int indexOfNth(String input, String value, int startIndex, int nth) {
|
||||||
|
if (nth < 1)
|
||||||
|
throw new IllegalArgumentException("Param 'nth' must be greater than 0!");
|
||||||
|
if (nth == 1)
|
||||||
|
return input.indexOf(value, startIndex);
|
||||||
|
int idx = input.indexOf(value, startIndex);
|
||||||
|
if (idx == -1)
|
||||||
|
return -1;
|
||||||
|
return indexOfNth(input, value, idx + 1, --nth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int countOccurrences(String haystack, String needle) {
|
||||||
|
if (haystack == null || haystack.isEmpty() || needle == null || needle.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
haystack = haystack.toLowerCase();
|
||||||
|
needle = needle.toLowerCase();
|
||||||
|
int lastIndex = 0;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
while (lastIndex != -1) {
|
||||||
|
lastIndex = haystack.indexOf(needle, lastIndex);
|
||||||
|
if (lastIndex != -1) {
|
||||||
|
count++;
|
||||||
|
lastIndex += needle.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue