Merge pull request #338 from SimpleMobileTools/search

Search
This commit is contained in:
Tibor Kaputa 2020-03-25 20:34:09 +01:00 committed by GitHub
commit 8bdac0e761
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 331 additions and 37 deletions

View File

@ -57,10 +57,10 @@ android {
}
dependencies {
implementation 'com.simplemobiletools:commons:5.22.19'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'
implementation 'com.simplemobiletools:commons:5.23.7'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
kapt 'androidx.room:room-compiler:2.2.2'
implementation 'androidx.room:room-runtime:2.2.2'
annotationProcessor 'androidx.room:room-compiler:2.2.2'
kapt 'androidx.room:room-compiler:2.2.4'
implementation 'androidx.room:room-runtime:2.2.4'
annotationProcessor 'androidx.room:room-compiler:2.2.4'
}

View File

@ -3,13 +3,20 @@ package com.simplemobiletools.notes.pro.activities
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.Spannable
import android.text.SpannableString
import android.text.method.ArrowKeyMovementMethod
import android.text.method.LinkMovementMethod
import android.text.style.BackgroundColorSpan
import android.util.TypedValue
import android.view.ActionMode
import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.core.graphics.ColorUtils
import com.simplemobiletools.commons.dialogs.ConfirmationAdvancedDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
@ -26,9 +33,14 @@ import com.simplemobiletools.notes.pro.adapters.NotesPagerAdapter
import com.simplemobiletools.notes.pro.databases.NotesDatabase
import com.simplemobiletools.notes.pro.dialogs.*
import com.simplemobiletools.notes.pro.extensions.*
import com.simplemobiletools.notes.pro.helpers.*
import com.simplemobiletools.notes.pro.fragments.TextFragment
import com.simplemobiletools.notes.pro.helpers.MIME_TEXT_PLAIN
import com.simplemobiletools.notes.pro.helpers.NoteType
import com.simplemobiletools.notes.pro.helpers.NotesHelper
import com.simplemobiletools.notes.pro.helpers.OPEN_NOTE_ID
import com.simplemobiletools.notes.pro.models.Note
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.search_item.*
import java.io.File
import java.nio.charset.Charset
@ -47,6 +59,9 @@ class MainActivity : SimpleActivity() {
private var showSaveButton = false
private var showUndoButton = false
private var showRedoButton = false
private var searchIndex = 0
private var searchMatches = emptyList<Int>()
private var searchIsActive = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -65,7 +80,164 @@ class MainActivity : SimpleActivity() {
}
wasInit = true
checkAppOnSDCard()
searchListeners()
}
private fun searchListeners() {
search_query.onTextChangeListener { query ->
currentNotesView()?.let { noteView ->
currentTextFragment?.removeTextWatcher()
searchClearSpans(noteView.text)
if (query.isNotBlank() && query.length > 1) {
searchMatches = searchMatches(query, noteView.value)
searchHighLightText(noteView, query)
}
currentTextFragment?.setTextWatcher()
if (searchMatches.isNotEmpty()) {
noteView.requestFocus()
noteView.setSelection(searchMatches.getOrNull(searchIndex) ?: 0)
}
search_query.postDelayed({
search_query.requestFocus()
}, 50)
}
}
search_previous.setOnClickListener {
currentNotesView()?.let { noteView ->
if (searchIndex > 0) {
searchIndex--
}
else {
searchIndex = searchMatches.lastIndex
}
selectMatch(noteView)
}
}
search_next.setOnClickListener {
currentNotesView()?.let { noteView ->
if (searchIndex < searchMatches.lastIndex) {
searchIndex++
} else {
searchIndex = 0
}
selectMatch(noteView)
}
}
search_clear.setOnClickListener {
searchHide()
}
view_pager.onPageChangeListener {
currentTextFragment?.removeTextWatcher()
currentNotesView()?.let { noteView ->
searchClearSpans(noteView.text)
}
searchHide()
currentTextFragment?.setTextWatcher()
}
search_query.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
search_next.performClick()
return@OnEditorActionListener true
}
false
})
}
private val currentTextFragment: TextFragment? get() = mAdapter?.textFragment(view_pager.currentItem)
private fun searchClearSpans(editable: Editable) {
val spans = editable.getSpans(0, editable.length, Any::class.java)
for (span in spans) {
if (span is BackgroundColorSpan) {
editable.removeSpan(span)
}
}
}
private fun selectMatch(noteView: MyEditText) {
if (searchMatches.isNotEmpty()) {
noteView.requestFocus()
noteView.setSelection(searchMatches.getOrNull(searchIndex) ?: 0)
} else
hideKeyboard()
}
private fun searchMatches(textToHighlight: String, content: String): ArrayList<Int> {
val indexes = arrayListOf<Int>()
var indexOf = content.indexOf(textToHighlight, 0, ignoreCase = true)
var offset = 0
while (offset < content.length && indexOf != -1) {
indexOf = content.indexOf(textToHighlight, offset, ignoreCase = true)
if (indexOf == -1) {
break
} else {
indexes.add(indexOf)
}
offset = indexOf + 1
}
return indexes
}
private fun searchHighLightText(view: MyEditText, highlightText: String) {
val content = view.text.toString()
var indexOf = content.indexOf(highlightText, 0, true)
val wordToSpan = SpannableString(view.text)
var offset = 0
while (offset < content.length && indexOf != -1) {
indexOf = content.indexOf(highlightText, offset, true)
if (indexOf == -1) {
break
} else {
val spanBgColor = BackgroundColorSpan(ColorUtils.setAlphaComponent(config.primaryColor, 128))
val spanFlag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
wordToSpan.setSpan(spanBgColor, indexOf, indexOf + highlightText.length, spanFlag)
view.setText(wordToSpan, TextView.BufferType.SPANNABLE)
}
offset = indexOf + 1
}
}
private fun searchShow() {
searchIsActive = true
search_root.beVisible()
showKeyboard(search_query)
currentNotesView()?.let { noteView ->
noteView.requestFocus()
noteView.setSelection(0)
}
search_query.postDelayed({
search_query.requestFocus()
}, 250)
}
private fun searchHide() {
search_query.text?.clear()
searchIsActive = false
search_root.beGone()
}
override fun onResume() {
@ -82,6 +254,12 @@ class MainActivity : SimpleActivity() {
setTextColor(config.textColor)
}
updateTextColors(view_pager)
val contrastColor = config.primaryColor.getContrastColor()
search_root.setBackgroundColor(config.primaryColor)
search_previous.applyColorFilter(contrastColor)
search_next.applyColorFilter(contrastColor)
search_clear.applyColorFilter(contrastColor)
}
override fun onPause() {
@ -114,6 +292,7 @@ class MainActivity : SimpleActivity() {
findItem(R.id.open_note).isVisible = shouldBeVisible
findItem(R.id.delete_note).isVisible = shouldBeVisible
findItem(R.id.export_all_notes).isVisible = shouldBeVisible
findItem(R.id.open_search).isVisible = currentItemIsCheckList.not()
saveNoteButton = findItem(R.id.save_note)
saveNoteButton!!.isVisible = !config.autosaveNotes && showSaveButton && mCurrentNote.type == NoteType.TYPE_TEXT.value
@ -129,6 +308,7 @@ class MainActivity : SimpleActivity() {
}
when (item.itemId) {
R.id.open_search -> searchShow()
R.id.open_note -> displayOpenNoteDialog()
R.id.save_note -> saveNote()
R.id.undo -> undo()
@ -176,6 +356,8 @@ class MainActivity : SimpleActivity() {
}
super.onBackPressed()
}
} else if (searchIsActive) {
searchHide()
} else {
super.onBackPressed()
}
@ -188,6 +370,8 @@ class MainActivity : SimpleActivity() {
checkIntents(intent)
}
private val currentItemIsCheckList get() = mAdapter?.isChecklistFragment(view_pager.currentItem) ?: false
private fun checkIntents(intent: Intent) {
intent.apply {
if (action == Intent.ACTION_SEND && type == MIME_TEXT_PLAIN) {
@ -627,7 +811,8 @@ class MainActivity : SimpleActivity() {
private fun saveCurrentNote(force: Boolean) {
getPagerAdapter().saveCurrentNote(view_pager.currentItem, force)
if (mCurrentNote.type == NoteType.TYPE_CHECKLIST.value) {
mCurrentNote.value = getPagerAdapter().getNoteChecklistItems(view_pager.currentItem) ?: ""
mCurrentNote.value = getPagerAdapter().getNoteChecklistItems(view_pager.currentItem)
?: ""
}
}
@ -730,6 +915,7 @@ class MainActivity : SimpleActivity() {
}
fun currentNoteTextChanged(newText: String, showUndo: Boolean, showRedo: Boolean) {
if (searchIsActive.not()) {
var shouldRecreateMenu = false
if (showUndo != showUndoButton) {
showUndoButton = showUndo
@ -752,6 +938,7 @@ class MainActivity : SimpleActivity() {
invalidateOptionsMenu()
}
}
}
private fun checkWhatsNewDialog() {
arrayListOf<Release>().apply {

View File

@ -45,6 +45,10 @@ class NotesPagerAdapter(fm: FragmentManager, val notes: List<Note>, val activity
}
}
fun isChecklistFragment(position: Int): Boolean = (fragments[position] is ChecklistFragment)
fun textFragment(position: Int): TextFragment? = (fragments[position] as? TextFragment)
fun getCurrentNotesView(position: Int) = (fragments[position] as? TextFragment)?.getNotesView()
fun getCurrentNoteViewText(position: Int) = (fragments[position] as? TextFragment)?.getCurrentNoteViewText()

View File

@ -4,6 +4,7 @@ import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import com.simplemobiletools.notes.pro.databases.NotesDatabase
import com.simplemobiletools.notes.pro.helpers.Config
import com.simplemobiletools.notes.pro.helpers.MyWidgetProvider
@ -26,3 +27,5 @@ fun Context.updateWidgets() {
}
}
}
fun Context.color(id: Int) = ContextCompat.getColor(this, id)

View File

@ -7,3 +7,4 @@ import com.simplemobiletools.notes.pro.helpers.Config
val Fragment.config: Config? get() = if (context != null) Config.newInstance(context!!) else null
val Fragment.requiredActivity: FragmentActivity get() = this.activity!!

View File

@ -1,7 +1,12 @@
package com.simplemobiletools.notes.pro.extensions
import android.os.Build
import android.text.Html
import androidx.annotation.RequiresApi
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.helpers.isMarshmallowPlus
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.notes.pro.models.ChecklistItem
fun String.parseChecklistItems(): ArrayList<ChecklistItem>? {
@ -14,3 +19,12 @@ fun String.parseChecklistItems(): ArrayList<ChecklistItem>? {
}
return null
}
@RequiresApi(Build.VERSION_CODES.N)
fun String.toHtml() =
if (isNougatPlus()) {
Html.fromHtml(this, Html.FROM_HTML_MODE_LEGACY)
} else {
Html.fromHtml(this)
}

View File

@ -77,7 +77,8 @@ class TextFragment : NoteFragment() {
if (config!!.autosaveNotes) {
saveText(false)
}
view.text_note_view.removeTextChangedListener(textWatcher)
removeTextWatcher()
}
override fun setMenuVisibility(menuVisible: Boolean) {
@ -154,9 +155,12 @@ class TextFragment : NoteFragment() {
view.notes_counter.beGone()
}
view.text_note_view.addTextChangedListener(textWatcher)
setTextWatcher()
}
fun setTextWatcher() = view.text_note_view.addTextChangedListener(textWatcher)
fun removeTextWatcher() = view.text_note_view.removeTextChangedListener(textWatcher)
fun updateNoteValue(value: String) {
note?.value = value
}

View File

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<com.simplemobiletools.commons.views.MyViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/search_item" />
<com.simplemobiletools.commons.views.MyViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -11,6 +17,7 @@
android:layout_height="wrap_content"
android:layout_gravity="top"
android:paddingLeft="@dimen/activity_margin"
android:paddingRight="@dimen/activity_margin"/>
android:paddingRight="@dimen/activity_margin" />
</com.simplemobiletools.commons.views.MyViewPager>
</com.simplemobiletools.commons.views.MyViewPager>
</LinearLayout>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/search_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/color_primary"
android:gravity="center_vertical"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="@dimen/medium_margin"
android:paddingBottom="@dimen/medium_margin">
<com.simplemobiletools.commons.views.MyEditText
android:id="@+id/search_query"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/normal_margin"
android:layout_marginEnd="@dimen/normal_margin"
android:layout_weight="1"
android:background="@null"
android:hint="@string/search"
android:maxLength="50"
android:maxLines="1"
android:singleLine="true"
android:textCursorDrawable="@null" />
<ImageView
android:id="@+id/search_previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/smaller_margin"
android:background="?selectableItemBackgroundBorderless"
android:padding="@dimen/small_margin"
android:src="@drawable/ic_chevron_left_vector" />
<ImageView
android:id="@+id/search_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/smaller_margin"
android:background="?selectableItemBackgroundBorderless"
android:padding="@dimen/small_margin"
android:src="@drawable/ic_chevron_right_vector" />
<ImageView
android:id="@+id/search_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/medium_margin"
android:background="?selectableItemBackgroundBorderless"
android:padding="@dimen/small_margin"
android:src="@drawable/ic_cross_vector" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:alpha="0.1"
android:background="@color/md_grey_white" />
</FrameLayout>

View File

@ -16,6 +16,11 @@
android:icon="@drawable/ic_redo_vector"
android:title="@string/redo"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/open_search"
android:icon="@drawable/ic_search_vector"
android:title="@string/search"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/open_note"
android:icon="@drawable/ic_folder_open_vector"