Skip to content

Commit f9e9774

Browse files
authored
Merge pull request #1604 from zsmb13/search-diacritics
Ignore diacritics/accents in search when input doesn't contain them
2 parents e35c67b + 0ab640e commit f9e9774

File tree

3 files changed

+44
-9
lines changed

3 files changed

+44
-9
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ compose-material3 = "1.3.1"
2121
composeWindowSize = "0.5.0"
2222
credentials = "1.5.0"
2323
decompose = "3.3.0"
24+
doistx-normalize = "1.2.0"
2425
essenty = "2.5.0"
2526
googleid = "1.1.1"
2627
horologist = "0.7.10-alpha"
@@ -126,6 +127,7 @@ compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview"
126127
decompose-decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
127128
decompose-extensions-compose = { module = "com.arkivanov.decompose:extensions-compose", version.ref = "decompose" }
128129
decompose-extensions-compose-experimental = { module = "com.arkivanov.decompose:extensions-compose-experimental", version.ref = "decompose" }
130+
doistx-normalize = { module = "com.doist.x:normalize", version.ref = "doistx-normalize" }
129131
essenty-lifecycle = { module = "com.arkivanov.essenty:lifecycle", version.ref = "essenty" }
130132
essenty-backhandler = { module = "com.arkivanov.essenty:back-handler", version.ref = "essenty" }
131133
desugar = "com.android.tools:desugar_jdk_libs:2.1.3"

shared/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ kotlin {
100100
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.7.0")
101101

102102
api(libs.generativeai)
103+
104+
api(libs.doistx.normalize)
103105
}
104106
}
105107
commonTest {

shared/src/commonMain/kotlin/dev/johnoreilly/confetti/decompose/SearchComponent.kt

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import dev.johnoreilly.confetti.ConfettiRepository
66
import dev.johnoreilly.confetti.auth.User
77
import dev.johnoreilly.confetti.fragment.SessionDetails
88
import dev.johnoreilly.confetti.fragment.SpeakerDetails
9+
import doist.x.normalize.Form
10+
import doist.x.normalize.normalize
11+
import kotlinx.coroutines.Dispatchers
912
import kotlinx.coroutines.flow.Flow
1013
import kotlinx.coroutines.flow.MutableStateFlow
1114
import kotlinx.coroutines.flow.asStateFlow
1215
import kotlinx.coroutines.flow.combine
1316
import kotlinx.coroutines.flow.filterIsInstance
17+
import kotlinx.coroutines.flow.flowOn
1418
import kotlinx.coroutines.flow.map
1519
import kotlinx.coroutines.flow.update
1620
import kotlinx.coroutines.launch
@@ -89,6 +93,7 @@ class DefaultSearchComponent(
8993
.combine(search) { sessions, search ->
9094
sessions.filter { filterSessions(it, search) }
9195
}
96+
.flowOn(Dispatchers.Default)
9297

9398
override val bookmarks: Flow<Set<String>> = successSessions
9499
.map { state -> state.bookmarks }
@@ -98,27 +103,53 @@ class DefaultSearchComponent(
98103
.combine(search) { sessions, search ->
99104
sessions.filter { filterSpeakers(it, search) }
100105
}
106+
.flowOn(Dispatchers.Default)
101107

102108
private fun filterSpeakers(details: SpeakerDetails, filter: String): Boolean {
103109
if (filter.isBlank()) return false
104110

105111
val ignoreCase = true
106-
return details.name.contains(filter, ignoreCase) ||
107-
details.bio.orEmpty().contains(filter, ignoreCase) ||
108-
details.city.orEmpty().contains(filter, ignoreCase) ||
109-
details.company.orEmpty().contains(filter, ignoreCase)
112+
val ignoreDiacritics = !filter.containsDiacritics()
113+
114+
return sequenceOf(
115+
details.name,
116+
details.bio.orEmpty(),
117+
details.city.orEmpty(),
118+
details.company.orEmpty(),
119+
).any { it.adjustWith(ignoreDiacritics).contains(filter, ignoreCase) }
110120
}
111121

122+
private val diacriticsRegex = "\\p{Mn}+".toRegex()
123+
124+
fun String.containsDiacritics(): Boolean = normalizeNfd().contains(diacriticsRegex)
125+
126+
fun String.adjustWith(ignoreDiacritics: Boolean): String {
127+
return if (ignoreDiacritics) {
128+
normalizeNfd().replace(diacriticsRegex, "")
129+
} else {
130+
this
131+
}
132+
}
133+
134+
private fun String.normalizeNfd(): String = normalize(Form.NFD)
135+
112136
private fun filterSessions(details: SessionDetails, filter: String): Boolean {
113137
if (filter.isBlank()) return false
114138

115139
val ignoreCase = true
116-
return details.title.contains(filter, ignoreCase) ||
117-
details.sessionDescription.orEmpty().contains(filter, ignoreCase) ||
118-
details.room?.name.orEmpty().contains(filter, ignoreCase) ||
119-
details.speakers.any { speaker ->
120-
speaker.speakerDetails.name.contains(filter, ignoreCase)
140+
val ignoreDiacritics = !filter.containsDiacritics()
141+
142+
return sequence {
143+
yield(details.title)
144+
yield(details.sessionDescription.orEmpty())
145+
yield(details.room?.name.orEmpty())
146+
147+
details.speakers.forEach {
148+
yield(it.speakerDetails.name)
121149
}
150+
}.any {
151+
it.adjustWith(ignoreDiacritics).contains(filter, ignoreCase)
152+
}
122153
}
123154

124155
override fun onSearchChange(query: String) {

0 commit comments

Comments
 (0)