[feat] List natural sorting

This commit is contained in:
acite
2025-08-28 00:49:59 +08:00
parent 1a301770e2
commit e249ae27c9
2 changed files with 77 additions and 2 deletions

View File

@@ -0,0 +1,74 @@
package com.acitelight.aether.helper
import com.acitelight.aether.model.Video
import java.text.Collator
import java.util.Locale
fun MutableList<Video>.insertInNaturalOrder(n: Video) {
// Windows sorting is locale-sensitive. Use the default locale.
val collator = Collator.getInstance(Locale.getDefault())
val naturalComparator = Comparator<String> { s1, s2 ->
val naturalOrderComparator = fun(str1: String, str2: String): Int {
// Function to compare segments (numeric vs. non-numeric)
val compareSegments = fun(segment1: String, segment2: String, isNumeric: Boolean): Int {
return if (isNumeric) {
val num1 = segment1.toLongOrNull() ?: 0
val num2 = segment2.toLongOrNull() ?: 0
num1.compareTo(num2)
} else {
collator.compare(segment1, segment2)
}
}
// Regex to split string into numeric and non-numeric parts
val regex = "(\\d+)|(\\D+)".toRegex()
val matches1 = regex.findAll(str1).toList()
val matches2 = regex.findAll(str2).toList()
var i = 0
while (i < matches1.size && i < matches2.size) {
val match1 = matches1[i]
val match2 = matches2[i]
val isNumeric1 = match1.groupValues[1].isNotEmpty()
val isNumeric2 = match2.groupValues[1].isNotEmpty()
when {
isNumeric1 && isNumeric2 -> {
val result = compareSegments(match1.value, match2.value, true)
if (result != 0) return result
}
!isNumeric1 && !isNumeric2 -> {
val result = compareSegments(match1.value, match2.value, false)
if (result != 0) return result
}
isNumeric1 -> return -1 // Numeric part comes before non-numeric
isNumeric2 -> return 1
}
i++
}
// If one string is a prefix of the other, the shorter one comes first
return str1.length.compareTo(str2.length)
}
naturalOrderComparator(s1, s2)
}
var inserted = false
// Find the correct insertion point
for (i in this.indices) {
if (naturalComparator.compare(n.video.name, this[i].video.name) <= 0) {
this.add(i, n)
inserted = true
break
}
}
// If it's the largest, add to the end
if (!inserted) {
this.add(n)
}
}

View File

@@ -15,6 +15,7 @@ import androidx.lifecycle.viewModelScope
import coil3.ImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import com.acitelight.aether.dataStore
import com.acitelight.aether.helper.insertInNaturalOrder
import com.acitelight.aether.model.Video
import com.acitelight.aether.service.ApiClient.createOkHttp
import com.acitelight.aether.service.MediaManager
@@ -53,7 +54,7 @@ class VideoScreenViewModel(application: Application) : AndroidViewModel(applicat
}
MediaManager.listVideos(classes[0]){
v -> classesMap[classes[0]]?.add(v)
v -> classesMap[classes[0]]?.insertInNaturalOrder(v)
}
}
@@ -65,7 +66,7 @@ class VideoScreenViewModel(application: Application) : AndroidViewModel(applicat
MediaManager.listVideos(classes[index])
{
v -> classesMap[classes[index]]?.add(v)
v -> classesMap[classes[index]]?.insertInNaturalOrder(v)
}
}
}