Optimize partition search
This commit is contained in:
parent
334f1cb083
commit
bbcb59b7e6
2 changed files with 138 additions and 38 deletions
|
|
@ -17,12 +17,50 @@ data class DrawerItem(
|
|||
val firstNotes: 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 {
|
||||
if (query.isBlank()) return true
|
||||
val cleanQuery = query.normalizeForSearch()
|
||||
val cleanTitle = title.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 {
|
||||
|
|
@ -32,20 +70,66 @@ data class DrawerItem(
|
|||
val cleanTitle = title.normalizeForSearch()
|
||||
val cleanContent = contentTitle.normalizeForSearch()
|
||||
|
||||
if (cleanTitle == cleanQuery) return 1000
|
||||
if (cleanTitle.startsWith(cleanQuery)) {
|
||||
val nextCharIndex = cleanQuery.length
|
||||
val queryComponents = parseComponents(query)
|
||||
val titleComponents = parseComponents(title)
|
||||
|
||||
val isNextCharDigit = nextCharIndex < cleanTitle.length && cleanTitle[nextCharIndex].isDigit()
|
||||
if (queryComponents != null && titleComponents != null) {
|
||||
val samePrefix = queryComponents.prefix == titleComponents.prefix
|
||||
val sameNumber = queryComponents.number == titleComponents.number
|
||||
val sameVersion = queryComponents.version == titleComponents.version
|
||||
|
||||
return if (isNextCharDigit) {
|
||||
100
|
||||
} else {
|
||||
800
|
||||
if (samePrefix && sameNumber && sameVersion) {
|
||||
return 3000
|
||||
}
|
||||
|
||||
if (samePrefix && sameNumber) {
|
||||
return 2500
|
||||
}
|
||||
|
||||
if (samePrefix && !sameNumber) {
|
||||
if (cleanTitle == cleanQuery) {
|
||||
return 1200
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cleanTitle.contains(cleanQuery)) return 50
|
||||
if (cleanContent.contains(cleanQuery)) return 10
|
||||
|
||||
if (title.equals(query, ignoreCase = true)) return 2000
|
||||
|
||||
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 (queryDigits.isNotEmpty() && titleDigitBlocks.contains(queryDigits)) {
|
||||
return 950
|
||||
}
|
||||
return 800
|
||||
}
|
||||
|
||||
if (queryLetters.isEmpty() && queryDigits.isNotEmpty()) {
|
||||
if (titleDigitBlocks.contains(queryDigits)) return 1100
|
||||
if (cleanTitle.contains(queryDigits)) return 400
|
||||
if (cleanContent.contains(queryDigits)) return 200
|
||||
}
|
||||
|
||||
if (queryLetters.isNotEmpty() && queryDigits.isNotEmpty()) {
|
||||
val lettersInContent = cleanContent.contains(queryLetters)
|
||||
val lettersInTitle = cleanTitle.contains(queryLetters)
|
||||
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 600
|
||||
if (cleanContent.contains(cleanQuery)) return 100
|
||||
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
|
|
@ -1093,37 +1094,52 @@ fun MainScreenWithDrawer(
|
|||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
|
||||
modifier = Modifier.fillMaxHeight()
|
||||
) {
|
||||
val sortedSongs = remember(filteredSongs) {
|
||||
filteredSongs.sortedWith(
|
||||
compareBy<DrawerItem> { item ->
|
||||
!item.path.startsWith("assets://")
|
||||
}.thenComparing { a, b ->
|
||||
val regex = Regex("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")
|
||||
val chunksA = a.title.lowercase().split(regex)
|
||||
val chunksB = b.title.lowercase().split(regex)
|
||||
var result = 0
|
||||
val size = minOf(chunksA.size, chunksB.size)
|
||||
for (i in 0 until size) {
|
||||
val chunkA = chunksA[i]
|
||||
val chunkB = chunksB[i]
|
||||
val numA = chunkA.toLongOrNull()
|
||||
val numB = chunkB.toLongOrNull()
|
||||
result = if (numA != null && numB != null) {
|
||||
numA.compareTo(numB)
|
||||
} else {
|
||||
chunkA.compareTo(chunkB)
|
||||
}
|
||||
if (result != 0) break
|
||||
}
|
||||
if (result == 0) {
|
||||
chunksA.size.compareTo(chunksB.size)
|
||||
val sortedSongs = remember(filteredSongs, textInput) {
|
||||
val naturalComparator = Comparator<DrawerItem> { a, b ->
|
||||
val regex = Regex("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")
|
||||
val chunksA = a.title.lowercase().split(regex)
|
||||
val chunksB = b.title.lowercase().split(regex)
|
||||
var result = 0
|
||||
val size = minOf(chunksA.size, chunksB.size)
|
||||
for (i in 0 until size) {
|
||||
val chunkA = chunksA[i]
|
||||
val chunkB = chunksB[i]
|
||||
val numA = chunkA.toLongOrNull()
|
||||
val numB = chunkB.toLongOrNull()
|
||||
result = if (numA != null && numB != null) {
|
||||
numA.compareTo(numB)
|
||||
} else {
|
||||
result
|
||||
chunkA.compareTo(chunkB)
|
||||
}
|
||||
if (result != 0) break
|
||||
}
|
||||
)
|
||||
if (result == 0) chunksA.size.compareTo(chunksB.size) else result
|
||||
}
|
||||
|
||||
if (textInput.isBlank()) {
|
||||
filteredSongs.sortedWith(
|
||||
compareBy<DrawerItem> { item -> !item.path.startsWith("assets://") }
|
||||
.thenComparing(naturalComparator)
|
||||
)
|
||||
} else {
|
||||
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()) {
|
||||
item {
|
||||
ListItem(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue