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
2 changes: 2 additions & 0 deletions lib/app/modules/home/controllers/home_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ class HomeController extends GetxController {
int result;
if (sortColumn == 'id') {
result = a.id!.compareTo(b.id!);
} else if (sortColumn == 'Priority') {
return compareTasks(selectedSort.value)(a, b);
} else {
result = compareTasks(sortColumn)(a, b);
}
Expand Down
40 changes: 36 additions & 4 deletions lib/app/modules/home/views/show_tasks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,19 @@ class TaskViewBuilder extends StatelessWidget {
case 'Modified-':
return b.modified!.compareTo(a.modified!);
case 'Due till+':
return a.due!.compareTo(b.due!);
return _compareDue(a.due, b.due);
case 'Due till-':
return b.due!.compareTo(a.due!);
return _compareDue(b.due, a.due);
case 'Priority-':
return b.priority!.compareTo(a.priority!);
final p = _priorityRank(b.priority)
.compareTo(_priorityRank(a.priority));
if (p != 0) return p;
return _compareDue(a.due, b.due);
case 'Priority+':
return a.priority!.compareTo(b.priority!);
final p = _priorityRank(a.priority)
.compareTo(_priorityRank(b.priority));
if (p != 0) return p;
return _compareDue(a.due, b.due);
case 'Project+':
return a.project!.compareTo(b.project!);
case 'Project-':
Expand Down Expand Up @@ -236,6 +242,32 @@ class TaskViewBuilder extends StatelessWidget {
taskDatabase.markTaskAsDeleted(uuid);
}

int _priorityRank(String? priority) {
switch (priority) {
case 'H':
return 3;
case 'M':
return 2;
case 'L':
return 1;
default:
return 0;
}
}

int _compareDue(String? dueA, String? dueB) {
final DateTime? da = dueA != null ? DateTime.tryParse(dueA) : null;
final DateTime? db = dueB != null ? DateTime.tryParse(dueB) : null;
if (da != null && db != null) {
return da.compareTo(db);
} else if (da != null) {
return -1;
} else if (db != null) {
return 1;
}
return 0;
}

Color _getPriorityColor(String priority) {
switch (priority) {
case 'H':
Expand Down
21 changes: 19 additions & 2 deletions lib/app/modules/home/views/show_tasks_replica.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,15 @@ class TaskReplicaViewBuilder extends StatelessWidget {
case 'Due till-':
return (b.due ?? '').compareTo(a.due ?? '');
case 'Priority+':
return _priorityRank(a.priority)
final p = _priorityRank(a.priority)
.compareTo(_priorityRank(b.priority));
if (p != 0) return p;
return _compareDue(a.due, b.due);
case 'Priority-':
return _priorityRank(b.priority)
final p = _priorityRank(b.priority)
.compareTo(_priorityRank(a.priority));
if (p != 0) return p;
return _compareDue(a.due, b.due);
case 'Project+':
return (a.project ?? '').compareTo(b.project ?? '');
case 'Project-':
Expand Down Expand Up @@ -253,6 +257,19 @@ class TaskReplicaViewBuilder extends StatelessWidget {
return tags.join(' ');
}

int _compareDue(String? dueA, String? dueB) {
final DateTime? da = dueA != null ? DateTime.tryParse(dueA) : null;
final DateTime? db = dueB != null ? DateTime.tryParse(dueB) : null;
if (da != null && db != null) {
return da.compareTo(db);
} else if (da != null) {
return -1;
} else if (db != null) {
return 1;
}
return 0;
}

// Rank priorities by severity (H > M > L > none) so Priority sort is
// meaningful rather than alphabetical (which would order H < L < M).
int _priorityRank(String? priority) {
Expand Down
38 changes: 36 additions & 2 deletions lib/app/utils/taskfunctions/comparator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ import 'package:taskwarrior/app/models/json/task.dart';
import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart';


int _priorityRank(String? priority) {
switch (priority) {
case 'H':
return 3;
case 'M':
return 2;
case 'L':
return 1;
default:
return 0;
}
}

int _compareDue(DateTime? a, DateTime? b) {
if (a != null && b != null) {
return a.compareTo(b);
} else if (a != null) {
return -1;
} else if (b != null) {
return 1;
}
return 0;
}

int Function(Task, Task) compareTasks(String column) {
return (a, b) {
int? result;
Expand Down Expand Up @@ -45,9 +69,19 @@ int Function(Task, Task) compareTasks(String column) {
}
break;
case 'Priority':
var compare = {'H': 2, 'M': 1, 'L': 0};
case 'Priority+':
result =
(compare[a.priority] ?? -1).compareTo(compare[b.priority] ?? -1);
_priorityRank(a.priority).compareTo(_priorityRank(b.priority));
if (result == 0) {
result = _compareDue(a.due, b.due);
}
break;
case 'Priority-':
result =
_priorityRank(b.priority).compareTo(_priorityRank(a.priority));
if (result == 0) {
result = _compareDue(a.due, b.due);
}
break;
case 'Project':
result = (a.project ?? '').compareTo(b.project ?? '');
Expand Down
96 changes: 96 additions & 0 deletions test/utils/taskfunctions/comparator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,102 @@ void main() {
expect(comparator(task2, task1), lessThan(0));
});

test('should compare tasks with same priority by due date (earlier first)', () {
final taskEarly = createTask(priority: 'H', due: DateTime(2024, 1, 1));
final taskLate = createTask(priority: 'H', due: DateTime(2024, 1, 10));

final comparator = compareTasks('Priority');
final comparatorPlus = compareTasks('Priority+');
final comparatorMinus = compareTasks('Priority-');

expect(comparator(taskEarly, taskLate), lessThan(0));
expect(comparator(taskLate, taskEarly), greaterThan(0));

expect(comparatorPlus(taskEarly, taskLate), lessThan(0));
expect(comparatorPlus(taskLate, taskEarly), greaterThan(0));

expect(comparatorMinus(taskEarly, taskLate), lessThan(0));
expect(comparatorMinus(taskLate, taskEarly), greaterThan(0));
});

test('should sort tasks without due date after tasks with due date for same priority', () {
final taskWithDue = createTask(priority: 'M', due: DateTime(2024, 1, 1));
final taskWithoutDue = createTask(priority: 'M', due: null);

final comparator = compareTasks('Priority');
final comparatorPlus = compareTasks('Priority+');
final comparatorMinus = compareTasks('Priority-');

expect(comparator(taskWithDue, taskWithoutDue), lessThan(0));
expect(comparator(taskWithoutDue, taskWithDue), greaterThan(0));

expect(comparatorPlus(taskWithDue, taskWithoutDue), lessThan(0));
expect(comparatorPlus(taskWithoutDue, taskWithDue), greaterThan(0));

expect(comparatorMinus(taskWithDue, taskWithoutDue), lessThan(0));
expect(comparatorMinus(taskWithoutDue, taskWithDue), greaterThan(0));
});

test('should return 0 when same priority and both missing due date', () {
final task1 = createTask(priority: 'L', due: null);
final task2 = createTask(priority: 'L', due: null);

final comparator = compareTasks('Priority');
final comparatorPlus = compareTasks('Priority+');
final comparatorMinus = compareTasks('Priority-');

expect(comparator(task1, task2), equals(0));
expect(comparatorPlus(task1, task2), equals(0));
expect(comparatorMinus(task1, task2), equals(0));
});

test('should handle null priority safely and break ties with due date', () {
final taskNoPriorityEarly = createTask(priority: null, due: DateTime(2024, 1, 1));
final taskNoPriorityLate = createTask(priority: null, due: DateTime(2024, 1, 10));
final taskNoPriorityUndated = createTask(priority: null, due: null);
final taskWithPriority = createTask(priority: 'H', due: null);

final comparator = compareTasks('Priority');
final comparatorPlus = compareTasks('Priority+');
final comparatorMinus = compareTasks('Priority-');

// Null priorities compared to each other should order by due date (earlier first)
expect(comparator(taskNoPriorityEarly, taskNoPriorityLate), lessThan(0));
expect(comparator(taskNoPriorityLate, taskNoPriorityEarly), greaterThan(0));
expect(comparator(taskNoPriorityEarly, taskNoPriorityUndated), lessThan(0));

expect(comparatorPlus(taskNoPriorityEarly, taskNoPriorityLate), lessThan(0));
expect(comparatorMinus(taskNoPriorityEarly, taskNoPriorityLate), lessThan(0));

// Null priority vs non-null priority
// In Priority-: H precedes null
expect(comparatorMinus(taskWithPriority, taskNoPriorityEarly), lessThan(0));
expect(comparatorMinus(taskNoPriorityEarly, taskWithPriority), greaterThan(0));

// In Priority+: null precedes H
expect(comparatorPlus(taskNoPriorityEarly, taskWithPriority), lessThan(0));
expect(comparatorPlus(taskWithPriority, taskNoPriorityEarly), greaterThan(0));
});

test('should compare tasks correctly in both Priority+ and Priority- directions', () {
final taskH = createTask(priority: 'H');
final taskM = createTask(priority: 'M');
final taskL = createTask(priority: 'L');

final comparatorPlus = compareTasks('Priority+');
final comparatorMinus = compareTasks('Priority-');

// Priority+: ascending (L < M < H)
expect(comparatorPlus(taskL, taskM), lessThan(0));
expect(comparatorPlus(taskM, taskH), lessThan(0));
expect(comparatorPlus(taskH, taskL), greaterThan(0));

// Priority-: descending (H > M > L)
expect(comparatorMinus(taskH, taskM), lessThan(0));
expect(comparatorMinus(taskM, taskL), lessThan(0));
expect(comparatorMinus(taskL, taskH), greaterThan(0));
});

test('should compare tasks by Project', () {
final task1 = createTask(project: 'ProjectA');
final task2 = createTask(project: 'ProjectB');
Expand Down