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
116 changes: 115 additions & 1 deletion lib/app/modules/home/controllers/home_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:taskwarrior/app/models/json/task.dart';
import 'package:taskwarrior/app/models/storage.dart';
import 'package:taskwarrior/app/models/storage/client.dart';
import 'package:taskwarrior/app/models/tag_meta_data.dart';
import 'package:taskwarrior/app/models/json/annotation.dart';
import 'package:taskwarrior/app/modules/home/controllers/widget.controller.dart';
import 'package:taskwarrior/app/modules/splash/controllers/splash_controller.dart';
import 'package:taskwarrior/app/services/deep_link_service.dart';
Expand All @@ -30,6 +31,7 @@ import 'package:taskwarrior/app/utils/taskfunctions/projects.dart';
import 'package:taskwarrior/app/utils/taskfunctions/query.dart';
import 'package:taskwarrior/app/utils/taskfunctions/tags.dart';
import 'package:taskwarrior/app/utils/app_settings/app_settings.dart';
import 'package:taskwarrior/app/utils/taskfunctions/urgency.dart';
import 'package:taskwarrior/app/v3/champion/replica.dart';
import 'package:taskwarrior/app/v3/champion/models/task_for_replica.dart';
import 'package:taskwarrior/app/v3/db/task_database.dart';
Expand All @@ -39,6 +41,8 @@ import 'package:taskwarrior/app/v3/net/fetch.dart';
import 'package:textfield_tags/textfield_tags.dart';
import 'package:taskwarrior/app/utils/themes/theme_extension.dart';
import 'package:tutorial_coach_mark/tutorial_coach_mark.dart';
import 'package:built_collection/built_collection.dart';
import 'package:taskwarrior/app/v3/models/annotation.dart' as ChampionAnn;

class HomeController extends GetxController {
final SplashController splashController = Get.find<SplashController>();
Expand Down Expand Up @@ -197,7 +201,117 @@ class HomeController extends GetxController {
tasksFromReplica.value = await Replica.getAllTasksFromReplica();
debugPrint("Tasks from Replica: ${tasks.length}");
}


Future<void> mergeReplica(Task task) async {
final updated = task.rebuild(
(b) => b.modified = DateTime.now().toUtc(),
);
final replicaTask = convertTaskToReplica(updated);
await Replica.modifyTaskInReplica(replicaTask);
await refreshReplicaTaskList();
}
// its convert Task into Replica
TaskForReplica convertTaskToReplica(Task t){
return TaskForReplica(
uuid: t.uuid,
description: t.description,
status: t.status,
project: t.project,
priority: t.priority,
due: t.due?.toIso8601String(),
start: t.start?.toIso8601String(),
wait: t.wait?.toIso8601String(),
// entry: t.entry?.toIso8601String(),
tags: t.tags?.toList(),
);
}
// its convert Replica into Task
Task convertReplicaToTask(TaskForReplica t) {
final now = DateTime.now().toUtc();
return Task((b) => b
..uuid = t.uuid
..description = t.description ?? ''
..status = t.status ?? 'pending'
..project = t.project
..priority = t.priority
..due = t.due != null ? DateTime.parse(t.due!) : null
..start = t.start != null ? DateTime.parse(t.start!) : null
..wait = t.wait != null ? DateTime.parse(t.wait!) : null
// ..entry = DateTime.now().toUtc()
// ..modified = DateTime.now().toUtc()
..entry = now
..modified = now
..end = null
..tags = t.tags != null
? ListBuilder<String>(t.tags!)
: null
);
}

Future<void> mergeTaskChampion(TaskForC task) async {
await taskdb.updateTask(task);
tasks.value = await taskdb.fetchTasksFromDatabase();
}
// its convert Task into TaskForC
TaskForC convertTaskToTaskForC(Task t) {
debugPrint("Annotations to save: ${t.annotations?.length}");
return TaskForC(
id: t.id!,
uuid: t.uuid,
description: t.description,
status: t.status,
project: t.project,
priority: t.priority,
due: t.due?.toIso8601String(),
start: t.start?.toIso8601String(),
wait: t.wait?.toIso8601String(),
entry: t.entry.toIso8601String(),
modified: t.modified?.toIso8601String(),
end: t.end?.toIso8601String(),
rtype: null,
recur: t.recur,
depends: t.depends?.toList(),
urgency: urgency(t),
tags: t.tags?.toList(),
annotations: (t.annotations ?? BuiltList<Annotation>())
.map((a) => ChampionAnn.Annotation(
description: a.description,
entry: a.entry?.toIso8601String() ?? DateTime.now().toIso8601String(),
))
.toList()
);
}
// its convert TaskForC into Task
Task convertTaskForCToTask(TaskForC t) {
return Task((b) => b
..id = t.id
..uuid = t.uuid
..description = t.description
..status = t.status
..project = t.project
..priority = t.priority
..due = t.due != null ? DateTime.parse(t.due!) : null
..start = t.start != null ? DateTime.parse(t.start!) : null
..wait = t.wait != null ? DateTime.parse(t.wait!) : null
..entry = DateTime.parse(t.entry)
..modified = t.modified != null
? DateTime.parse(t.modified!)
: null
..end = t.end != null ? DateTime.parse(t.end!) : null
Comment on lines +293 to +300
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the file
find . -name "home_controller.dart" -type f

Repository: CCExtractor/taskwarrior-flutter

Length of output: 130


🏁 Script executed:

# Read the specific file around the mentioned lines
if [ -f "lib/app/modules/home/controllers/home_controller.dart" ]; then
  echo "=== Lines 290-315 ===" 
  sed -n '290,315p' lib/app/modules/home/controllers/home_controller.dart | cat -n
fi

Repository: CCExtractor/taskwarrior-flutter

Length of output: 1195


🏁 Script executed:

# Search for all DateTime.parse and DateTime.tryParse usage in the codebase
rg -n "DateTime\.(parse|tryParse)" --type=dart -B2 -A2

Repository: CCExtractor/taskwarrior-flutter

Length of output: 15552


🏁 Script executed:

# Check specifically in the home_controller.dart file
if [ -f "lib/app/modules/home/controllers/home_controller.dart" ]; then
  echo "=== DateTime parsing in home_controller.dart ===" 
  rg -n "DateTime\.(parse|tryParse)" lib/app/modules/home/controllers/home_controller.dart -B3 -A3
fi

Repository: CCExtractor/taskwarrior-flutter

Length of output: 1416


🏁 Script executed:

# Get context about the convertTaskForCToTask method
if [ -f "lib/app/modules/home/controllers/home_controller.dart" ]; then
  echo "=== Searching for convertTaskForCToTask method ===" 
  rg -n "convertTaskForCToTask" lib/app/modules/home/controllers/home_controller.dart -B5 -A50
fi

Repository: CCExtractor/taskwarrior-flutter

Length of output: 2155


Harden persisted timestamp parsing in task conversions.

Lines 293–300 and 309 use DateTime.parse() on stored values without defensive checks. Line 296 (..entry = DateTime.parse(t.entry)) is particularly risky—any malformed or empty string will throw FormatException and crash task loading. Empty strings pass null-checks but will still cause crashes.

Replace with DateTime.tryParse() and explicit fallback handling. This pattern is already used elsewhere in the codebase (e.g., lines 357, 367 in taskc_details_controller.dart).

Also applies to the similar conversion method at lines 234–242 (convertTaskForReplicaToTask).

Proposed fix example
   Task convertTaskForCToTask(TaskForC t) {
+  DateTime? _safeParse(String? raw) {
+    if (raw == null || raw.isEmpty) return null;
+    return DateTime.tryParse(raw);
+  }
+  final fallbackNow = DateTime.now().toUtc();
+
   return Task((b) => b
     ..id = t.id
     ..uuid = t.uuid
     ..description = t.description
     ..status = t.status
     ..project = t.project
     ..priority = t.priority
-    ..due = t.due != null ? DateTime.parse(t.due!) : null
-    ..start = t.start != null ? DateTime.parse(t.start!) : null
-    ..wait = t.wait != null ? DateTime.parse(t.wait!) : null
-    ..entry = DateTime.parse(t.entry)
-    ..modified = t.modified != null
-        ? DateTime.parse(t.modified!)
-        : null
-    ..end = t.end != null ? DateTime.parse(t.end!) : null
+    ..due = _safeParse(t.due)
+    ..start = _safeParse(t.start)
+    ..wait = _safeParse(t.wait)
+    ..entry = _safeParse(t.entry) ?? fallbackNow
+    ..modified = _safeParse(t.modified)
+    ..end = _safeParse(t.end)
     ..tags = t.tags != null
         ? ListBuilder<String>(t.tags!)
         : null
     ..annotations = t.annotations != null
         ? ListBuilder<Annotation>(
             t.annotations!.map(
               (a) => Annotation((ann) => ann
                 ..description = a.description ?? ''
-                ..entry = DateTime.parse(a.entry ?? DateTime.now().toIso8601String())),
+                ..entry = _safeParse(a.entry) ?? fallbackNow),
             ),
           )
         : null     
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/app/modules/home/controllers/home_controller.dart` around lines 293 -
300, The task conversion code is currently using DateTime.parse on persisted
strings (fields t.entry, t.due, t.start, t.wait, t.modified, t.end) which will
throw FormatException for empty or malformed values; update the conversion logic
in the method containing those chained assignments and in the similar
convertTaskForReplicaToTask method to use DateTime.tryParse(...) for each
timestamp and fall back to null (or an appropriate default) when tryParse
returns null, ensuring you replace DateTime.parse(...) calls for t.entry and the
other fields with defensive tryParse usage and explicit null handling.

..tags = t.tags != null
? ListBuilder<String>(t.tags!)
: null
..annotations = t.annotations != null
? ListBuilder<Annotation>(
t.annotations!.map(
(a) => Annotation((ann) => ann
..description = a.description ?? ''
..entry = DateTime.parse(a.entry ?? DateTime.now().toIso8601String())),
),
)
: null
);
}
void addListenerToScrollController() {
scrollController.addListener(() {
//scroll listener
Expand Down
7 changes: 5 additions & 2 deletions lib/app/modules/home/views/show_tasks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ class TaskViewBuilder extends StatelessWidget {
color: tColors.secondaryBackgroundColor,
child: InkWell(
splashColor: tColors.primaryBackgroundColor,
onTap: () =>
Get.toNamed(Routes.TASKC_DETAILS, arguments: task),
// onTap: () =>
// Get.toNamed(Routes.TASKC_DETAILS, arguments: task),
onTap: () =>
// print("NAVIGATING TO TASK_INFO_ROUTE");
Get.toNamed(Routes.TASK_INFO_ROUTE, arguments: task),
child: Container(
decoration: BoxDecoration(
border: Border.all(
Expand Down
3 changes: 2 additions & 1 deletion lib/app/modules/home/views/show_tasks_replica.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ class TaskReplicaViewBuilder extends StatelessWidget {
child: InkWell(
splashColor: tColors.primaryBackgroundColor,
onTap: () =>
Get.toNamed(Routes.TASKC_DETAILS, arguments: task),
// Get.toNamed(Routes.TASKC_DETAILS, arguments: task),
Get.toNamed(Routes.TASK_INFO_ROUTE, arguments: task),
child: Container(
decoration: BoxDecoration(
border: Border.all(
Expand Down
4 changes: 2 additions & 2 deletions lib/app/modules/home/views/tasks_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ class TasksBuilder extends StatelessWidget {
child: InkWell(
splashColor: tColors.secondaryBackgroundColor,
onTap: () {
Get.toNamed(Routes.DETAIL_ROUTE,
Get.toNamed(Routes.TASK_INFO_ROUTE,
arguments: ["uuid", task.uuid]);
},
// child: Text(task.entry.toString()),
Expand Down Expand Up @@ -271,7 +271,7 @@ class TasksBuilder extends StatelessWidget {
child: InkWell(
splashColor: tColors.secondaryBackgroundColor,
onTap: () {
Get.toNamed(Routes.DETAIL_ROUTE,
Get.toNamed(Routes.TASK_INFO_ROUTE,
arguments: ["uuid", task.uuid]);
},
// child: Text(task.entry.toString()),
Expand Down
15 changes: 15 additions & 0 deletions lib/app/modules/infoRoute/bindings/task_info_binding.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:get/get.dart';
Comment on lines +1 to +4
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Step 1: Verify dart:io import in the specific file
echo "=== Checking the specific file ==="
cat -n lib/app/modules/infoRoute/bindings/task_info_binding.dart | head -20

Repository: CCExtractor/taskwarrior-flutter

Length of output: 719


🏁 Script executed:

#!/bin/bash
# Step 2: Check if dart:io is actually used in the binding file
echo "=== Checking if dart:io is used in the binding ==="
rg "dart:io" lib/app/modules/infoRoute/bindings/task_info_binding.dart -A 5 -B 5

Repository: CCExtractor/taskwarrior-flutter

Length of output: 450


🏁 Script executed:

#!/bin/bash
# Step 3: Search for all dart:io imports in lib/
echo "=== All dart:io imports in lib/ ==="
rg -n "^import 'dart:io';" lib/ -C 1

Repository: CCExtractor/taskwarrior-flutter

Length of output: 5009


🏁 Script executed:

#!/bin/bash
# Step 4: Check if this binding is conditionally imported for web
echo "=== Searching for conditional imports or web guards ==="
rg -n "dart:io|web" lib/app/modules/infoRoute/bindings/ -B 2 -A 2

Repository: CCExtractor/taskwarrior-flutter

Length of output: 425


Remove unused dart:io import from this binding.

Line 1 imports dart:io but it is not used anywhere in the binding file. Bindings should only import dependencies they directly reference. Removing this unused import improves code cleanliness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/app/modules/infoRoute/bindings/task_info_binding.dart` around lines 1 -
4, Remove the unused import 'dart:io' from task_info_binding.dart (the binding
that contains imports including package:flutter/material.dart,
package:flutter_slidable/flutter_slidable.dart, and package:get/get.dart);
locate the top-of-file import list in the TaskInfo binding and delete the line
"import 'dart:io';" so only used packages remain imported.

import 'package:taskwarrior/app/modules/infoRoute/controllers/tasks_info_route_controller.dart';
// import 'package:taskwarrior/app/modules/infoRoute/views/tasks_info_view.dart';

class TasksInfoRouteBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<TasksInfoRouteController>(
() => TasksInfoRouteController(),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart';
import 'package:taskwarrior/app/models/json/task.dart';
import 'package:collection/collection.dart';
class TasksInfoRouteController extends GetxController {
late String uuid;
var task = Rxn<Task>();
@override
void onInit() {
super.onInit();
var arguments = Get.arguments;
// uuid = arguments[1] as String;
if(arguments is List){
uuid = arguments[1] as String;
} else{
uuid = arguments.uuid;
}
Comment on lines +15 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n lib/app/modules/infoRoute/controllers/tasks_info_route_controller.dart | head -40

Repository: CCExtractor/taskwarrior-flutter

Length of output: 1622


🏁 Script executed:

rg -nP --type=dart "Routes\.TASK_INFO_ROUTE" -B2 -A2

Repository: CCExtractor/taskwarrior-flutter

Length of output: 2685


🏁 Script executed:

rg -nP --type=dart "Get\.toNamed\(\s*Routes\.TASK_INFO_ROUTE" -C3

Repository: CCExtractor/taskwarrior-flutter

Length of output: 3137


🏁 Script executed:

rg -nP --type=dart "TASK_INFO_ROUTE" -C2

Repository: CCExtractor/taskwarrior-flutter

Length of output: 3944


Validate List bounds and argument shape before assigning uuid.

Lines 15–19 assume List has at least 2 elements and non-list arguments have a .uuid property. Add defensive checks to prevent RangeError and NoSuchMethodError on unexpected argument shapes.

Minimal fix
-    if(arguments is List){
-      uuid = arguments[1] as String;
-    } else{
-      uuid = arguments.uuid;
-    }
+    if (arguments is List && arguments.length > 1) {
+      uuid = arguments[1] as String;
+    } else if (arguments is! List) {
+      uuid = arguments.uuid;
+    } else {
+      throw ArgumentError('Invalid TASK_INFO_ROUTE arguments: $arguments');
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(arguments is List){
uuid = arguments[1] as String;
} else{
uuid = arguments.uuid;
}
if (arguments is List && arguments.length > 1) {
uuid = arguments[1] as String;
} else if (arguments is! List) {
uuid = arguments.uuid;
} else {
throw ArgumentError('Invalid TASK_INFO_ROUTE arguments: $arguments');
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/app/modules/infoRoute/controllers/tasks_info_route_controller.dart`
around lines 15 - 19, The current assignment of uuid assumes arguments is a List
with at least two items or an object with a .uuid property; add defensive
validation before using arguments: when arguments is List check arguments.length
> 1 and that arguments[1] is a non-null String before assigning uuid, otherwise
handle the error or fallback; when not a List, check that arguments has a uuid
(e.g., arguments != null && arguments.uuid is String) or provide alternative
extraction (e.g., check for Map with 'uuid' key) and handle missing/invalid
values consistently (return error, null, or throw). Update the block around the
if(arguments is List) { ... } else { ... } to perform these guards and clear
fallback/error handling.

loadTask();
}
Future<void> loadTask() async {
final homeController = Get.find<HomeController>();
Task? foundTask;
if (homeController.taskchampion.value) {
final dbTask = await homeController.taskdb.getTaskByUuid(uuid);

if (dbTask != null) {
foundTask =
homeController.convertTaskForCToTask(dbTask);
}
}
else if (homeController.taskReplica.value) {
await homeController.refreshReplicaTaskList();
final replicaTask = homeController.tasksFromReplica
.firstWhereOrNull((t) => t.uuid == uuid);
if (replicaTask != null) {
foundTask =
homeController.convertReplicaToTask(replicaTask);
}
}
else {
foundTask = homeController.getTask(uuid);
}
task.value = foundTask;
}
}
Loading