[Android] Add comparator to sort media identically to the android auto keyboard

Robert Stone git at videolan.org
Fri Jul 9 09:12:04 UTC 2021


vlc-android | branch: master | Robert Stone <rhstone at gmail.com> | Tue Jul  6 22:15:09 2021 -0700| [3592eb89ed077872a06e8505659fa1a1c392509f] | committer: Nicolas Pomepuy

Add comparator to sort media identically to the android auto keyboard

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

 .../videolan/vlc/gui/helpers/MediaComparators.kt   | 71 ++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/application/vlc-android/src/org/videolan/vlc/gui/helpers/MediaComparators.kt b/application/vlc-android/src/org/videolan/vlc/gui/helpers/MediaComparators.kt
index b9684e025..b78acd9a1 100644
--- a/application/vlc-android/src/org/videolan/vlc/gui/helpers/MediaComparators.kt
+++ b/application/vlc-android/src/org/videolan/vlc/gui/helpers/MediaComparators.kt
@@ -20,10 +20,20 @@
 package org.videolan.vlc.gui.helpers
 
 import org.videolan.medialibrary.interfaces.media.MediaWrapper
+import org.videolan.medialibrary.media.MediaLibraryItem
+import java.text.Normalizer
 import java.util.*
 
 object MediaComparators {
 
+    private val englishArticles by lazy { arrayOf("a ", "an ", "the ") }
+    private val asciiAlphaNumeric by lazy {
+        BitSet().also { b -> "0123456789abcdefghijklmnopqrstuvwxyz".toCharArray().forEach { c -> b.set(c.toInt(), true) } }
+    }
+    private val asciiPunctuation by lazy {
+        BitSet().also { b -> "\t !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~".toCharArray().forEach { c -> b.set(c.toInt(), true) } }
+    }
+
     val BY_TRACK_NUMBER: Comparator<MediaWrapper> = Comparator { m1, m2 ->
         if (m1.discNumber < m2.discNumber) return at Comparator -1
         if (m1.discNumber > m2.discNumber) return at Comparator 1
@@ -34,4 +44,65 @@ object MediaComparators {
             0
     }
 
+    val ANDROID_AUTO: Comparator<MediaLibraryItem> = Comparator { item1, item2 ->
+        buildComparableTitle(item1.title).compareTo(buildComparableTitle(item2.title))
+    }
+
+    private fun buildComparableTitle(origTitle: String): String {
+        val tTitle = origTitle.trim()
+        if (tTitle.isEmpty()) return tTitle
+        /* Remove invalid leading characters and articles */
+        val invCharTitle = removeLeadingPunctuation(tTitle).toLowerCase(Locale.US)
+        val scrubbedTitle = formatArticles(invCharTitle, false).ifEmpty { invCharTitle }
+        /* Decompose the first letter to handle ä, ç, é, ô, etc. This yields two chars: an a-z letter, and
+         * a unicode combining character representing the diacritic mark. The combining character is dropped.
+         */
+        val nChars = Normalizer.normalize(scrubbedTitle[0].toString(), Normalizer.Form.NFD)
+        val firstChar = removeNonAlphaNumeric(nChars).ifEmpty { nChars }
+        /* Assemble the title */
+        return "${firstChar[0]}${scrubbedTitle.substring(1)}"
+    }
+
+    /**
+     * Functionally identical to "^(?i)(the|an|a)\\s+(.*)", and either returning "$2" or "$2, $1"
+     */
+    fun formatArticles(title: String, appendPrefix: Boolean): String {
+        for (article in englishArticles)
+            if (title.startsWith(article, true)) {
+                val suffix = title.substring(article.length).trim()
+                return if (appendPrefix) {
+                    val prefix = title.substring(0, article.length).trim()
+                    listOf(suffix, prefix).filter { it.isNotEmpty() }.joinToString(", ") { it }
+                } else suffix
+            }
+        return title
+    }
+
+    /**
+     * Remove all non-lowercase alphanumeric characters.
+     * Functionally identical to replaceAll("[^a-z0-9]+", "")
+     */
+    private fun removeNonAlphaNumeric(title: String): String {
+        return if (title.length == 1 && asciiAlphaNumeric.get(title[0].toInt())) title
+        else buildString {
+            for (c in title.toCharArray())
+                if (asciiAlphaNumeric.get(c.toInt()))
+                    append(c)
+        }
+    }
+
+    /**
+     * Find the first occurrence of non-punctuation characters.
+     * Functionally identical to replaceAll("^[\t\\x20-\\x2F\\x3A-\\x40\\x5B-\\x60\\x7B-\\x7E]+", "")
+     * "[On Android]...Unicode character classes are always used." (see Pattern.UNICODE_CHARACTER_CLASS)
+     * therefore "^[\\p{Blank}\\p{Punct}]+" is not a direct substitute.
+     */
+    private fun removeLeadingPunctuation(title: String): String {
+        title.forEachIndexed { i, c ->
+            if (!asciiPunctuation.get(c.toInt()))
+                return title.substring(i)
+        }
+        return title
+    }
 }
+



More information about the Android mailing list