Optimize partition search

This commit is contained in:
Hasinjato 2026-10-08 12:31:09 +03:00
parent 334f1cb083
commit bbcb59b7e6
2 changed files with 138 additions and 38 deletions

View file

@ -17,12 +17,50 @@ data class DrawerItem(
val firstNotes: String = "", val firstNotes: String = "",
val path: String val path: String
) { ) {
private data class TitleComponents(
val prefix: String,
val number: String,
val version: String?
)
private fun parseComponents(str: String): TitleComponents? {
// préfixe (lettres), numéro (chiffres), version optionnelle
val regex = Regex("""^([a-zA-Z]+)[-_\s]*(\d+)(?:[-_\s]*(\d+))?""")
val match = regex.find(str.trim()) ?: return null
val prefix = match.groupValues[1].lowercase()
val number = match.groupValues[2]
val version = match.groupValues[3].ifEmpty { null }
return TitleComponents(prefix, number, version)
}
fun matchesQuery(query: String): Boolean { fun matchesQuery(query: String): Boolean {
if (query.isBlank()) return true if (query.isBlank()) return true
val cleanQuery = query.normalizeForSearch() val cleanQuery = query.normalizeForSearch()
val cleanTitle = title.normalizeForSearch() val cleanTitle = title.normalizeForSearch()
val cleanContent = contentTitle.normalizeForSearch() val cleanContent = contentTitle.normalizeForSearch()
return (cleanTitle + cleanContent).contains(cleanQuery)
if ((cleanTitle + cleanContent).contains(cleanQuery) || (cleanContent + cleanTitle).contains(cleanQuery)) {
return true
}
val queryComponents = parseComponents(query)
val titleComponents = parseComponents(title)
if (queryComponents != null && titleComponents != null) {
if (queryComponents.prefix == titleComponents.prefix) {
return true
}
}
val queryLetters = query.filter { it.isLetter() }.lowercase()
val queryDigits = query.filter { it.isDigit() }
if (queryLetters.isNotEmpty() && queryDigits.isNotEmpty()) {
val lettersMatch = cleanContent.contains(queryLetters) || cleanTitle.contains(queryLetters)
val digitsMatch = title.contains(queryDigits) || cleanTitle.contains(queryDigits) || cleanContent.contains(queryDigits)
return lettersMatch && digitsMatch
}
return false
} }
fun getSearchScore(query: String): Int { fun getSearchScore(query: String): Int {
@ -32,20 +70,66 @@ data class DrawerItem(
val cleanTitle = title.normalizeForSearch() val cleanTitle = title.normalizeForSearch()
val cleanContent = contentTitle.normalizeForSearch() val cleanContent = contentTitle.normalizeForSearch()
val queryComponents = parseComponents(query)
val titleComponents = parseComponents(title)
if (queryComponents != null && titleComponents != null) {
val samePrefix = queryComponents.prefix == titleComponents.prefix
val sameNumber = queryComponents.number == titleComponents.number
val sameVersion = queryComponents.version == titleComponents.version
if (samePrefix && sameNumber && sameVersion) {
return 3000
}
if (samePrefix && sameNumber) {
return 2500
}
if (samePrefix && !sameNumber) {
if (cleanTitle == cleanQuery) {
return 1200
}
}
}
if (title.equals(query, ignoreCase = true)) return 2000
if (cleanTitle == cleanQuery) return 1000 if (cleanTitle == cleanQuery) return 1000
val titleDigitBlocks = Regex("\\d+").findAll(title).map { it.value }.toList()
val queryDigits = query.filter { it.isDigit() }
val queryLetters = query.filter { it.isLetter() }.lowercase()
if (cleanTitle.startsWith(cleanQuery)) { if (cleanTitle.startsWith(cleanQuery)) {
val nextCharIndex = cleanQuery.length if (queryDigits.isNotEmpty() && titleDigitBlocks.contains(queryDigits)) {
return 950
}
return 800
}
val isNextCharDigit = nextCharIndex < cleanTitle.length && cleanTitle[nextCharIndex].isDigit() if (queryLetters.isEmpty() && queryDigits.isNotEmpty()) {
if (titleDigitBlocks.contains(queryDigits)) return 1100
if (cleanTitle.contains(queryDigits)) return 400
if (cleanContent.contains(queryDigits)) return 200
}
return if (isNextCharDigit) { if (queryLetters.isNotEmpty() && queryDigits.isNotEmpty()) {
100 val lettersInContent = cleanContent.contains(queryLetters)
} else { val lettersInTitle = cleanTitle.contains(queryLetters)
800 val digitsInTitleBlock = titleDigitBlocks.contains(queryDigits)
val digitsInTitle = cleanTitle.contains(queryDigits)
if ((lettersInContent || lettersInTitle) && digitsInTitleBlock) {
return 850
}
if ((lettersInContent || lettersInTitle) && digitsInTitle) {
return 500
} }
} }
if (cleanTitle.contains(cleanQuery)) return 50
if (cleanContent.contains(cleanQuery)) return 10 if (cleanTitle.contains(cleanQuery)) return 600
if (cleanContent.contains(cleanQuery)) return 100
return 0 return 0
} }

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
@ -1093,11 +1094,8 @@ fun MainScreenWithDrawer(
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
modifier = Modifier.fillMaxHeight() modifier = Modifier.fillMaxHeight()
) { ) {
val sortedSongs = remember(filteredSongs) { val sortedSongs = remember(filteredSongs, textInput) {
filteredSongs.sortedWith( val naturalComparator = Comparator<DrawerItem> { a, b ->
compareBy<DrawerItem> { item ->
!item.path.startsWith("assets://")
}.thenComparing { a, b ->
val regex = Regex("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)") val regex = Regex("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")
val chunksA = a.title.lowercase().split(regex) val chunksA = a.title.lowercase().split(regex)
val chunksB = b.title.lowercase().split(regex) val chunksB = b.title.lowercase().split(regex)
@ -1115,15 +1113,33 @@ fun MainScreenWithDrawer(
} }
if (result != 0) break if (result != 0) break
} }
if (result == 0) { if (result == 0) chunksA.size.compareTo(chunksB.size) else result
chunksA.size.compareTo(chunksB.size) }
if (textInput.isBlank()) {
filteredSongs.sortedWith(
compareBy<DrawerItem> { item -> !item.path.startsWith("assets://") }
.thenComparing(naturalComparator)
)
} else { } else {
result filteredSongs.sortedWith(
} compareByDescending<DrawerItem> { item -> item.getSearchScore(textInput) }
} .thenBy { item -> !item.path.startsWith("assets://") }
.thenComparing(naturalComparator)
) )
} }
LazyColumn(Modifier.fillMaxSize()) { }
val searchListState = rememberLazyListState()
LaunchedEffect(textInput) {
if (sortedSongs.isNotEmpty()) {
searchListState.scrollToItem(0)
}
}
LazyColumn(
state = searchListState,
modifier = Modifier.fillMaxSize()
) {
if (sortedSongs.isEmpty() && textInput.isNotEmpty()) { if (sortedSongs.isEmpty() && textInput.isNotEmpty()) {
item { item {
ListItem( ListItem(