diff --git a/app/build.gradle b/app/build.gradle index 627f01cc..1682c41b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' } diff --git a/app/src/main/kotlin/com/simplemobiletools/notes/pro/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/notes/pro/activities/MainActivity.kt index b0a82912..d70b9259 100644 --- a/app/src/main/kotlin/com/simplemobiletools/notes/pro/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/notes/pro/activities/MainActivity.kt @@ -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() + 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 { + val indexes = arrayListOf() + 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,26 +915,28 @@ class MainActivity : SimpleActivity() { } fun currentNoteTextChanged(newText: String, showUndo: Boolean, showRedo: Boolean) { - var shouldRecreateMenu = false - if (showUndo != showUndoButton) { - showUndoButton = showUndo - shouldRecreateMenu = true - } - - if (showRedo != showRedoButton) { - showRedoButton = showRedo - shouldRecreateMenu = true - } - - if (!config.autosaveNotes) { - showSaveButton = newText != mCurrentNote.value - if (showSaveButton != saveNoteButton?.isVisible) { + if (searchIsActive.not()) { + var shouldRecreateMenu = false + if (showUndo != showUndoButton) { + showUndoButton = showUndo shouldRecreateMenu = true } - } - if (shouldRecreateMenu) { - invalidateOptionsMenu() + if (showRedo != showRedoButton) { + showRedoButton = showRedo + shouldRecreateMenu = true + } + + if (!config.autosaveNotes) { + showSaveButton = newText != mCurrentNote.value + if (showSaveButton != saveNoteButton?.isVisible) { + shouldRecreateMenu = true + } + } + + if (shouldRecreateMenu) { + invalidateOptionsMenu() + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/notes/pro/adapters/NotesPagerAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/notes/pro/adapters/NotesPagerAdapter.kt index 1d183faf..9e4180fe 100644 --- a/app/src/main/kotlin/com/simplemobiletools/notes/pro/adapters/NotesPagerAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/notes/pro/adapters/NotesPagerAdapter.kt @@ -45,6 +45,10 @@ class NotesPagerAdapter(fm: FragmentManager, val notes: List, 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() diff --git a/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Context.kt index ee459e6d..c213f570 100644 --- a/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Context.kt @@ -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) diff --git a/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Fragment.kt b/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Fragment.kt index 3524f4f5..4be475f7 100644 --- a/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Fragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/Fragment.kt @@ -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!! + diff --git a/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/String.kt b/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/String.kt index a7be6176..3ce92869 100644 --- a/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/String.kt +++ b/app/src/main/kotlin/com/simplemobiletools/notes/pro/extensions/String.kt @@ -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? { @@ -14,3 +19,12 @@ fun String.parseChecklistItems(): ArrayList? { } return null } + +@RequiresApi(Build.VERSION_CODES.N) +fun String.toHtml() = + if (isNougatPlus()) { + Html.fromHtml(this, Html.FROM_HTML_MODE_LEGACY) + } else { + Html.fromHtml(this) + } + diff --git a/app/src/main/kotlin/com/simplemobiletools/notes/pro/fragments/TextFragment.kt b/app/src/main/kotlin/com/simplemobiletools/notes/pro/fragments/TextFragment.kt index a813a878..9cb22532 100644 --- a/app/src/main/kotlin/com/simplemobiletools/notes/pro/fragments/TextFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/notes/pro/fragments/TextFragment.kt @@ -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 } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a73f4561..b457e45e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,16 +1,23 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> - + + + android:layout_height="match_parent"> - + + + + diff --git a/app/src/main/res/layout/search_item.xml b/app/src/main/res/layout/search_item.xml new file mode 100644 index 00000000..1f12d556 --- /dev/null +++ b/app/src/main/res/layout/search_item.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/menu.xml b/app/src/main/res/menu/menu.xml index fe032a88..b6e49a5a 100644 --- a/app/src/main/res/menu/menu.xml +++ b/app/src/main/res/menu/menu.xml @@ -16,6 +16,11 @@ android:icon="@drawable/ic_redo_vector" android:title="@string/redo" app:showAsAction="ifRoom"/> +