Skip to content

Commit

Permalink
Add support for search occurrences count and search navigation (#338)
Browse files Browse the repository at this point in the history
* Add support for search occurrences count and search navigation

* Separate out search logic

* Update normalise method to use correct replacement
  • Loading branch information
kpchandora authored Feb 22, 2025
1 parent 2b83a3b commit 7e72a23
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,15 @@ class Builder(val context: Context) {
is String -> SpannableString(s).apply {
setSpan(o, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}

is SpannableStringBuilder -> s.apply {
setSpan(o, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}

is SpannableString -> s.apply {
setSpan(o, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}

else -> throw IllegalArgumentException("unhandled type $o")
}

Expand Down Expand Up @@ -92,14 +95,20 @@ class Builder(val context: Context) {

fun highlight(span: CharSequence, search: String?): CharSequence {
if (search.isNullOrEmpty()) return span
val normalizedText = Normalizer.normalize(span, Normalizer.Form.NFD)
.replace("\\p{InCombiningDiacriticalMarks}+".toRegex(), "")
.lowercase()
val normalizedText = span.normalise().lowercase()

val startIndexes = normalizedText.allOccurrences(search)
if (startIndexes.isNotEmpty()) {
return highlight(span, search, startIndexes)
}
return span
}

fun highlight(span: CharSequence, search: String?, indexes: List<Int>): CharSequence {
if (search.isNullOrEmpty()) return span
if (indexes.isNotEmpty()) {
val highlighted: Spannable = SpannableString(span)
startIndexes.forEach {
indexes.forEach {
highlighted.setSpan(
BackgroundColorSpan(context.color(R.color.pluto___text_highlight)),
it,
Expand All @@ -112,6 +121,12 @@ class Builder(val context: Context) {
return span
}

fun occurrences(span: CharSequence, search: String?): List<Int> {
if (search.isNullOrEmpty()) return emptyList()
val normalizedText = span.normalise().lowercase()
return normalizedText.allOccurrences(search)
}

fun clickable(span: CharSequence, listener: ClickableSpan): CharSequence {
return span(span, listener)
}
Expand All @@ -124,6 +139,11 @@ class Builder(val context: Context) {
return span(span, StyleSpan(Typeface.ITALIC))
}

private fun CharSequence.normalise(): String {
return Normalizer.normalize(this, Normalizer.Form.NFD)
.replace("\\p{InCombiningDiacriticalMarks}+".toRegex(), "")
}

fun build(): CharSequence {
return spanBuilder
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ internal class ContentFragment : Fragment(R.layout.pluto_network___fragment_cont
private val argumentData: ContentFormatterData?
get() = arguments?.getParcelable(DATA)

private var currentHighlightIndex = 0
private var occurrences = emptyList<Int>()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onBackPressed { handleBackPress() }
Expand All @@ -50,20 +53,33 @@ internal class ContentFragment : Fragment(R.layout.pluto_network___fragment_cont
binding.editSearch.doOnTextChanged { text, _, _, _ ->
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
text?.toString()?.let { search ->
argumentData?.let {
binding.content.setSpan {
append(highlight(it.content, search.trim()))
append("\n")
}
}

scrollToText(search.trim())
processSearch(search)
}
}
}

binding.previousHighlight.setOnDebounceClickListener {
if (occurrences.isNotEmpty()) {
currentHighlightIndex = (currentHighlightIndex - 1 + occurrences.size) % occurrences.size
scrollToText(occurrences[currentHighlightIndex], binding.editSearch.text.toString())
}
}

binding.nextHighlight.setOnDebounceClickListener {
if (occurrences.isNotEmpty()) {
currentHighlightIndex = (currentHighlightIndex + 1) % occurrences.size
scrollToText(occurrences[currentHighlightIndex], binding.editSearch.text.toString())
}
}

binding.share.setOnDebounceClickListener {
argumentData?.let {
contentSharer.share(Shareable(title = "Share content", content = it.content.toString()))
contentSharer.share(
Shareable(
title = "Share content",
content = it.content.toString()
)
)
}
}
argumentData?.let {
Expand All @@ -88,16 +104,34 @@ internal class ContentFragment : Fragment(R.layout.pluto_network___fragment_cont
}
}

private fun processSearch(search: String) {
currentHighlightIndex = 0
argumentData?.let {
binding.content.setSpan {
occurrences = occurrences(it.content, search.trim())
append(highlight(it.content, search.trim(), occurrences))
append("\n")
binding.searchCount.visibility = if (search.isEmpty()) View.GONE else VISIBLE
binding.searchCount.text = occurrences.size.toString()
val highlightsVisibility = if (occurrences.size < 2) View.GONE else VISIBLE
binding.previousHighlight.visibility = highlightsVisibility
binding.nextHighlight.visibility = highlightsVisibility
}
}

scrollToText(currentHighlightIndex, search.trim())
}

/**
* helps to auto scroll to target search
*/
private fun scrollToText(targetText: String) {
private fun scrollToText(startIndex: Int, targetText: String) {
if (targetText.isEmpty()) {
return
}

val contentText = binding.content.getText().toString().lowercase()
val index = contentText.indexOf(targetText.lowercase())
val index = contentText.indexOf(targetText.lowercase(), startIndex)

if (index != -1) {
binding.content.post {
Expand All @@ -108,7 +142,7 @@ internal class ContentFragment : Fragment(R.layout.pluto_network___fragment_cont
val y = layout.getLineTop(lineNumber)

binding.horizontalScroll.smoothScrollTo(x / 2, 0)
binding.contentNestedScrollView.smoothScrollTo(0, y / 2)
binding.contentNestedScrollView.smoothScrollTo(0, y)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="@color/pluto___dark_80"
android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z" />

</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">

<path
android:fillColor="@color/pluto___dark_80"
android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z" />

</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,57 @@
android:textColorHint="@color/pluto___text_dark_40"
android:textSize="@dimen/pluto___text_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/clearSearch"
app:layout_constraintEnd_toStartOf="@+id/nextHighlight"
app:layout_constraintStart_toEndOf="@+id/closeSearch"
app:layout_constraintTop_toTopOf="parent" />

<ImageView
android:id="@+id/nextHighlight"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:contentDescription="@string/pluto_network___search_next_highlight"
android:foreground="?android:attr/selectableItemBackground"
android:paddingHorizontal="@dimen/pluto___margin_mini"
android:src="@drawable/pluto_network___ic_arrow_down"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/previousHighlight"
app:layout_constraintStart_toEndOf="@id/editSearch"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />

<ImageView
android:id="@+id/previousHighlight"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:contentDescription="@string/pluto_network___search_previous_highlight"
android:foreground="?android:attr/selectableItemBackground"
android:paddingHorizontal="@dimen/pluto___margin_mini"
android:src="@drawable/pluto_network___ic_arrow_up"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/searchCount"
app:layout_constraintStart_toEndOf="@id/nextHighlight"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />

<TextView
android:id="@+id/searchCount"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:fontFamily="@font/muli_semibold"
android:gravity="center"
android:paddingHorizontal="@dimen/pluto___margin_xsmall"
android:textColor="@color/pluto___text_dark_40"
android:textSize="@dimen/pluto___text_small"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/clearSearch"
app:layout_constraintStart_toEndOf="@id/previousHighlight"
app:layout_constraintTop_toTopOf="parent"
tools:text="10"
tools:visibility="visible" />

<ImageView
android:id="@+id/clearSearch"
android:layout_width="wrap_content"
Expand All @@ -167,6 +214,7 @@
android:paddingHorizontal="@dimen/pluto___margin_xsmall"
android:src="@drawable/pluto_network___ic_close_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/searchCount"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@
<item quantity="one">1 items</item>
<item quantity="other">%d items</item>
</plurals>
<string name="pluto_network___search_next_highlight">Next highlight</string>
<string name="pluto_network___search_previous_highlight">Previous highlight</string>
</resources>

0 comments on commit 7e72a23

Please sign in to comment.