diff --git a/app/src/main/java/com/todoroo/astrid/core/CustomFilterExposer.java b/app/src/main/java/com/todoroo/astrid/core/CustomFilterExposer.java index 762965329..7ac617a11 100644 --- a/app/src/main/java/com/todoroo/astrid/core/CustomFilterExposer.java +++ b/app/src/main/java/com/todoroo/astrid/core/CustomFilterExposer.java @@ -11,9 +11,11 @@ import static com.google.common.collect.Lists.transform; import com.todoroo.astrid.api.CustomFilter; import com.todoroo.astrid.api.Filter; +import java.util.Collections; import java.util.List; import javax.inject.Inject; import org.tasks.data.FilterDao; +import org.tasks.filters.AlphanumComparator; public final class CustomFilterExposer { @@ -25,7 +27,9 @@ public final class CustomFilterExposer { } public List getFilters() { - return newArrayList(transform(filterDao.getFilters(), this::load)); + List filters = newArrayList(transform(filterDao.getFilters(), this::load)); + Collections.sort(filters, new AlphanumComparator()); + return filters; } public Filter getFilter(long id) { diff --git a/app/src/main/java/com/todoroo/astrid/gtasks/GtasksFilterExposer.java b/app/src/main/java/com/todoroo/astrid/gtasks/GtasksFilterExposer.java index d9ddf8f33..074ed8253 100644 --- a/app/src/main/java/com/todoroo/astrid/gtasks/GtasksFilterExposer.java +++ b/app/src/main/java/com/todoroo/astrid/gtasks/GtasksFilterExposer.java @@ -11,6 +11,7 @@ import static com.todoroo.andlib.utility.DateUtilities.now; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.GtasksFilter; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -18,6 +19,7 @@ import javax.inject.Inject; import org.tasks.data.GoogleTaskAccount; import org.tasks.data.GoogleTaskList; import org.tasks.data.GoogleTaskListDao; +import org.tasks.filters.AlphanumComparator; import org.tasks.filters.GoogleTaskFilters; import org.tasks.sync.SyncAdapters; @@ -53,6 +55,9 @@ public class GtasksFilterExposer { filters.get(filter.googleTaskAccount).add(filter.toGtasksFilter()); } } + for (Map.Entry> entry : filters.entrySet()) { + Collections.sort(entry.getValue(), new AlphanumComparator()); + } return filters; } diff --git a/app/src/main/java/com/todoroo/astrid/tags/TagFilterExposer.java b/app/src/main/java/com/todoroo/astrid/tags/TagFilterExposer.java index 8c4047019..1bc7b04ba 100644 --- a/app/src/main/java/com/todoroo/astrid/tags/TagFilterExposer.java +++ b/app/src/main/java/com/todoroo/astrid/tags/TagFilterExposer.java @@ -6,16 +6,19 @@ package com.todoroo.astrid.tags; +import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.transform; import static com.todoroo.andlib.utility.DateUtilities.now; import com.google.common.base.Strings; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.TagFilter; +import java.util.Collections; import java.util.List; import javax.inject.Inject; import org.tasks.data.TagData; import org.tasks.data.TagDataDao; +import org.tasks.filters.AlphanumComparator; import org.tasks.filters.TagFilters; /** @@ -41,7 +44,10 @@ public class TagFilterExposer { } public List getFilters() { - return transform(tagDataDao.getTagFilters(now()), TagFilters::toTagFilter); + List tags = + newArrayList(transform(tagDataDao.getTagFilters(now()), TagFilters::toTagFilter)); + Collections.sort(tags, new AlphanumComparator()); + return tags; } public Filter getFilterByUuid(String uuid) { diff --git a/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java b/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java index 9d2da7bca..cd1b1e0d2 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java +++ b/app/src/main/java/org/tasks/caldav/CaldavFilterExposer.java @@ -5,6 +5,7 @@ import static com.todoroo.andlib.utility.DateUtilities.now; import com.todoroo.astrid.api.CaldavFilter; import com.todoroo.astrid.api.Filter; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -12,6 +13,7 @@ import javax.inject.Inject; import org.tasks.data.CaldavAccount; import org.tasks.data.CaldavCalendar; import org.tasks.data.CaldavDao; +import org.tasks.filters.AlphanumComparator; import org.tasks.filters.CaldavFilters; import org.tasks.sync.SyncAdapters; @@ -37,6 +39,9 @@ public class CaldavFilterExposer { filters.get(filter.caldavAccount).add(filter.toCaldavFilter()); } } + for (Map.Entry> entry : filters.entrySet()) { + Collections.sort(entry.getValue(), new AlphanumComparator()); + } return filters; } diff --git a/app/src/main/java/org/tasks/data/CaldavDao.java b/app/src/main/java/org/tasks/data/CaldavDao.java index 6cfd78b14..cb12285af 100644 --- a/app/src/main/java/org/tasks/data/CaldavDao.java +++ b/app/src/main/java/org/tasks/data/CaldavDao.java @@ -132,7 +132,7 @@ public abstract class CaldavDao { + " LEFT JOIN caldav_tasks ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid" + " LEFT JOIN tasks ON caldav_tasks.cd_task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now AND cd_deleted = 0" + " GROUP BY caldav_lists.cdl_uuid" - + " ORDER BY caldav_accounts.cda_name COLLATE NOCASE, caldav_lists.cdl_name COLLATE NOCASE") + + " ORDER BY caldav_accounts.cda_name COLLATE NOCASE") public abstract List getCaldavFilters(long now); @Query( diff --git a/app/src/main/java/org/tasks/data/FilterDao.java b/app/src/main/java/org/tasks/data/FilterDao.java index 40f3bb706..b341ffcb5 100644 --- a/app/src/main/java/org/tasks/data/FilterDao.java +++ b/app/src/main/java/org/tasks/data/FilterDao.java @@ -25,7 +25,7 @@ public interface FilterDao { @Insert void insert(Filter filter); - @Query("SELECT * FROM filters ORDER BY title ASC") + @Query("SELECT * FROM filters") List getFilters(); @Query("SELECT * FROM filters WHERE _id = :id LIMIT 1") diff --git a/app/src/main/java/org/tasks/data/GoogleTaskListDao.java b/app/src/main/java/org/tasks/data/GoogleTaskListDao.java index 86e5fe230..e3865a721 100644 --- a/app/src/main/java/org/tasks/data/GoogleTaskListDao.java +++ b/app/src/main/java/org/tasks/data/GoogleTaskListDao.java @@ -66,6 +66,6 @@ public abstract class GoogleTaskListDao { + " LEFT JOIN google_tasks ON google_tasks.gt_list_id = google_task_lists.gtl_remote_id" + " LEFT JOIN tasks ON google_tasks.gt_task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now AND gt_deleted = 0" + " GROUP BY google_task_lists.gtl_remote_id" - + " ORDER BY google_task_lists.gtl_account COLLATE NOCASE, google_task_lists.gtl_title COLLATE NOCASE") + + " ORDER BY google_task_lists.gtl_account COLLATE NOCASE") public abstract List getGoogleTaskFilters(long now); } diff --git a/app/src/main/java/org/tasks/data/TagDataDao.java b/app/src/main/java/org/tasks/data/TagDataDao.java index 2830fa09e..591ef629e 100644 --- a/app/src/main/java/org/tasks/data/TagDataDao.java +++ b/app/src/main/java/org/tasks/data/TagDataDao.java @@ -82,7 +82,6 @@ public abstract class TagDataDao { + " LEFT JOIN tags ON tags.tag_uid = tagdata.remoteId" + " LEFT JOIN tasks ON tags.task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now" + " WHERE tagdata.name IS NOT NULL AND tagdata.name != ''" - + " GROUP BY tagdata.remoteId" - + " ORDER BY tagdata.name COLLATE NOCASE") + + " GROUP BY tagdata.remoteId") public abstract List getTagFilters(long now); } diff --git a/app/src/main/java/org/tasks/filters/AlphanumComparator.java b/app/src/main/java/org/tasks/filters/AlphanumComparator.java new file mode 100644 index 000000000..4dc61f82b --- /dev/null +++ b/app/src/main/java/org/tasks/filters/AlphanumComparator.java @@ -0,0 +1,114 @@ +package org.tasks.filters; + +/* + * The Alphanum Algorithm is an improved sorting algorithm for strings + * containing numbers. Instead of sorting numbers in ASCII order like + * a standard sort, this algorithm sorts numbers in numeric order. + * + * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com + * + * Released under the MIT License - https://opensource.org/licenses/MIT + * + * Copyright 2007-2017 David Koelle + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import com.todoroo.astrid.api.Filter; +import java.util.Comparator; + +/** + * This is an updated version with enhancements made by Daniel Migowski, Andre Bogus, and David + * Koelle. Updated by David Koelle in 2017. + * + *

To use this class: Use the static "sort" method from the java.util.Collections class: + * Collections.sort(your list, new AlphanumComparator()); + */ +public class AlphanumComparator implements Comparator { + private boolean isDigit(char ch) { + return ((ch >= 48) && (ch <= 57)); + } + + /** Length of string is passed in for improved efficiency (only need to calculate it once) * */ + private String getChunk(String s, int slength, int marker) { + StringBuilder chunk = new StringBuilder(); + char c = s.charAt(marker); + chunk.append(c); + marker++; + if (isDigit(c)) { + while (marker < slength) { + c = s.charAt(marker); + if (!isDigit(c)) break; + chunk.append(c); + marker++; + } + } else { + while (marker < slength) { + c = s.charAt(marker); + if (isDigit(c)) break; + chunk.append(c); + marker++; + } + } + return chunk.toString(); + } + + public int compare(Filter f1, Filter f2) { + String s1 = f1.listingTitle; + String s2 = f2.listingTitle; + if ((s1 == null) || (s2 == null)) { + return 0; + } + int thisMarker = 0; + int thatMarker = 0; + int s1Length = s1.length(); + int s2Length = s2.length(); + + while (thisMarker < s1Length && thatMarker < s2Length) { + String thisChunk = getChunk(s1, s1Length, thisMarker); + thisMarker += thisChunk.length(); + + String thatChunk = getChunk(s2, s2Length, thatMarker); + thatMarker += thatChunk.length(); + + // If both chunks contain numeric characters, sort them numerically + int result; + if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) { + // Simple chunk comparison by length. + int thisChunkLength = thisChunk.length(); + result = thisChunkLength - thatChunk.length(); + // If equal, the first different number counts + if (result == 0) { + for (int i = 0; i < thisChunkLength; i++) { + result = thisChunk.charAt(i) - thatChunk.charAt(i); + if (result != 0) { + return result; + } + } + } + } else { + result = thisChunk.compareTo(thatChunk); + } + + if (result != 0) return result; + } + + return s1Length - s2Length; + } +}