[Android] Change the browser filename sort to a more "natural" one

Nicolas Pomepuy git at videolan.org
Wed Sep 21 11:55:25 UTC 2022


vlc-android | branch: master | Nicolas Pomepuy <nicolas at videolabs.io> | Tue Sep 20 08:03:10 2022 +0200| [af3831c124c22f5a19c6aa856c490b4dc50e3f4b] | committer: Duncan McNamara

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

Fixes #2631

> https://code.videolan.org/videolan/vlc-android/commit/af3831c124c22f5a19c6aa856c490b4dc50e3f4b
---

 .../org/videolan/vlc/providers/BrowserProvider.kt  | 19 ++++---
 .../src/org/videolan/vlc/util/Kextensions.kt       | 61 ++++++++++++++++++++++
 .../src/org/videolan/vlc/util/ModelsHelper.kt      | 38 +++++++-------
 3 files changed, 92 insertions(+), 26 deletions(-)

diff --git a/application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt b/application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt
index d3d2a1d60..c65704174 100644
--- a/application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt
+++ b/application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt
@@ -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() {
diff --git a/application/vlc-android/src/org/videolan/vlc/util/Kextensions.kt b/application/vlc-android/src/org/videolan/vlc/util/Kextensions.kt
index 72180a879..6f08794aa 100644
--- a/application/vlc-android/src/org/videolan/vlc/util/Kextensions.kt
+++ b/application/vlc-android/src/org/videolan/vlc/util/Kextensions.kt
@@ -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 at 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 at 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 {
diff --git a/application/vlc-android/src/org/videolan/vlc/util/ModelsHelper.kt b/application/vlc-android/src/org/videolan/vlc/util/ModelsHelper.kt
index 01c2c3d3c..3c2245353 100644
--- a/application/vlc-android/src/org/videolan/vlc/util/ModelsHelper.kt
+++ b/application/vlc-android/src/org/videolan/vlc/util/ModelsHelper.kt
@@ -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 at Comparator -1
-        else if (type1 != MediaWrapper.TYPE_DIR && type2 == MediaWrapper.TYPE_DIR) return at 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 at Comparator -1
+    else if (type1 != MediaWrapper.TYPE_DIR && type2 == MediaWrapper.TYPE_DIR) return at 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 at Comparator -1
-        else if (type1 != MediaWrapper.TYPE_DIR && type2 == MediaWrapper.TYPE_DIR) return at 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 at Comparator -1
+    else if (type1 != MediaWrapper.TYPE_DIR && type2 == MediaWrapper.TYPE_DIR) return at 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



More information about the Android mailing list