From e89d66839d0c40834cc8c692d1c85f321915f38f Mon Sep 17 00:00:00 2001
From: Dejvino <scoutmaster@dejvino.cz>
Date: Wed, 2 Aug 2023 04:47:47 +0200
Subject: [PATCH] =?UTF-8?q?Fix=20#615:=20'Checklist=20-=20Order=20issue=20?=
 =?UTF-8?q?with=20char=20like=20=C3=89=20or=20=C3=88'=20by=20using=20a=20l?=
 =?UTF-8?q?ocale-aware=20Collator?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../pro/helpers/CollatorBasedComparator.kt    | 79 +++++++++++++++++++
 .../notes/pro/models/ChecklistItem.kt         |  4 +-
 2 files changed, 81 insertions(+), 2 deletions(-)
 create mode 100644 app/src/main/kotlin/com/simplemobiletools/notes/pro/helpers/CollatorBasedComparator.kt

diff --git a/app/src/main/kotlin/com/simplemobiletools/notes/pro/helpers/CollatorBasedComparator.kt b/app/src/main/kotlin/com/simplemobiletools/notes/pro/helpers/CollatorBasedComparator.kt
new file mode 100644
index 00000000..74febf15
--- /dev/null
+++ b/app/src/main/kotlin/com/simplemobiletools/notes/pro/helpers/CollatorBasedComparator.kt
@@ -0,0 +1,79 @@
+package com.simplemobiletools.notes.pro.helpers
+
+import java.text.Collator
+
+/**
+ * Collator-based string comparator
+ *
+ * Adapted from AlphanumericComparator to support numerical collation.
+ */
+class CollatorBasedComparator: Comparator<String> {
+    override fun compare(string1: String, string2: String): Int {
+        val collator = getCollator()
+
+        var thisMarker = 0
+        var thatMarker = 0
+
+        while (thisMarker < string1.length && thatMarker < string2.length) {
+            val thisChunk = getChunk(string1, string1.length, thisMarker)
+            thisMarker += thisChunk.length
+
+            val thatChunk = getChunk(string2, string2.length, thatMarker)
+            thatMarker += thatChunk.length
+
+            val result = if (isDigit(thisChunk[0]) && isDigit(thatChunk[0])) {
+                collateNumerically(thisChunk, thatChunk)
+            } else {
+                collator.compare(thisChunk, thatChunk)
+            }
+
+            if (result != 0) {
+                return coerceResult(result)
+            }
+        }
+
+        return coerceResult(string1.length - string2.length)
+    }
+
+    private fun collateNumerically(string1: String, string2: String): Int {
+        var result: Int
+        result = string1.length - string2.length
+        if (result == 0) {
+            // equal length, the first different number counts
+            for (i in string1.indices) {
+                result = string1[i] - string2[i]
+                if (result != 0) {
+                    break
+                }
+            }
+        }
+        return result
+    }
+
+    private fun getChunk(string: String, length: Int, marker: Int): String {
+        var current = marker
+        var c = string[current]
+        val chunk = StringBuilder(c.toString())
+        current++
+        val chunkOfDigits = isDigit(c)
+        while (current < length) {
+            c = string[current]
+            if (isDigit(c) != chunkOfDigits) {
+                break
+            }
+            chunk.append(c)
+            current++
+        }
+        return chunk.toString()
+    }
+
+    private fun isDigit(ch: Char) = ch in '0'..'9'
+    private fun coerceResult(compareToResult: Int) = compareToResult.coerceIn(-1, 1)
+
+    private fun getCollator(): Collator {
+        val collator = Collator.getInstance()
+        collator.strength = Collator.PRIMARY
+        collator.decomposition = Collator.CANONICAL_DECOMPOSITION
+        return collator
+    }
+}
diff --git a/app/src/main/kotlin/com/simplemobiletools/notes/pro/models/ChecklistItem.kt b/app/src/main/kotlin/com/simplemobiletools/notes/pro/models/ChecklistItem.kt
index 5f9bc70f..fb2437ce 100644
--- a/app/src/main/kotlin/com/simplemobiletools/notes/pro/models/ChecklistItem.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/notes/pro/models/ChecklistItem.kt
@@ -1,8 +1,8 @@
 package com.simplemobiletools.notes.pro.models
 
-import com.simplemobiletools.commons.helpers.AlphanumericComparator
 import com.simplemobiletools.commons.helpers.SORT_BY_TITLE
 import com.simplemobiletools.commons.helpers.SORT_DESCENDING
+import com.simplemobiletools.notes.pro.helpers.CollatorBasedComparator
 import kotlinx.serialization.Serializable
 
 @Serializable
@@ -19,7 +19,7 @@ data class ChecklistItem(
 
     override fun compareTo(other: ChecklistItem): Int {
         var result = when {
-            sorting and SORT_BY_TITLE != 0 -> AlphanumericComparator().compare(title.lowercase(), other.title.lowercase())
+            sorting and SORT_BY_TITLE != 0 -> CollatorBasedComparator().compare(title, other.title)
             else -> dateCreated.compareTo(other.dateCreated)
         }