@@ -6,11 +6,15 @@ import dev.johnoreilly.confetti.ConfettiRepository
66import dev.johnoreilly.confetti.auth.User
77import dev.johnoreilly.confetti.fragment.SessionDetails
88import dev.johnoreilly.confetti.fragment.SpeakerDetails
9+ import doist.x.normalize.Form
10+ import doist.x.normalize.normalize
11+ import kotlinx.coroutines.Dispatchers
912import kotlinx.coroutines.flow.Flow
1013import kotlinx.coroutines.flow.MutableStateFlow
1114import kotlinx.coroutines.flow.asStateFlow
1215import kotlinx.coroutines.flow.combine
1316import kotlinx.coroutines.flow.filterIsInstance
17+ import kotlinx.coroutines.flow.flowOn
1418import kotlinx.coroutines.flow.map
1519import kotlinx.coroutines.flow.update
1620import 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