From e9172bd7ef560e5a76d15e7ee4fadbc6d5a7eea0 Mon Sep 17 00:00:00 2001 From: PhilKes Date: Sun, 9 Nov 2025 14:05:43 +0100 Subject: [PATCH] Highlight keyword in notes search and jump to keyword in note --- .../activity/main/fragment/NotallyFragment.kt | 17 ++- .../activity/note/EditActivity.kt | 16 +++ .../presentation/view/main/BaseNoteAdapter.kt | 22 +++- .../presentation/view/main/BaseNoteVH.kt | 121 ++++++++++++++---- .../view/misc/EditTextAutoClearFocus.kt | 1 + .../view/misc/StylableEditTextWithHistory.kt | 1 + .../misc/highlightableview/HighlightSpan.kt | 6 + .../HighlightableEditText.kt | 7 +- .../HighlightableTextView.kt | 101 +++++++++++++++ .../EditTextWithHistoryChange.kt | 2 +- .../main/res/layout/recycler_base_note.xml | 24 ++-- 11 files changed, 268 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightSpan.kt rename app/src/main/java/com/philkes/notallyx/presentation/view/misc/{ => highlightableview}/HighlightableEditText.kt (93%) create mode 100644 app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightableTextView.kt diff --git a/app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/NotallyFragment.kt b/app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/NotallyFragment.kt index 2b7688b1..ba7fdbe6 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/NotallyFragment.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/NotallyFragment.kt @@ -27,6 +27,7 @@ import com.philkes.notallyx.databinding.FragmentNotesBinding import com.philkes.notallyx.presentation.activity.main.MainActivity import com.philkes.notallyx.presentation.activity.main.fragment.SearchFragment.Companion.EXTRA_INITIAL_FOLDER import com.philkes.notallyx.presentation.activity.main.fragment.SearchFragment.Companion.EXTRA_INITIAL_LABEL +import com.philkes.notallyx.presentation.activity.note.EditActivity import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.EXTRA_FOLDER_FROM import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.EXTRA_FOLDER_TO import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.EXTRA_NOTE_ID @@ -187,6 +188,7 @@ abstract class NotallyFragment : Fragment(), ItemListener { binding?.EnterSearchKeywordLayout?.visibility = View.VISIBLE requestFocus() activity?.showKeyboard(this) + notesAdapter?.setSearchKeyword(model.keyword) } else { // In other fragments, respect the preference val alwaysShowSearchBar = model.preferences.alwaysShowSearchBar.value @@ -195,16 +197,18 @@ abstract class NotallyFragment : Fragment(), ItemListener { setText("") clearFocus() activity?.hideKeyboard(this) + notesAdapter?.setSearchKeyword("") } } doAfterTextChanged { text -> val isSearchFragment = navController.currentDestination?.id == R.id.Search if (isSearchFragment) { - model.keyword = requireNotNull(text, { "text is null" }).trim().toString() + model.keyword = requireNotNull(text, { "text is null" }).toString() + notesAdapter?.apply { setSearchKeyword(model.keyword) } } if (text?.isNotEmpty() == true && !isSearchFragment) { setText("") - model.keyword = text.trim().toString() + model.keyword = text.toString() navController.navigate( R.id.Search, Bundle().apply { @@ -213,6 +217,9 @@ abstract class NotallyFragment : Fragment(), ItemListener { }, ) } + this@NotallyFragment.binding?.MainListView?.apply { + postOnAnimationDelayed({ scrollToPosition(0) }, 10) + } } } } @@ -299,6 +306,12 @@ abstract class NotallyFragment : Fragment(), ItemListener { private fun goToActivity(activity: Class<*>, baseNote: BaseNote) { val intent = Intent(requireContext(), activity) intent.putExtra(EXTRA_SELECTED_BASE_NOTE, baseNote.id) + // If launched from Search fragment with a non-empty keyword, pass it to the editor to + // auto-highlight + val isInSearch = view?.findNavController()?.currentDestination?.id == R.id.Search + if (isInSearch && model.keyword.isNotBlank()) { + intent.putExtra(EditActivity.EXTRA_INITIAL_SEARCH_QUERY, model.keyword) + } openNoteActivityResultLauncher.launch(intent) } diff --git a/app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt b/app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt index 70e1a94d..52cf17c0 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt @@ -236,6 +236,21 @@ abstract class EditActivity(private val type: Type) : } } + override fun onStart() { + super.onStart() + // If launched with an initial search query (from global search), auto-start in-note search + intent.getStringExtra(EXTRA_INITIAL_SEARCH_QUERY)?.let { initialQuery -> + if (initialQuery.isNotBlank()) { + binding.EnterSearchKeyword.postOnAnimation { + startSearch() + binding.EnterSearchKeyword.setText(initialQuery) + binding.EnterSearchKeyword.setSelection(initialQuery.length) + } + } + intent.removeExtra(EXTRA_INITIAL_SEARCH_QUERY) + } + } + private fun configureEdgeToEdgeInsets() { WindowCompat.setDecorFitsSystemWindows(window, false) @@ -1242,6 +1257,7 @@ abstract class EditActivity(private val type: Type) : const val EXTRA_NOTE_ID = "notallyx.intent.extra.NOTE_ID" const val EXTRA_FOLDER_FROM = "notallyx.intent.extra.FOLDER_FROM" const val EXTRA_FOLDER_TO = "notallyx.intent.extra.FOLDER_TO" + const val EXTRA_INITIAL_SEARCH_QUERY = "notallyx.intent.extra.INITIAL_SEARCH_QUERY" val DEFAULT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler() } diff --git a/app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteAdapter.kt b/app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteAdapter.kt index 6db3af2d..ab6452a9 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteAdapter.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteAdapter.kt @@ -29,6 +29,8 @@ class BaseNoteAdapter( private val listener: ItemListener, ) : RecyclerView.Adapter() { + private var searchKeyword: String = "" + private var list = SortedList(Item::class.java, notesSort.createCallback()) override fun getItemViewType(position: Int): Int { @@ -45,13 +47,19 @@ class BaseNoteAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (val item = list[position]) { is Header -> (holder as HeaderVH).bind(item) - is BaseNote -> - (holder as BaseNoteVH).bind( - item, - imageRoot, - selectedIds.contains(item.id), - notesSort.sortedBy, - ) + is BaseNote -> { + (holder as BaseNoteVH).apply { + setSearchKeyword(searchKeyword) + bind(item, imageRoot, selectedIds.contains(item.id), notesSort.sortedBy) + } + } + } + } + + fun setSearchKeyword(keyword: String) { + if (searchKeyword != keyword) { + searchKeyword = keyword + notifyDataSetChanged() } } diff --git a/app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteVH.kt b/app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteVH.kt index e3c95a1a..df90dec0 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteVH.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteVH.kt @@ -34,6 +34,8 @@ import com.philkes.notallyx.presentation.extractColor import com.philkes.notallyx.presentation.getQuantityString import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews import com.philkes.notallyx.presentation.view.misc.ItemListener +import com.philkes.notallyx.presentation.view.misc.highlightableview.HighlightableTextView +import com.philkes.notallyx.presentation.view.misc.highlightableview.SEARCH_SNIPPET_ITEM_LINES import com.philkes.notallyx.presentation.view.note.listitem.init import com.philkes.notallyx.presentation.viewmodel.preference.DateFormat import com.philkes.notallyx.presentation.viewmodel.preference.NotesSortBy @@ -56,6 +58,12 @@ class BaseNoteVH( listener: ItemListener, ) : RecyclerView.ViewHolder(binding.root) { + private var searchKeyword: String = "" + + fun setSearchKeyword(keyword: String) { + this.searchKeyword = keyword + } + init { val title = preferences.textSize.displayTitleSize val body = preferences.textSize.displayBodySize @@ -95,8 +103,8 @@ class BaseNoteVH( updateCheck(checked, baseNote.color) when (baseNote.type) { - Type.NOTE -> bindNote(baseNote.body, baseNote.spans, baseNote.title.isEmpty()) - Type.LIST -> bindList(baseNote.items, baseNote.title.isEmpty()) + Type.NOTE -> bindNote(baseNote, searchKeyword) + Type.LIST -> bindList(baseNote, searchKeyword) } val (date, datePrefixResId) = when (sortBy) { @@ -111,12 +119,18 @@ class BaseNoteVH( setFiles(baseNote.files) binding.Title.apply { - text = baseNote.title isVisible = baseNote.title.isNotEmpty() updatePadding( bottom = if (baseNote.hasNoContents() || shouldOnlyDisplayTitle(baseNote)) 0 else 8.dp ) + if (searchKeyword.isNotBlank()) { + val snippet = extractSearchSnippet(baseNote.title, searchKeyword) + if (snippet != null) { + showSearchSnippet(snippet) + } else text = baseNote.title + } else text = baseNote.title + setCompoundDrawablesWithIntrinsicBounds( if (baseNote.type == Type.LIST && preferences.maxItems < 1) R.drawable.checkbox_small @@ -148,9 +162,23 @@ class BaseNoteVH( binding.RemindersView.isVisible = baseNote.reminders.any { it.hasUpcomingNotification() } } - private fun bindNote(body: String, spans: List, isTitleEmpty: Boolean) { + private fun bindNote(baseNote: BaseNote, keyword: String) { binding.LinearLayout.visibility = GONE + if (keyword.isBlank()) { + bindNote(baseNote.body, baseNote.spans, baseNote.title.isEmpty()) + return + } + binding.Note.apply { + val snippet = extractSearchSnippet(baseNote.body, keyword) + if (snippet == null) { + bindNote(baseNote.body, baseNote.spans, baseNote.title.isEmpty()) + } else { + showSearchSnippet(snippet) + } + } + } + private fun bindNote(body: String, spans: List, isTitleEmpty: Boolean) { binding.Note.apply { text = body.applySpans(spans) if (preferences.maxLines < 1) { @@ -162,29 +190,84 @@ class BaseNoteVH( } } - private fun bindList(items: List, isTitleEmpty: Boolean) { + /** Shows a snippet of ListItems around the ListItem that contains keyword */ + private fun LinearLayout.bindListSearch( + initializedItems: List, + keyword: String, + isTitleEmpty: Boolean, + ) { + binding.LinearLayout.visibility = VISIBLE + val keywordItemIdx = + initializedItems.indexOfFirst { it.body.contains(keyword, ignoreCase = true) } + if (keywordItemIdx == -1) { + return bindList(initializedItems, isTitleEmpty) + } + val listItemViews = children.filterIsInstance(HighlightableTextView::class.java).toList() + listItemViews.forEach { it.visibility = GONE } + val startItemIdx = (keywordItemIdx - SEARCH_SNIPPET_ITEM_LINES).coerceAtLeast(0) + val endItemIdx = + (keywordItemIdx + SEARCH_SNIPPET_ITEM_LINES).coerceAtMost(initializedItems.lastIndex) + (startItemIdx..endItemIdx).forEachIndexed { viewIdx, itemIdx -> + listItemViews[viewIdx].apply { + val item = initializedItems[itemIdx] + text = item.body + if (itemIdx == keywordItemIdx) { + highlight(keyword) + } + handleChecked(this, item.checked) + visibility = VISIBLE + updateLayoutParams { + marginStart = if (item.isChild) 20.dp else 0 + } + } + } + bindItemsRemaining(initializedItems.size, endItemIdx - startItemIdx + 1) + } + + private fun bindList(baseNote: BaseNote, keyword: String) { + binding.Note.visibility = GONE + val initializedItems = baseNote.items.init() + if (baseNote.items.isEmpty()) { + binding.LinearLayout.visibility = GONE + return + } + if (keyword.isBlank()) { + bindList(initializedItems, baseNote.title.isEmpty()) + return + } + binding.LinearLayout.bindListSearch(initializedItems, keyword, baseNote.title.isEmpty()) + } + + private fun bindItemsRemaining(totalItems: Int, displayedItems: Int) { + if (displayedItems > 0 && totalItems > displayedItems) { + binding.ItemsRemaining.apply { + visibility = VISIBLE + text = (totalItems - displayedItems).toString() + } + } else binding.ItemsRemaining.visibility = GONE + } + + private fun bindList(initializedItems: List, isTitleEmpty: Boolean) { binding.apply { - Note.visibility = GONE - if (items.isEmpty()) { + bindItemsRemaining(initializedItems.size, preferences.maxItems) + if (initializedItems.isEmpty()) { LinearLayout.visibility = GONE } else { LinearLayout.visibility = VISIBLE val forceShowFirstItem = preferences.maxItems < 1 && isTitleEmpty - val initializedItems = items.init() val filteredList = initializedItems.take(if (forceShowFirstItem) 1 else preferences.maxItems) - LinearLayout.children.forEachIndexed { index, view -> - if (view.id != R.id.ItemsRemaining) { + LinearLayout.children + .filterIsInstance(HighlightableTextView::class.java) + .forEachIndexed { index, view -> if (index < filteredList.size) { val item = filteredList[index] - (view as TextView).apply { + view.apply { text = item.body handleChecked(this, item.checked) visibility = VISIBLE - if (item.isChild) { - updateLayoutParams { - marginStart = 20.dp - } + updateLayoutParams { + marginStart = if (item.isChild) 20.dp else 0 } if (index == filteredList.lastIndex) { updatePadding(bottom = 0) @@ -192,14 +275,6 @@ class BaseNoteVH( } } else view.visibility = GONE } - } - - if (preferences.maxItems > 0 && items.size > preferences.maxItems) { - ItemsRemaining.apply { - visibility = VISIBLE - text = (items.size - preferences.maxItems).toString() - } - } else ItemsRemaining.visibility = GONE } } } diff --git a/app/src/main/java/com/philkes/notallyx/presentation/view/misc/EditTextAutoClearFocus.kt b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/EditTextAutoClearFocus.kt index 11c9ce7f..d3137e28 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/view/misc/EditTextAutoClearFocus.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/EditTextAutoClearFocus.kt @@ -3,6 +3,7 @@ package com.philkes.notallyx.presentation.view.misc import android.content.Context import android.util.AttributeSet import android.view.KeyEvent +import com.philkes.notallyx.presentation.view.misc.highlightableview.HighlightableEditText class EditTextAutoClearFocus(context: Context, attributeSet: AttributeSet) : HighlightableEditText(context, attributeSet) { diff --git a/app/src/main/java/com/philkes/notallyx/presentation/view/misc/StylableEditTextWithHistory.kt b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/StylableEditTextWithHistory.kt index 9e7cfb99..6fa53a84 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/view/misc/StylableEditTextWithHistory.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/StylableEditTextWithHistory.kt @@ -24,6 +24,7 @@ import com.philkes.notallyx.presentation.createTextWatcherWithHistory import com.philkes.notallyx.presentation.removeSelectionFromSpans import com.philkes.notallyx.presentation.setCancelButton import com.philkes.notallyx.presentation.showAndFocus +import com.philkes.notallyx.presentation.view.misc.highlightableview.HighlightableEditText import com.philkes.notallyx.utils.changehistory.ChangeHistory import com.philkes.notallyx.utils.changehistory.EditTextState import com.philkes.notallyx.utils.changehistory.EditTextWithHistoryChange diff --git a/app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightSpan.kt b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightSpan.kt new file mode 100644 index 00000000..8f47bb39 --- /dev/null +++ b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightSpan.kt @@ -0,0 +1,6 @@ +package com.philkes.notallyx.presentation.view.misc.highlightableview + +import android.text.style.BackgroundColorSpan +import androidx.annotation.ColorInt + +class HighlightSpan(@ColorInt color: Int) : BackgroundColorSpan(color) diff --git a/app/src/main/java/com/philkes/notallyx/presentation/view/misc/HighlightableEditText.kt b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightableEditText.kt similarity index 93% rename from app/src/main/java/com/philkes/notallyx/presentation/view/misc/HighlightableEditText.kt rename to app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightableEditText.kt index d1c21911..bc1501c3 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/view/misc/HighlightableEditText.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightableEditText.kt @@ -1,13 +1,12 @@ -package com.philkes.notallyx.presentation.view.misc +package com.philkes.notallyx.presentation.view.misc.highlightableview import android.content.Context import android.text.Spanned -import android.text.style.BackgroundColorSpan import android.text.style.CharacterStyle import android.util.AttributeSet -import androidx.annotation.ColorInt import androidx.appcompat.widget.AppCompatEditText import com.philkes.notallyx.presentation.removeSelectionFromSpans +import com.philkes.notallyx.presentation.view.misc.EditTextWithWatcher import com.philkes.notallyx.presentation.withAlpha /** @@ -95,5 +94,3 @@ open class HighlightableEditText(context: Context, attrs: AttributeSet) : } } } - -class HighlightSpan(@ColorInt color: Int) : BackgroundColorSpan(color) diff --git a/app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightableTextView.kt b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightableTextView.kt new file mode 100644 index 00000000..097623c4 --- /dev/null +++ b/app/src/main/java/com/philkes/notallyx/presentation/view/misc/highlightableview/HighlightableTextView.kt @@ -0,0 +1,101 @@ +package com.philkes.notallyx.presentation.view.misc.highlightableview + +import android.content.Context +import android.text.SpannableString +import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE +import android.text.TextUtils +import android.util.AttributeSet +import android.widget.TextView +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.view.isVisible + +/** Amount of lines to show for a search snippet on top and below the keyword line */ +const val SEARCH_SNIPPET_ITEM_LINES = 2 +const val SEARCH_SNIPPET_SIZE = 100 +private const val ELLIPSED_TEXT = "..." + +/** TextView that can highlight keyword with snippet around it */ +class HighlightableTextView(context: Context, attrs: AttributeSet?) : + AppCompatTextView(context, attrs) { + + fun clearHighlights() { + text = text.toString() + } + + /** Visibly highlight text from [startIdx] to [endIdx]. */ + fun highlight(keyword: String) { + val startIdx = this.text.indexOf(keyword, ignoreCase = true) + if (startIdx == -1) { + return + } + highlight(startIdx, startIdx + keyword.length) + } + + fun highlight(startIdx: Int, endIdx: Int) { + this.text = + SpannableString(this.text.toString()).apply { + setSpan(HighlightSpan(highlightColor), startIdx, endIdx, SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + + fun showSearchSnippet(snippet: Snippet) { + val displayedText = buildString { + if (!snippet.includesFirstLine) append(ELLIPSED_TEXT) + append(snippet.text) + if (!snippet.includesLastLine) append(ELLIPSED_TEXT) + } + val displayKeywordIdx = + when { + !snippet.includesFirstLine -> snippet.keywordIdx + ELLIPSED_TEXT.length + else -> snippet.keywordIdx + } + text = displayedText + highlight(displayKeywordIdx, displayKeywordIdx + snippet.keyword.length) + isVisible = true + maxLines = Int.MAX_VALUE + } + + /** + * Extract a snippet around given keyword that has given lines around the keyword. + * + * @return Snippet's text, keyword idx in the Snippet, whether or not the snippet includes the + * first line of the text. + */ + fun extractSearchSnippet(text: String, keyword: String): Snippet? { + val keywordIndex = text.indexOf(keyword, ignoreCase = true) + if (keywordIndex == -1) { + return null + } + if (text.length <= (2 * SEARCH_SNIPPET_SIZE) + keyword.length) { + return Snippet(text, keyword, text.indexOf(keyword, ignoreCase = true), true, true) + } + val start = (keywordIndex - SEARCH_SNIPPET_SIZE).coerceAtLeast(0) + val end = (keywordIndex + keyword.length + SEARCH_SNIPPET_SIZE).coerceAtMost(text.lastIndex) + val snippetText = text.substring(start, end + 1) + val keywordIdxInSnippet = snippetText.indexOf(keyword, ignoreCase = true) + return Snippet(snippetText, keyword, keywordIdxInSnippet, start == 0, end == text.lastIndex) + } + + fun TextView.ellipsizeMultilineTextToFit(text: String): String { + val paint = paint + val availableWidth = width - paddingLeft - paddingRight + // if (availableWidth <= 0) { + // // Wait for layout if width not ready yet + // post { ellipsizeMultilineTextToFit(text) } + // return text + // } + + return text.lines().joinToString("\n") { line -> + TextUtils.ellipsize(line, paint, availableWidth.toFloat(), TextUtils.TruncateAt.END) + .toString() + } + } +} + +data class Snippet( + val text: String, + val keyword: String, + val keywordIdx: Int, + val includesFirstLine: Boolean, + val includesLastLine: Boolean, +) diff --git a/app/src/main/java/com/philkes/notallyx/utils/changehistory/EditTextWithHistoryChange.kt b/app/src/main/java/com/philkes/notallyx/utils/changehistory/EditTextWithHistoryChange.kt index b6193813..dc9718e0 100644 --- a/app/src/main/java/com/philkes/notallyx/utils/changehistory/EditTextWithHistoryChange.kt +++ b/app/src/main/java/com/philkes/notallyx/utils/changehistory/EditTextWithHistoryChange.kt @@ -3,8 +3,8 @@ package com.philkes.notallyx.utils.changehistory import android.text.Editable import androidx.core.text.getSpans import com.philkes.notallyx.presentation.clone -import com.philkes.notallyx.presentation.view.misc.HighlightSpan import com.philkes.notallyx.presentation.view.misc.StylableEditTextWithHistory +import com.philkes.notallyx.presentation.view.misc.highlightableview.HighlightSpan class EditTextWithHistoryChange( private val editText: StylableEditTextWithHistory, diff --git a/app/src/main/res/layout/recycler_base_note.xml b/app/src/main/res/layout/recycler_base_note.xml index 97c6465d..6a42593a 100644 --- a/app/src/main/res/layout/recycler_base_note.xml +++ b/app/src/main/res/layout/recycler_base_note.xml @@ -80,7 +80,7 @@ android:paddingEnd="16dp" app:layout_constraintTop_toBottomOf="@id/Space"> - - - - - - - - - - - -