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
17 changes: 14 additions & 3 deletions lib/app/models/data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ class Data {

List<Task> _completedData() {
var data = _allData().where(
(task) => task.status == 'completed' || task.status == 'deleted');
(task) => task.status == 'completed');
return [
for (var task in data) task.rebuild((b) => b..id = 0),
];
}

List<Task> completedData() {
var data = _allData().where(
(task) => task.status == 'completed' || task.status == 'deleted');
(task) => task.status == 'completed');
return data
.toList()
.asMap()
Expand All @@ -85,8 +85,19 @@ class Data {
];
}

List<Task> deletedData() {
var data = _allData().where(
(task) => task.status == 'deleted');
return data
.toList()
.asMap()
.entries
.map((entry) => entry.value.rebuild((b) => b..id = entry.key + 1))
.toList();
}

List<Task> allData() {
var data = pendingData()..addAll(_completedData());
var data = pendingData()..addAll(_completedData())..addAll(deletedData());
return data;
}

Expand Down
4 changes: 4 additions & 0 deletions lib/app/models/filters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Filters {
const Filters({
required this.pendingFilter,
required this.waitingFilter,
required this.deletedFilter,
required this.togglePendingFilter,
required this.toggleWaitingFilter,
required this.tagFilters,
Expand All @@ -14,8 +15,11 @@ class Filters {

final bool pendingFilter;
final bool waitingFilter;
final bool deletedFilter;

final void Function() togglePendingFilter;
final void Function() toggleWaitingFilter;

final TagFilters tagFilters;
final dynamic projects;
final String projectFilter;
Expand Down
182 changes: 127 additions & 55 deletions lib/app/modules/home/controllers/home_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class HomeController extends GetxController {
late Storage storage;
final RxBool pendingFilter = false.obs;
final RxBool waitingFilter = false.obs;
final RxBool deletedFilter = false.obs;

final RxString projectFilter = ''.obs;
final RxBool tagUnion = false.obs;
final RxString selectedSort = ''.obs;
Expand Down Expand Up @@ -101,6 +103,7 @@ class HomeController extends GetxController {
everAll([
pendingFilter,
waitingFilter,
deletedFilter,
projectFilter,
tagUnion,
selectedSort,
Expand Down Expand Up @@ -192,6 +195,10 @@ class HomeController extends GetxController {
final SharedPreferences prefs = await SharedPreferences.getInstance();
taskchampion.value = prefs.getBool('settings_taskc') ?? false;
taskReplica.value = prefs.getBool('settings_taskr_repl') ?? false;

if (taskchampion.value || taskReplica.value) {
deletedFilter.value = false;
}
}

Future<void> refreshReplicaTasks() async {
Expand All @@ -216,96 +223,136 @@ class HomeController extends GetxController {
}

void _profileSet() {
pendingFilter.value = Query(storage.tabs.tab()).getPendingFilter();
if (!Query(storage.tabs.tab()).getWaitingFilter()) {
waitingFilter.value = Query(storage.tabs.tab()).getWaitingFilter();
} else {
Query(storage.tabs.tab()).toggleWaitingFilter();
waitingFilter.value = Query(storage.tabs.tab()).getWaitingFilter();
}
final bool isPending = Query(storage.tabs.tab()).getPendingFilter();

pendingFilter.value = isPending;
deletedFilter.value = false;
waitingFilter.value = Query(storage.tabs.tab()).getWaitingFilter();

projectFilter.value = Query(storage.tabs.tab()).projectFilter();
tagUnion.value = Query(storage.tabs.tab()).tagUnion();
selectedSort.value = Query(storage.tabs.tab()).getSelectedSort();
selectedTags.addAll(Query(storage.tabs.tab()).getSelectedTags());
selectedTags.assignAll(Query(storage.tabs.tab()).getSelectedTags());

_refreshTasks();
pendingTags.value = _pendingTags();
projects.value = _projects();

if (searchVisible.value) {
toggleSearch();
}
}

void _refreshTasks() {
if (pendingFilter.value) {
queriedTasks.value = storage.data
.pendingData()
.where((task) => task.status == 'pending')
.toList();
List<Task> baseTasks;
if (taskchampion.value || taskReplica.value) {
final source = _dedupeReplicaTasks(tasksFromReplica);

if (pendingFilter.value) {
baseTasks = source.where((t) => t.status == 'pending').map(_mapReplicaToTask).toList();
} else {
baseTasks = source
.where((t) => t.status == 'completed').map(_mapReplicaToTask).toList();
}
} else {
queriedTasks.value = storage.data.completedData();
if (pendingFilter.value) {
baseTasks = storage.data.pendingData();
} else if (deletedFilter.value) {
baseTasks = storage.data.deletedData();
} else {
baseTasks = storage.data.completedData();
}
}

queriedTasks.assignAll(baseTasks);
if (waitingFilter.value) {
var currentTime = DateTime.now();
queriedTasks.value = queriedTasks
.where((task) => task.wait != null && task.wait!.isAfter(currentTime))
.toList();
final now = DateTime.now();
queriedTasks.value = queriedTasks.where((task) {
return task.wait != null && task.wait!.isAfter(now);
}).toList();
}

if (projectFilter.value.isNotEmpty) {
queriedTasks.value = queriedTasks.where((task) {
if (task.project == null) {
return false;
} else {
return task.project!.startsWith(projectFilter.value);
}
return task.project?.startsWith(projectFilter.value) ?? false;
}).toList();
}

queriedTasks.value = queriedTasks.where((task) {
var tags = task.tags?.toSet() ?? {};
final tags = task.tags?.toSet() ?? <String>{};

if (selectedTags.isEmpty) return true;

if (tagUnion.value) {
if (selectedTags.isEmpty) {
return true;
}
return selectedTags.any((tag) => (tag.startsWith('+'))
? tags.contains(tag.substring(1))
: !tags.contains(tag.substring(1)));
return selectedTags.any((tag) {
final clean = tag.substring(1);
return tag.startsWith('+')
? tags.contains(clean)
: !tags.contains(clean);
});
} else {
return selectedTags.every((tag) => (tag.startsWith('+'))
? tags.contains(tag.substring(1))
: !tags.contains(tag.substring(1)));
return selectedTags.every((tag) {
final clean = tag.substring(1);
return tag.startsWith('+')
? tags.contains(clean)
: !tags.contains(clean);
});
}
}).toList();

var sortColumn =
selectedSort.value.substring(0, selectedSort.value.length - 1);
var ascending = selectedSort.value.endsWith('+');
queriedTasks.sort((a, b) {
int result;
if (sortColumn == 'id') {
result = a.id!.compareTo(b.id!);
} else {
result = compareTasks(sortColumn)(a, b);
}
return ascending ? result : -result;
});
if (selectedSort.value.isNotEmpty) {
final column =
selectedSort.value.substring(0, selectedSort.value.length - 1);
final ascending = selectedSort.value.endsWith('+');

queriedTasks.sort((a, b) {
final result = column == 'id'
? a.id!.compareTo(b.id!)
: compareTasks(column)(a, b);
return ascending ? result : -result;
});
}

searchedTasks.assignAll(queriedTasks);
var searchTerm = searchController.text;
if (searchVisible.value) {
searchedTasks.value = searchedTasks
.where((task) =>
task.description.contains(searchTerm) ||
(task.annotations?.asList() ?? []).any(
(annotation) => annotation.description.contains(searchTerm)))
.toList();
if (searchVisible.value && searchController.text.isNotEmpty) {
final term = searchController.text.toLowerCase();
searchedTasks.value = searchedTasks.where((task) {
return task.description.toLowerCase().contains(term) ||
(task.annotations?.asList() ?? []).any(
(a) => a.description.toLowerCase().contains(term),
);
}).toList();
}

pendingTags.value = _pendingTags();
projects.value = _projects();
}


Task _mapReplicaToTask(TaskForReplica t) {
return Task((b) => b
..description = t.description
..project = t.project
..priority = t.priority
..status = t.status
..tags.replace(t.tags ?? []));
}

List<TaskForReplica> _dedupeReplicaTasks(
List<TaskForReplica> source,
) {
final Map<String, TaskForReplica> latest = {};

for (final task in source) {
final existing = latest[task.uuid];

if (existing == null || (task.modified ?? 0) > (existing.modified ?? 0)) {
latest[task.uuid] = task;
}
}

return latest.values.toList();
}

Map<String, TagMetadata> _pendingTags() {
var frequency = tagFrequencies(storage.data.pendingData());
var modified = tagsLastModified(storage.data.pendingData());
Expand Down Expand Up @@ -380,6 +427,30 @@ class HomeController extends GetxController {
_refreshTasks();
}

void toggleStatusFilter() {
// for taskchampion and replica mode, only toggle between pending and completed
if (taskchampion.value || taskReplica.value) {
pendingFilter.value = !pendingFilter.value;
deletedFilter.value = false;
_refreshTasks();
return;
}
if (pendingFilter.value) {
// Pending → Completed
pendingFilter.value = false;
deletedFilter.value = false;
} else if (!pendingFilter.value && !deletedFilter.value) {
// Completed → Deleted
deletedFilter.value = true;
} else {
// Deleted → Pending
pendingFilter.value = true;
deletedFilter.value = false;
}

_refreshTasks();
}

Task getTask(String uuid) {
return storage.data.getTask(uuid);
}
Expand Down Expand Up @@ -616,6 +687,7 @@ class HomeController extends GetxController {
var filters = Filters(
pendingFilter: pendingFilter.value,
waitingFilter: waitingFilter.value,
deletedFilter: deletedFilter.value,
togglePendingFilter: togglePendingFilter,
toggleWaitingFilter: toggleWaitingFilter,
projects: projects,
Expand Down
64 changes: 35 additions & 29 deletions lib/app/modules/home/views/filter_drawer_home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,44 +80,50 @@ class FilterDrawer extends StatelessWidget {
border: Border.all(color: TaskWarriorColors.borderColor),
),
child: ListTile(
contentPadding: const EdgeInsets.only(
left: 8,
),
contentPadding: const EdgeInsets.only(left: 8),
onTap:
homeController.toggleStatusFilter,
title: RichText(
key: homeController.statusKey,
maxLines: 2,
text: TextSpan(
children: <TextSpan>[
children: [
TextSpan(
text:
'${SentenceManager(currentLanguage: homeController.selectedLanguage.value).sentences.filterDrawerStatus} : ',
style: TextStyle(
fontFamily: FontFamily.poppins,
fontSize: TaskWarriorFonts.fontSizeMedium,
color: tColors.primaryTextColor,
)),
text:
'${SentenceManager(currentLanguage: homeController.selectedLanguage.value).sentences.filterDrawerStatus} : ',
style: TextStyle(
fontFamily: FontFamily.poppins,
fontSize: TaskWarriorFonts.fontSizeMedium,
color: tColors.primaryTextColor,
),
),
TextSpan(
text: filters.pendingFilter
? SentenceManager(
currentLanguage: homeController
.selectedLanguage.value)
.sentences
.filterDrawerPending
: SentenceManager(
currentLanguage: homeController
.selectedLanguage.value)
.sentences
.filterDrawerCompleted,
style: TextStyle(
fontFamily: FontFamily.poppins,
fontSize: TaskWarriorFonts.fontSizeMedium,
color: tColors.primaryTextColor,
)),
text: filters.deletedFilter
? SentenceManager(
currentLanguage:
homeController.selectedLanguage.value)
.sentences
.filterDrawerDeleted
: filters.pendingFilter
? SentenceManager(
currentLanguage: homeController
.selectedLanguage.value)
.sentences
.filterDrawerPending
: SentenceManager(
currentLanguage: homeController
.selectedLanguage.value)
.sentences
.filterDrawerCompleted,
style: TextStyle(
fontFamily: FontFamily.poppins,
fontSize: TaskWarriorFonts.fontSizeMedium,
color: tColors.primaryTextColor,
),
),
],
),
),
onTap: filters.togglePendingFilter,
textColor: tColors.primaryTextColor,
),
),
const Divider(
Expand Down
Loading