Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/src/main/java/com/owncloud/android/datamodel/OCFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import thirdparties.daveKoeller.AlphanumComparator;
import com.owncloud.android.utils.sort.AlphanumericComparator;

public class OCFile implements Parcelable, Comparable<OCFile>, ServerFileInterface {

Expand Down Expand Up @@ -554,13 +554,13 @@ public int describeContents() {
@Override
public int compareTo(@NonNull OCFile another) {
if (isFolder() && another.isFolder()) {
return AlphanumComparator.compare(this, another);
return AlphanumericComparator.compare(this, another);
} else if (isFolder()) {
return -1;
} else if (another.isFolder()) {
return 1;
}
return AlphanumComparator.compare(this, another);
return AlphanumericComparator.compare(this, another);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import thirdparties.daveKoeller.AlphanumComparator
import com.owncloud.android.utils.sort.AlphanumericComparator
import java.util.Calendar
import java.util.GregorianCalendar
import javax.inject.Inject
Expand Down Expand Up @@ -328,7 +328,7 @@ class BackupFragment :
val backupFiles = listOf(calendarBackupFolderPath, contactsBackupFolderPath)
.mapNotNull { path -> storageManager.getFileByDecryptedRemotePath(path) }
.flatMap { folder -> fetchBackupFiles(folder, storageManager) }
.sortedWith(AlphanumComparator())
.sortedWith(AlphanumericComparator())
withContext(Dispatchers.Main) {
binding.contactsDatepicker.setVisibleIf(backupFiles.isNotEmpty())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.files.model.ServerFileInterface
import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import thirdparties.daveKoeller.AlphanumComparator
import com.owncloud.android.utils.sort.AlphanumericComparator
import java.io.File
import java.util.Locale

Expand Down Expand Up @@ -46,17 +46,17 @@ class FileSortOrderByName internal constructor(name: String?, ascending: Boolean
private fun <T : ServerFileInterface> sortServerFiles(files: MutableList<T>): MutableList<T> {
files.sortWith { o1: ServerFileInterface, o2: ServerFileInterface ->
when {
o1.isFolder && o2.isFolder -> sortMultiplier * AlphanumComparator.compare(o1, o2)
o1.isFolder && o2.isFolder -> sortMultiplier * AlphanumericComparator.compare(o1, o2)
o1.isFolder -> -1
o2.isFolder -> 1
else -> sortMultiplier * AlphanumComparator.compare(o1, o2)
else -> sortMultiplier * AlphanumericComparator.compare(o1, o2)
}
}
return files
}

private fun sortOnlyByName(files: MutableList<OCFile>): MutableList<OCFile> {
files.sortWith { o1: OCFile, o2: OCFile -> sortMultiplier * AlphanumComparator.compare(o1, o2) }
files.sortWith { o1: OCFile, o2: OCFile -> sortMultiplier * AlphanumericComparator.compare(o1, o2) }
return files
}

Expand All @@ -75,7 +75,7 @@ class FileSortOrderByName internal constructor(name: String?, ascending: Boolean

o2.isDirectory -> 1

else -> sortMultiplier * AlphanumComparator.compare(
else -> sortMultiplier * AlphanumericComparator.compare(
o1.path.lowercase(Locale.getDefault()),
o2.path.lowercase(Locale.getDefault())
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
Comment on lines +1 to +6
package com.owncloud.android.utils.sort

import com.owncloud.android.lib.resources.files.model.ServerFileInterface
import java.text.Collator

/**
* Sorts names the way people read them, so `abc2` comes before `abc10` instead of after it.
*
*/
class AlphanumericComparator<T : Any> : Comparator<T> {

override fun compare(first: T, second: T): Int = compare(first.toString(), second.toString())

companion object {

@JvmStatic
fun compare(first: ServerFileInterface, second: ServerFileInterface): Int =
compare(first.fileName, second.fileName)

@JvmStatic
fun compare(first: String, second: String): Int = if (first == second) 0 else compareByChunks(first, second)

private fun compareByChunks(first: String, second: String): Int {
var ourStart = 0
var theirStart = 0

while (ourStart < first.length && theirStart < second.length) {
val ours = chunkAt(first, ourStart)
val theirs = chunkAt(second, theirStart)

val byChunk = compareChunks(ours, theirs)
if (byChunk != 0) {
return byChunk
}

ourStart += ours.length
theirStart += theirs.length
}

return when (val byLength = first.length.compareTo(second.length)) {
0 -> first.compareTo(second)
else -> byLength
}
}

private val collators: ThreadLocal<Collator> = ThreadLocal.withInitial { Collator.getInstance() }

private val collator: Collator
get() = collators.get() ?: Collator.getInstance()
Comment on lines +52 to +55

private fun chunkAt(name: String, start: Int): String {
val kind = kindOf(name[start])
var end = start + 1

if (kind != ChunkKind.SEPARATOR) {
while (end < name.length && kindOf(name[end]) == kind) {
end++
}
}

return name.substring(start, end)
}

private fun compareChunks(ours: String, theirs: String): Int {
val kind = kindOf(ours[0])
val byKind = kind.compareTo(kindOf(theirs[0]))
if (byKind != 0) {
return byKind
}
Comment on lines +70 to +75

return when (kind) {
ChunkKind.SEPARATOR -> compareSeparators(ours[0], theirs[0])
ChunkKind.NUMBER -> compareNumbers(ours, theirs)
ChunkKind.TEXT -> compareText(ours, theirs)
}
}

private fun kindOf(char: Char): ChunkKind = when {
char <= LAST_ASCII_PUNCTUATION && !char.isLetterOrDigit() -> ChunkKind.SEPARATOR
char in ZERO..NINE -> ChunkKind.NUMBER
else -> ChunkKind.TEXT
}

private fun compareSeparators(ours: Char, theirs: Char): Int = when {
ours == theirs -> 0
ours == DOT -> -1
theirs == DOT -> 1
else -> ours.compareTo(theirs)
}

private fun compareNumbers(ours: String, theirs: String): Int {
val ourValue = ours.trimStart(ZERO)
val theirValue = theirs.trimStart(ZERO)

val byDigitCount = ourValue.length.compareTo(theirValue.length)
if (byDigitCount != 0) {
return byDigitCount
}

return when (val byValue = ourValue.compareTo(theirValue)) {
0 -> ours.length.compareTo(theirs.length)
else -> byValue
}
}

private fun compareText(ours: String, theirs: String): Int {
val byCollation = collator.compare(ours, theirs)
if (byCollation != 0) {
return byCollation
}

return when (val byLength = ours.length.compareTo(theirs.length)) {
0 -> ours.compareTo(theirs)
else -> byLength
}
}

private const val DOT = '.'
private const val ZERO = '0'
private const val NINE = '9'
private const val LAST_ASCII_PUNCTUATION = '~'
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/com/owncloud/android/utils/sort/ChunkKind.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.utils.sort

internal enum class ChunkKind {
SEPARATOR,
NUMBER,
TEXT
}
Loading
Loading