Skip to content
Snippets Groups Projects
Commit af3831c1 authored by Nicolas Pomepuy's avatar Nicolas Pomepuy Committed by Duncan McNamara
Browse files

Change the browser filename sort to a more "natural" one

Fixes #2631
parent 135a2f05
No related branches found
No related tags found
1 merge request!1481Change the browser filename sort to a more "natural" one
......@@ -74,15 +74,22 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
val descriptionUpdate = MutableLiveData<Pair<Int, String>>()
internal val medialibrary = Medialibrary.getInstance()
private val comparator : Comparator<MediaLibraryItem>?
get() = when {
fun isComparatorAboutFilename() = when {
Settings.showTvUi && sort == Medialibrary.SORT_ALPHA && desc -> false
Settings.showTvUi && sort == Medialibrary.SORT_ALPHA && !desc -> false
sort == Medialibrary.SORT_ALPHA && desc -> false
sort == Medialibrary.SORT_ALPHA && !desc -> false
(sort == Medialibrary.SORT_FILENAME || sort == Medialibrary.SORT_DEFAULT) && desc -> true
else -> true
}
fun getComparator(nbOfDigits: Int): Comparator<MediaLibraryItem>? = when {
Settings.showTvUi && sort == Medialibrary.SORT_ALPHA && desc -> tvDescComp
Settings.showTvUi && sort == Medialibrary.SORT_ALPHA && !desc -> tvAscComp
url != null && Uri.parse(url)?.scheme == "upnp" -> null
sort == Medialibrary.SORT_ALPHA && desc -> descComp
sort == Medialibrary.SORT_ALPHA && !desc -> ascComp
(sort == Medialibrary.SORT_FILENAME || sort == Medialibrary.SORT_DEFAULT) && desc -> filenameDescComp
else -> filenameAscComp
(sort == Medialibrary.SORT_FILENAME || sort == Medialibrary.SORT_DEFAULT) && desc -> getFilenameDescComp(nbOfDigits)
else -> getFilenameAscComp(nbOfDigits)
}
init {
......@@ -178,7 +185,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
* @param files the files to sort
*/
fun sort(files: MutableList<MediaLibraryItem>) {
comparator?.let { files.apply { this.sortWith(it) } } ?: if (desc) files.apply { reverse() }
getComparator(if (isComparatorAboutFilename()) files.determineMaxNbOfDigits() else 0)?.let { files.apply { this.sortWith(it) } } ?: if (desc) files.apply { reverse() }
}
suspend fun browseUrl(url: String): List<MediaLibraryItem> {
......@@ -232,7 +239,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
}.buffer(Channel.UNLIMITED)
open fun addMedia(media: MediaLibraryItem) {
comparator?.let { dataset.add(media, it) } ?: dataset.add(media)
getComparator(if (isComparatorAboutFilename()) dataset.value.determineMaxNbOfDigits() else 0)?.let { dataset.add(media, it) } ?: dataset.add(media)
}
open fun refresh() {
......
......@@ -58,6 +58,7 @@ import java.net.URI
import java.net.URISyntaxException
import java.security.SecureRandom
import java.util.*
import kotlin.math.max
import kotlin.math.roundToInt
fun String.validateLocation(): Boolean {
......@@ -381,6 +382,66 @@ fun <T> Flow<T>.launchWhenStarted(scope: LifecycleCoroutineScope): Job = scope.l
collect() // tail-call
}
/**
* Sanitize a string by adding enough "0" at the start
* to make a "natural" alphanumeric comparison (1, 2, 10, 11, 20) instead of a strict one (1, 10, 11, 21, 20)
*
* @param nbOfDigits the number of digits to reach
* @return a string having exactly [nbOfDigits] digits at the start
*/
fun String?.sanitizeStringForAlphaCompare(nbOfDigits: Int): String? {
if (this == null) return null
if (first().isDigit()) return buildString {
for (i in 0 until (nbOfDigits - (getStartingNumber()?.numberOfDigits() ?: 0))) {
append("0")
}
append(this@sanitizeStringForAlphaCompare)
}
return this
}
/**
* Calculate the number of digits of an Int
*
* @return the number of digits of this Int
*/
fun Int.numberOfDigits(): Int = when (this) {
in -9..9 -> 1
else -> 1 + (this / 10).numberOfDigits()
}
/**
* Get the number described at the start of this String if any
*
* @return the starting number of this String, null if no number found
*/
fun String.getStartingNumber(): Int? {
return try {
buildString {
for (c in this@getStartingNumber)
if (c.isDigit())
append(c)
else break
}.toInt()
} catch (e: NumberFormatException) {
null
}
}
/**
* Determine the max number of digits iat the start of
* this lit items' filename
*
* @return a max number of digits
*/
fun List<MediaLibraryItem>.determineMaxNbOfDigits(): Int {
var numberOfPrepending = 0
forEach {
numberOfPrepending = max((it as? MediaWrapper)?.fileName?.getStartingNumber()?.numberOfDigits()
?: 0, numberOfPrepending)
}
return numberOfPrepending
}
fun Fragment.showParentFolder(media: MediaWrapper) {
val parent = MLServiceLocator.getAbstractMediaWrapper(media.uri.retrieveParent()).apply {
......
......@@ -283,26 +283,24 @@ val tvDescComp by lazy {
}
}
val filenameAscComp by lazy {
Comparator<MediaLibraryItem> { item1, item2 ->
val type1 = (item1 as? MediaWrapper)?.type
val type2 = (item2 as? MediaWrapper)?.type
if (type1 == MediaWrapper.TYPE_DIR && type2 != MediaWrapper.TYPE_DIR) return@Comparator -1
else if (type1 != MediaWrapper.TYPE_DIR && type2 == MediaWrapper.TYPE_DIR) return@Comparator 1
val filename1 = (item1 as? MediaWrapper)?.fileName ?: (item1 as? Storage)?.title
val filename2 = (item2 as? MediaWrapper)?.fileName ?: (item2 as? Storage)?.title
filename1?.lowercase(Locale.getDefault())?.compareTo(filename2?.lowercase(Locale.getDefault()) ?: "") ?: -1
}
fun getFilenameAscComp(nbOfDigits: Int): Comparator<MediaLibraryItem> = Comparator<MediaLibraryItem> { item1, item2 ->
val type1 = (item1 as? MediaWrapper)?.type
val type2 = (item2 as? MediaWrapper)?.type
if (type1 == MediaWrapper.TYPE_DIR && type2 != MediaWrapper.TYPE_DIR) return@Comparator -1
else if (type1 != MediaWrapper.TYPE_DIR && type2 == MediaWrapper.TYPE_DIR) return@Comparator 1
val filename1 = (item1 as? MediaWrapper)?.fileName ?: (item1 as? Storage)?.title
val filename2 = (item2 as? MediaWrapper)?.fileName ?: (item2 as? Storage)?.title
filename1?.lowercase(Locale.getDefault()).sanitizeStringForAlphaCompare(nbOfDigits)?.compareTo(filename2?.lowercase(Locale.getDefault()).sanitizeStringForAlphaCompare(nbOfDigits)
?: "") ?: -1
}
val filenameDescComp by lazy {
Comparator<MediaLibraryItem> { item1, item2 ->
val type1 = (item1 as? MediaWrapper)?.type
val type2 = (item2 as? MediaWrapper)?.type
if (type1 == MediaWrapper.TYPE_DIR && type2 != MediaWrapper.TYPE_DIR) return@Comparator -1
else if (type1 != MediaWrapper.TYPE_DIR && type2 == MediaWrapper.TYPE_DIR) return@Comparator 1
val filename1 = (item1 as? MediaWrapper)?.fileName ?: (item1 as? Storage)?.title
val filename2 = (item2 as? MediaWrapper)?.fileName ?: (item2 as? Storage)?.title
filename2?.lowercase(Locale.getDefault())?.compareTo(filename1?.lowercase(Locale.getDefault()) ?: "") ?: -1
}
fun getFilenameDescComp(nbOfDigits: Int): Comparator<MediaLibraryItem> = Comparator<MediaLibraryItem> { item1, item2 ->
val type1 = (item1 as? MediaWrapper)?.type
val type2 = (item2 as? MediaWrapper)?.type
if (type1 == MediaWrapper.TYPE_DIR && type2 != MediaWrapper.TYPE_DIR) return@Comparator -1
else if (type1 != MediaWrapper.TYPE_DIR && type2 == MediaWrapper.TYPE_DIR) return@Comparator 1
val filename1 = (item1 as? MediaWrapper)?.fileName ?: (item1 as? Storage)?.title
val filename2 = (item2 as? MediaWrapper)?.fileName ?: (item2 as? Storage)?.title
filename2?.lowercase(Locale.getDefault()).sanitizeStringForAlphaCompare(nbOfDigits)?.compareTo(filename1?.lowercase(Locale.getDefault()).sanitizeStringForAlphaCompare(nbOfDigits)
?: "") ?: -1
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment