From 976b39da98a0d4ba951adf906c6460c10ff33e54 Mon Sep 17 00:00:00 2001 From: Shubham Ingale Date: Sat, 30 Aug 2025 13:54:17 +0530 Subject: [PATCH 1/7] Revert "Ccsync integrate latest (#497)" This reverts commit 5cb76575368ab30d119e74a59d0faa0828b1a79d. --- lib/app/modules/home/views/show_details.dart | 142 +--- lib/app/v3/db/task_database.dart | 691 ++++--------------- lib/app/v3/models/annotation.dart | 22 +- lib/app/v3/models/task.dart | 128 ++-- 4 files changed, 231 insertions(+), 752 deletions(-) diff --git a/lib/app/modules/home/views/show_details.dart b/lib/app/modules/home/views/show_details.dart index e1a5bc44..2a0dea77 100644 --- a/lib/app/modules/home/views/show_details.dart +++ b/lib/app/modules/home/views/show_details.dart @@ -8,9 +8,8 @@ import 'package:taskwarrior/app/utils/constants/utilites.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; import 'package:taskwarrior/app/v3/db/task_database.dart'; -import 'package:taskwarrior/app/v3/models/annotation.dart'; -import 'package:taskwarrior/app/v3/models/task.dart'; // Ensure this path is correct for your new model -// import 'package:taskwarrior/app/v3/net/modify.dart'; +import 'package:taskwarrior/app/v3/models/task.dart'; +import 'package:taskwarrior/app/v3/net/modify.dart'; class TaskDetails extends StatefulWidget { final TaskForC task; @@ -26,14 +25,6 @@ class _TaskDetailsState extends State { late String status; late String priority; late String due; - late String start; - late String wait; - late List tags; - late List depends; - late String rtype; - late String recur; - late List annotations; - late TaskDatabase taskDatabase; bool hasChanges = false; @@ -42,22 +33,11 @@ class _TaskDetailsState extends State { void initState() { super.initState(); description = widget.task.description; - project = widget.task.project ?? '-'; + project = widget.task.project!; status = widget.task.status; - priority = widget.task.priority ?? '-'; + priority = widget.task.priority!; due = widget.task.due ?? '-'; - start = widget.task.start ?? '-'; - wait = widget.task.wait ?? '-'; - tags = widget.task.tags ?? []; - depends = widget.task.depends ?? []; - rtype = widget.task.rtype ?? '-'; - recur = widget.task.recur ?? '-'; - annotations = widget.task.annotations ?? []; - - due = _buildDate(due); // Format the date for display - start = _buildDate(start); - wait = _buildDate(wait); - + due = _buildDate(due); setState(() { taskDatabase = TaskDatabase(); taskDatabase.open(); @@ -67,10 +47,6 @@ class _TaskDetailsState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - // This part seems redundant if taskDatabase.open() is already in initState - // and ideally, the database connection should be managed more robustly - // (e.g., singleton, provider, or passed down). - // However, keeping it as per original logic, but be aware of potential multiple openings. taskDatabase = TaskDatabase(); taskDatabase.open(); } @@ -133,7 +109,7 @@ class _TaskDetailsState extends State { _buildSelectableDetail( '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPagePriority}:', priority, - ['H', 'M', 'L', '-'], (value) { + ['H', 'M', 'L'], (value) { setState(() { priority = value; hasChanges = true; @@ -147,52 +123,10 @@ class _TaskDetailsState extends State { hasChanges = true; }); }), - _buildDatePickerDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageStart}:', - start, (value) { - setState(() { - start = value; - hasChanges = true; - }); - }), - _buildDatePickerDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageWait}:', - wait, (value) { - setState(() { - wait = value; - hasChanges = true; - }); - }), - _buildEditableDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageTags}:', - tags.join(', '), (value) { - setState(() { - tags = value.split(',').map((e) => e.trim()).toList(); - hasChanges = true; - }); - }), - _buildEditableDetail('Depends:', depends.join(', '), (value) { - setState(() { - depends = value.split(',').map((e) => e.trim()).toList(); - hasChanges = true; - }); - }), - _buildEditableDetail('Rtype:', rtype, (value) { - setState(() { - rtype = value; - hasChanges = true; - }); - }), - _buildEditableDetail('Recur:', recur, (value) { - setState(() { - recur = value; - hasChanges = true; - }); - }), - _buildDetail('UUID:', widget.task.uuid ?? '-'), + _buildDetail('UUID:', widget.task.uuid!), _buildDetail( '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageUrgency}:', - widget.task.urgency?.toStringAsFixed(2) ?? '-'), + widget.task.urgency.toString()), _buildDetail( '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageEnd}:', _buildDate(widget.task.end)), @@ -202,11 +136,6 @@ class _TaskDetailsState extends State { _buildDetail( '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageModified}:', _buildDate(widget.task.modified)), - _buildDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences}:', - annotations.isNotEmpty - ? annotations.map((e) => e.description).join('\n') - : '-'), ], ), ), @@ -254,9 +183,7 @@ class _TaskDetailsState extends State { onTap: () async { final DateTime? pickedDate = await showDatePicker( context: context, - initialDate: value != '-' - ? DateTime.tryParse(value) ?? DateTime.now() - : DateTime.now(), + initialDate: value != '-' ? DateTime.parse(value) : DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2101), builder: (BuildContext context, Widget? child) { @@ -269,9 +196,8 @@ class _TaskDetailsState extends State { if (pickedDate != null) { final TimeOfDay? pickedTime = await showTimePicker( context: context, - initialTime: TimeOfDay.fromDateTime(value != '-' - ? DateTime.tryParse(value) ?? DateTime.now() - : DateTime.now()), + initialTime: TimeOfDay.fromDateTime( + value != '-' ? DateTime.parse(value) : DateTime.now()), ); if (pickedTime != null) { final DateTime fullDateTime = DateTime( @@ -281,9 +207,6 @@ class _TaskDetailsState extends State { pickedTime.hour, pickedTime.minute); onChanged(DateFormat('yyyy-MM-dd HH:mm:ss').format(fullDateTime)); - } else { - // If only date is picked, use current time - onChanged(DateFormat('yyyy-MM-dd HH:mm:ss').format(pickedDate)); } } }, @@ -491,36 +414,25 @@ class _TaskDetailsState extends State { } Future _saveTask() async { - // Update the TaskForC object with the new values - // final updatedTask = TaskForC( - // id: widget.task.id, - // description: description, - // project: project == '-' ? null : project, - // status: status, - // uuid: widget.task.uuid, - // urgency: widget - // .task.urgency, // Urgency is typically calculated, not edited directly - // priority: priority == '-' ? null : priority, - // due: due == '-' ? null : due, - // start: start == '-' ? null : start, - // end: widget - // .task.end, // 'end' is usually set when completed, not edited directly - // entry: widget.task.entry, // 'entry' is static - // wait: wait == '-' ? null : wait, - // modified: DateFormat('yyyy-MM-dd HH:mm:ss') - // .format(DateTime.now()), // Update modified time - // tags: tags.isEmpty ? null : tags, - // depends: depends.isEmpty ? null : depends, - // rtype: rtype == '-' ? null : rtype, - // recur: recur == '-' ? null : recur, - // annotations: annotations.isEmpty ? null : annotations, - // ); - + await taskDatabase.saveEditedTaskInDB( + widget.task.uuid!, + description, + project, + status, + priority, + due, + ); setState(() { hasChanges = false; }); - // Assuming modifyTaskOnTaskwarrior takes the updated TaskForC object - // await modifyTaskOnTaskwarrior(updatedTask); + modifyTaskOnTaskwarrior( + description, + project, + due, + priority, + status, + widget.task.uuid!, + ); } } diff --git a/lib/app/v3/db/task_database.dart b/lib/app/v3/db/task_database.dart index b1da7997..0216e810 100644 --- a/lib/app/v3/db/task_database.dart +++ b/lib/app/v3/db/task_database.dart @@ -1,8 +1,7 @@ -import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; -import 'package:taskwarrior/app/v3/models/annotation.dart'; -import 'package:taskwarrior/app/v3/models/task.dart'; // Path to your updated TaskForC model +import 'package:taskwarrior/app/v3/models/task.dart'; class TaskDatabase { Database? _database; @@ -11,441 +10,124 @@ class TaskDatabase { var databasesPath = await getDatabasesPath(); String path = join(databasesPath, 'tasks.db'); - _database = await openDatabase( - path, - version: 2, - onCreate: (Database db, version) async { - // Create the main Tasks table - await db.execute(''' - CREATE TABLE Tasks ( - uuid TEXT PRIMARY KEY, - id INTEGER, - description TEXT, - project TEXT, - status TEXT, - urgency REAL, - priority TEXT, - due TEXT, - start TEXT, -- New column - end TEXT, - entry TEXT, - wait TEXT, -- New column - modified TEXT, - rtype TEXT, -- New column - recur TEXT -- New column - ) - '''); - - // Create TaskTags table - await db.execute(''' - CREATE TABLE TaskTags ( - task_uuid TEXT NOT NULL, - tag TEXT NOT NULL, - PRIMARY KEY (task_uuid, tag), - FOREIGN KEY (task_uuid) REFERENCES Tasks (uuid) ON DELETE CASCADE - ) - '''); - - // Create TaskDepends table - await db.execute(''' - CREATE TABLE TaskDepends ( - task_uuid TEXT NOT NULL, - depends_on_uuid TEXT NOT NULL, - PRIMARY KEY (task_uuid, depends_on_uuid), - FOREIGN KEY (task_uuid) REFERENCES Tasks (uuid) ON DELETE CASCADE, - FOREIGN KEY (depends_on_uuid) REFERENCES Tasks (uuid) ON DELETE CASCADE - ) - '''); - - // Create TaskAnnotations table - await db.execute(''' - CREATE TABLE TaskAnnotations ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - task_uuid TEXT NOT NULL, - entry TEXT NOT NULL, - description TEXT NOT NULL, - FOREIGN KEY (task_uuid) REFERENCES Tasks (uuid) ON DELETE CASCADE - ) - '''); - - debugPrint( - "All tables created: Tasks, TaskTags, TaskDepends, TaskAnnotations"); - }, - onUpgrade: (db, oldVersion, newVersion) async { - // This code runs when an existing database is opened with a higher version. - debugPrint("Database upgrade from version $oldVersion to $newVersion"); - - // Example: Migrating from version 1 to version 2 - if (oldVersion < 2) { - // 1. Add new columns to the 'Tasks' table - try { - await db.execute("ALTER TABLE Tasks ADD COLUMN start TEXT"); - } catch (e) { - debugPrint("Could not add 'start' column: $e"); - } - try { - await db.execute("ALTER TABLE Tasks ADD COLUMN wait TEXT"); - } catch (e) { - debugPrint("Could not add 'wait' column: $e"); - } - try { - await db.execute("ALTER TABLE Tasks ADD COLUMN rtype TEXT"); - } catch (e) { - debugPrint("Could not add 'rtype' column: $e"); - } - try { - await db.execute("ALTER TABLE Tasks ADD COLUMN recur TEXT"); - } catch (e) { - debugPrint("Could not add 'recur' column: $e"); - } - - // 2. Create the new relational tables - await db.execute(''' - CREATE TABLE TaskTags ( - task_uuid TEXT NOT NULL, - tag TEXT NOT NULL, - PRIMARY KEY (task_uuid, tag), - FOREIGN KEY (task_uuid) REFERENCES Tasks (uuid) ON DELETE CASCADE - ) - '''); - await db.execute(''' - CREATE TABLE TaskDepends ( - task_uuid TEXT NOT NULL, - depends_on_uuid TEXT NOT NULL, - PRIMARY KEY (task_uuid, depends_on_uuid), - FOREIGN KEY (task_uuid) REFERENCES Tasks (uuid) ON DELETE CASCADE, - FOREIGN KEY (depends_on_uuid) REFERENCES Tasks (uuid) ON DELETE CASCADE - ) - '''); + _database = await openDatabase(path, + version: 1, + onOpen: (db) async => await addTagsColumnIfNeeded(db), + onCreate: (Database db, version) async { await db.execute(''' - CREATE TABLE TaskAnnotations ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - task_uuid TEXT NOT NULL, - entry TEXT NOT NULL, - description TEXT NOT NULL, - FOREIGN KEY (task_uuid) REFERENCES Tasks (uuid) ON DELETE CASCADE - ) - '''); - debugPrint("New tables and columns added for upgrade to version 2."); - - // 3. Migrate data from the old 'tags' column in 'Tasks' to the new 'TaskTags' table - // This is crucial if your old schema stored tags as a space-separated string. - try { - final List> oldTasksWithTags = - await db.query('Tasks', - columns: ['uuid', 'tags'], // Select old tags column - where: 'tags IS NOT NULL AND tags != ""'); - - await db.transaction((txn) async { - for (var oldTask in oldTasksWithTags) { - final String taskUuid = oldTask['uuid']; - final String tagsString = oldTask['tags']; - final List tags = - tagsString.split(' ').where((s) => s.isNotEmpty).toList(); - - for (String tag in tags) { - await txn.insert( - 'TaskTags', - {'task_uuid': taskUuid, 'tag': tag}, - conflictAlgorithm: ConflictAlgorithm.ignore, - ); - } - } - }); - debugPrint( - "Migrated tags data from old 'tags' column to 'TaskTags' table."); - } catch (e) { - debugPrint( - "Error migrating old tags data (possibly old 'tags' column didn't exist or was empty): $e"); - } - - // 4. (Optional but recommended) Drop the old 'tags' column from 'Tasks' table - // Only do this after you are absolutely sure the data migration is successful. - try { - await db.execute("ALTER TABLE Tasks DROP COLUMN tags"); - debugPrint("Dropped old 'tags' column from 'Tasks' table."); - } catch (e) { - debugPrint( - "Could not drop old 'tags' column (might not exist): $e"); - } - } - }, - ); + CREATE TABLE Tasks ( + uuid TEXT PRIMARY KEY, + id INTEGER, + description TEXT, + project TEXT, + status TEXT, + urgency REAL, + priority TEXT, + due TEXT, + end TEXT, + entry TEXT, + modified TEXT + ) + '''); + }); } - Future ensureDatabaseIsOpen() async { - if (_database == null || !_database!.isOpen) { - await open(); + Future addTagsColumnIfNeeded(Database db) async { + try { + await db.rawQuery("SELECT tags FROM Tasks LIMIT 0"); + } catch (e) { + await db.execute("ALTER TABLE Tasks ADD COLUMN tags TEXT"); + debugPrint("Added Column tags"); } } - // Helper method to convert a database map to a TaskForC object - Future _taskFromDbMap(Map taskMap) async { - final String taskUuid = taskMap['uuid'] as String; - - // Fetch Tags - final List> tagMaps = await _database!.query( - 'TaskTags', - columns: ['tag'], - where: 'task_uuid = ?', - whereArgs: [taskUuid], - ); - final List tags = - tagMaps.map((map) => map['tag'] as String).toList(); - - // Fetch Depends - final List> dependsMaps = await _database!.query( - 'TaskDepends', - columns: ['depends_on_uuid'], - where: 'task_uuid = ?', - whereArgs: [taskUuid], - ); - final List depends = - dependsMaps.map((map) => map['depends_on_uuid'] as String).toList(); - - // Fetch Annotations - final List> annotationMaps = await _database!.query( - 'TaskAnnotations', - where: 'task_uuid = ?', - whereArgs: [taskUuid], - ); - final List annotations = - annotationMaps.map((map) => Annotation.fromJson(map)).toList(); - - return TaskForC( - id: taskMap['id'], - description: taskMap['description'], - project: taskMap['project'], - status: taskMap['status'], - uuid: taskMap['uuid'], - urgency: (taskMap['urgency'] as num?)?.toDouble(), - priority: taskMap['priority'], - due: taskMap['due'], - start: taskMap['start'], - end: taskMap['end'], - entry: taskMap['entry'], - wait: taskMap['wait'], - modified: taskMap['modified'], - rtype: taskMap['rtype'], - recur: taskMap['recur'], - tags: tags, - depends: depends, - annotations: annotations, - ); - } - - // Helper method to convert a TaskForC object to a map for the 'Tasks' table - Map _taskToDbMap(TaskForC task) { - return { - 'id': task.id, - 'description': task.description, - 'project': task.project, - 'status': task.status, - 'uuid': task.uuid, - 'urgency': task.urgency, - 'priority': task.priority, - 'due': task.due, - 'start': task.start, - 'end': task.end, - 'entry': task.entry, - 'wait': task.wait, - 'modified': task.modified, - 'rtype': task.rtype, - 'recur': task.recur, - }; + Future ensureDatabaseIsOpen() async { + if (_database == null) { + await open(); + } } - // --- CRUD Operations for TaskForC --- - Future> fetchTasksFromDatabase() async { await ensureDatabaseIsOpen(); - final List> taskMaps = await _database!.query('Tasks'); - debugPrint("Database fetch: ${taskMaps.length} tasks found."); - - List tasks = []; - for (var taskMap in taskMaps) { - tasks.add(await _taskFromDbMap(taskMap)); - } - return tasks; + final List> maps = await _database!.query('Tasks'); + debugPrint("Database fetch ${maps.last}"); + var a = List.generate(maps.length, (i) { + return TaskForC( + id: maps[i]['id'], + description: maps[i]['description'], + project: maps[i]['project'], + status: maps[i]['status'], + uuid: maps[i]['uuid'], + urgency: maps[i]['urgency'], + priority: maps[i]['priority'], + due: maps[i]['due'], + end: maps[i]['end'], + entry: maps[i]['entry'], + modified: maps[i]['modified'], + tags: maps[i]['tags'] != null ? maps[i]['tags'].split(' ') : []); + }); + // debugPrint('Tasks from db'); + // debugPrint(a.toString()); + return a; } Future deleteAllTasksInDB() async { await ensureDatabaseIsOpen(); - await _database!.transaction((txn) async { - await txn.delete('TaskAnnotations'); - await txn.delete('TaskDepends'); - await txn.delete('TaskTags'); - await txn.delete('Tasks'); - }); - - debugPrint('Deleted all tasks and related data'); + await _database!.delete('Tasks'); + debugPrint('Deleted all tasks'); + await open(); + debugPrint('Created new task table'); } Future printDatabaseContents() async { await ensureDatabaseIsOpen(); - debugPrint('--- Contents of Tasks table ---'); - List> tasks = await _database!.query('Tasks'); - for (var map in tasks) { - debugPrint(map.toString()); - } - - debugPrint('--- Contents of TaskTags table ---'); - List> taskTags = await _database!.query('TaskTags'); - for (var map in taskTags) { - debugPrint(map.toString()); - } - - debugPrint('--- Contents of TaskDepends table ---'); - List> taskDepends = - await _database!.query('TaskDepends'); - for (var map in taskDepends) { - debugPrint(map.toString()); - } - - debugPrint('--- Contents of TaskAnnotations table ---'); - List> taskAnnotations = - await _database!.query('TaskAnnotations'); - for (var map in taskAnnotations) { - debugPrint(map.toString()); + List> maps = await _database!.query('Tasks'); + for (var map in maps) { + map.forEach((key, value) { + debugPrint('Key: $key, Value: $value, Type: ${value.runtimeType}'); + }); } } Future insertTask(TaskForC task) async { await ensureDatabaseIsOpen(); - debugPrint("Database Insert: Starting for task UUID: ${task.uuid}"); - - await _database!.transaction((txn) async { - // 1. Insert into Tasks table - await txn.insert( - 'Tasks', - _taskToDbMap(task), // Use helper to get map for main table - conflictAlgorithm: ConflictAlgorithm.replace, - ); - debugPrint("Inserted main task data for UUID: ${task.uuid}"); - - // 2. Insert into TaskTags table - if (task.tags != null && task.tags!.isNotEmpty) { - for (String tag in task.tags!) { - await txn.insert( - 'TaskTags', - {'task_uuid': task.uuid, 'tag': tag}, - conflictAlgorithm: ConflictAlgorithm.ignore, - ); - } - debugPrint("Inserted tags for UUID: ${task.uuid}"); - } - - // 3. Insert into TaskDepends table - if (task.depends != null && task.depends!.isNotEmpty) { - for (String dependsOnUuid in task.depends!) { - await txn.insert( - 'TaskDepends', - {'task_uuid': task.uuid, 'depends_on_uuid': dependsOnUuid}, - conflictAlgorithm: ConflictAlgorithm.ignore, - ); - } - debugPrint("Inserted dependencies for UUID: ${task.uuid}"); - } - - // 4. Insert into TaskAnnotations table - if (task.annotations != null && task.annotations!.isNotEmpty) { - for (Annotation annotation in task.annotations!) { - await txn.insert( - 'TaskAnnotations', - { - 'task_uuid': task.uuid, - 'entry': annotation.entry, - 'description': annotation.description, - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - } - debugPrint("Inserted annotations for UUID: ${task.uuid}"); - } - }); - debugPrint("Database Insert Complete for task UUID: ${task.uuid}"); + debugPrint("Database Insert"); + var dbi = await _database!.insert( + 'Tasks', + task.toDbJson(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + debugPrint("Database Insert ${task.toDbJson()} $dbi"); } Future updateTask(TaskForC task) async { await ensureDatabaseIsOpen(); - debugPrint("Database Update: Starting for task UUID: ${task.uuid}"); - - await _database!.transaction((txn) async { - // 1. Update main Tasks table - await txn.update( - 'Tasks', - _taskToDbMap(task), // Use helper to get map for main table - where: 'uuid = ?', - whereArgs: [task.uuid], - ); - debugPrint("Updated main task data for UUID: ${task.uuid}"); - - // 2. Update TaskTags table: Delete existing, then insert new - await txn - .delete('TaskTags', where: 'task_uuid = ?', whereArgs: [task.uuid]); - if (task.tags != null && task.tags!.isNotEmpty) { - for (String tag in task.tags!) { - await txn.insert( - 'TaskTags', - {'task_uuid': task.uuid, 'tag': tag}, - conflictAlgorithm: ConflictAlgorithm.ignore, - ); - } - } - debugPrint("Updated tags for UUID: ${task.uuid}"); - - // 3. Update TaskDepends table: Delete existing, then insert new - await txn.delete('TaskDepends', - where: 'task_uuid = ?', whereArgs: [task.uuid]); - if (task.depends != null && task.depends!.isNotEmpty) { - for (String dependsOnUuid in task.depends!) { - await txn.insert( - 'TaskDepends', - {'task_uuid': task.uuid, 'depends_on_uuid': dependsOnUuid}, - conflictAlgorithm: ConflictAlgorithm.ignore, - ); - } - } - debugPrint("Updated dependencies for UUID: ${task.uuid}"); - - // 4. Update TaskAnnotations table: Delete existing, then insert new - await txn.delete('TaskAnnotations', - where: 'task_uuid = ?', whereArgs: [task.uuid]); - if (task.annotations != null && task.annotations!.isNotEmpty) { - for (Annotation annotation in task.annotations!) { - await txn.insert( - 'TaskAnnotations', - { - 'task_uuid': task.uuid, - 'entry': annotation.entry, - 'description': annotation.description, - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - } - } - debugPrint("Updated annotations for UUID: ${task.uuid}"); - }); - debugPrint("Database Update Complete for task UUID: ${task.uuid}"); + + await _database!.update( + 'Tasks', + task.toDbJson(), + where: 'uuid = ?', + whereArgs: [task.uuid], + ); } Future getTaskByUuid(String uuid) async { await ensureDatabaseIsOpen(); - final List> taskMaps = await _database!.query( + List> maps = await _database!.query( 'Tasks', where: 'uuid = ?', whereArgs: [uuid], ); - if (taskMaps.isEmpty) { + if (maps.isNotEmpty) { + return TaskForC.fromDbJson(maps.first); + } else { return null; } - - return await _taskFromDbMap(taskMaps.first); } Future markTaskAsCompleted(String uuid) async { @@ -457,98 +139,46 @@ class TaskDatabase { where: 'uuid = ?', whereArgs: [uuid], ); - debugPrint('Task $uuid marked as completed'); + debugPrint('task${uuid}completed'); + debugPrint({DateTime.now().toIso8601String()}.toString()); } Future markTaskAsDeleted(String uuid) async { await ensureDatabaseIsOpen(); - // Due to FOREIGN KEY ... ON DELETE CASCADE, related tags, depends, and annotations - // will be automatically deleted when the main task is deleted from the Tasks table. - await _database!.delete( + await _database!.update( 'Tasks', + {'status': 'deleted'}, where: 'uuid = ?', whereArgs: [uuid], ); - debugPrint('Task $uuid deleted (and related data cascaded)'); + debugPrint('task${uuid}deleted'); } Future saveEditedTaskInDB( String uuid, String newDescription, - String? newProject, + String newProject, String newStatus, - String? newPriority, - String? newDue, - List? newTags, - List? newDepends, - List? newAnnotations, + String newPriority, + String newDue, ) async { await ensureDatabaseIsOpen(); - debugPrint('Saving edited task $uuid'); - - await _database!.transaction((txn) async { - await txn.update( - 'Tasks', - { - 'description': newDescription, - 'project': newProject, - 'status': newStatus, - 'priority': newPriority, - 'due': newDue, - 'modified': (DateTime.now()).toIso8601String(), - }, - where: 'uuid = ?', - whereArgs: [uuid], - ); - debugPrint('Main task data updated for $uuid'); - - // Update Tags - await txn.delete('TaskTags', where: 'task_uuid = ?', whereArgs: [uuid]); - if (newTags != null && newTags.isNotEmpty) { - for (String tag in newTags) { - await txn.insert( - 'TaskTags', - {'task_uuid': uuid, 'tag': tag}, - conflictAlgorithm: ConflictAlgorithm.ignore, - ); - } - } - debugPrint('Tags updated for $uuid'); - - // Update Depends - await txn - .delete('TaskDepends', where: 'task_uuid = ?', whereArgs: [uuid]); - if (newDepends != null && newDepends.isNotEmpty) { - for (String dependsOnUuid in newDepends) { - await txn.insert( - 'TaskDepends', - {'task_uuid': uuid, 'depends_on_uuid': dependsOnUuid}, - conflictAlgorithm: ConflictAlgorithm.ignore, - ); - } - } - debugPrint('Dependencies updated for $uuid'); - - // Update Annotations - await txn - .delete('TaskAnnotations', where: 'task_uuid = ?', whereArgs: [uuid]); - if (newAnnotations != null && newAnnotations.isNotEmpty) { - for (Annotation annotation in newAnnotations) { - await txn.insert( - 'TaskAnnotations', - { - 'task_uuid': uuid, - 'entry': annotation.entry, - 'description': annotation.description, - }, - conflictAlgorithm: ConflictAlgorithm.replace, - ); - } - } - debugPrint('Annotations updated for $uuid'); - }); - debugPrint('Task $uuid edited successfully'); + + debugPrint('task${uuid}deleted'); + await _database!.update( + 'Tasks', + { + 'description': newDescription, + 'project': newProject, + 'status': newStatus, + 'priority': newPriority, + 'due': newDue, + }, + where: 'uuid = ?', + whereArgs: [uuid], + ); + debugPrint('task${uuid}edited'); } Future> findTasksWithoutUUIDs() async { @@ -560,110 +190,67 @@ class TaskDatabase { whereArgs: [''], ); - List tasks = []; - for (var taskMap in maps) { - // Note: If task_uuid is NULL or empty, the _taskFromDbMap will attempt to query related tables - // with a potentially invalid UUID. Ensure your data integrity that tasks always have a UUID - // if they are expected to have tags/depends/annotations. - tasks.add(await _taskFromDbMap(taskMap)); - } - return tasks; + return List.generate(maps.length, (i) { + return TaskForC.fromDbJson(maps[i]); + }); } Future> getTasksByProject(String project) async { - await ensureDatabaseIsOpen(); - - List> taskMaps = await _database!.query( + List> maps = await _database!.query( 'Tasks', where: 'project = ?', whereArgs: [project], ); - debugPrint("DB Stored for $project: ${taskMaps.length} tasks"); - - List tasks = []; - for (var taskMap in taskMaps) { - tasks.add(await _taskFromDbMap(taskMap)); - } - return tasks; + debugPrint("DB Stored for $maps"); + return List.generate(maps.length, (i) { + return TaskForC( + uuid: maps[i]['uuid'], + id: maps[i]['id'], + description: maps[i]['description'], + project: maps[i]['project'], + status: maps[i]['status'], + urgency: maps[i]['urgency'], + priority: maps[i]['priority'], + due: maps[i]['due'], + end: maps[i]['end'], + entry: maps[i]['entry'], + modified: maps[i]['modified'], + tags: maps[i]['tags'].toString().split(' ')); + }); } Future> fetchUniqueProjects() async { - await ensureDatabaseIsOpen(); + var taskDatabase = TaskDatabase(); + await taskDatabase.open(); + await taskDatabase.ensureDatabaseIsOpen(); - final List> result = await _database!.rawQuery( - 'SELECT DISTINCT project FROM Tasks WHERE project IS NOT NULL AND project != ""'); + final List> result = await taskDatabase._database! + .rawQuery( + 'SELECT DISTINCT project FROM Tasks WHERE project IS NOT NULL'); return result.map((row) => row['project'] as String).toList(); } Future> searchTasks(String query) async { - await ensureDatabaseIsOpen(); - - final List> taskMaps = await _database!.query( - 'Tasks', + final List> maps = await _database!.query( + 'tasks', where: 'description LIKE ? OR project LIKE ?', whereArgs: ['%$query%', '%$query%'], ); - - List tasks = []; - for (var taskMap in taskMaps) { - tasks.add(await _taskFromDbMap(taskMap)); - } - return tasks; + return List.generate(maps.length, (i) { + return TaskForC.fromDbJson(maps[i]); + }); } Future close() async { - if (_database != null && _database!.isOpen) { - await _database!.close(); - _database = null; - } + await _database!.close(); } - Future deleteTask( - {String? description, - String? due, - String? project, - String? priority, - String? uuid}) async { - await ensureDatabaseIsOpen(); - - if (uuid != null && uuid.isNotEmpty) { - // If UUID is provided, use it for a more robust deletion which triggers cascade - await markTaskAsDeleted(uuid); - } else { - // Fallback for deleting without UUID (less reliable, does not cascade automatically for related tables) - List whereClauses = []; - List whereArgs = []; - - if (description != null) { - whereClauses.add('description = ?'); - whereArgs.add(description); - } - if (due != null) { - whereClauses.add('due = ?'); - whereArgs.add(due); - } - if (project != null) { - whereClauses.add('project = ?'); - whereArgs.add(project); - } - if (priority != null) { - whereClauses.add('priority = ?'); - whereArgs.add(priority); - } - - if (whereClauses.isNotEmpty) { - await _database!.delete( - 'Tasks', - where: whereClauses.join(' AND '), - whereArgs: whereArgs, - ); - debugPrint( - 'Task deleted using old method (description, due, project, priority). NOTE: Related data in TaskTags, TaskDepends, TaskAnnotations for this task UUID might remain if UUID was not used for deletion.'); - } else { - debugPrint( - 'Delete task called without sufficient identifying parameters (UUID or other fields). No action taken.'); - } - } + Future deleteTask({description, due, project, priority}) async { + await _database!.delete( + 'Tasks', + where: 'description = ? AND due = ? AND project = ? AND priority = ?', + whereArgs: [description, due, project, priority], + ); } } diff --git a/lib/app/v3/models/annotation.dart b/lib/app/v3/models/annotation.dart index 037b5f4c..9f8036e7 100644 --- a/lib/app/v3/models/annotation.dart +++ b/lib/app/v3/models/annotation.dart @@ -1,21 +1,5 @@ -// Annotation class to mirror the Go Annotation struct class Annotation { - final String entry; - final String description; - - Annotation({required this.entry, required this.description}); - - factory Annotation.fromJson(Map json) { - return Annotation( - entry: json['entry'], - description: json['description'], - ); - } - - Map toJson() { - return { - 'entry': entry, - 'description': description, - }; - } + final String? entry; + final String? description; + Annotation({this.entry, this.description}); } diff --git a/lib/app/v3/models/task.dart b/lib/app/v3/models/task.dart index bca01e8c..c93884f0 100644 --- a/lib/app/v3/models/task.dart +++ b/lib/app/v3/models/task.dart @@ -1,5 +1,4 @@ -import 'package:flutter/foundation.dart'; -import 'package:taskwarrior/app/v3/models/annotation.dart'; +import 'package:flutter/material.dart'; class TaskForC { final int id; @@ -10,72 +9,76 @@ class TaskForC { final double? urgency; final String? priority; final String? due; - final String? start; // Added: Corresponds to Go's 'Start' final String? end; final String entry; - final String? wait; // Added: Corresponds to Go's 'Wait' final String? modified; - final List? tags; // Changed to List to match Go's []string - final List? depends; // Added: Corresponds to Go's 'Depends' - final String? rtype; // Added: Corresponds to Go's 'RType' - final String? recur; // Added: Corresponds to Go's 'Recur' - final List? - annotations; // Added: Corresponds to Go's 'Annotations' + final List? tags; - TaskForC({ - required this.id, - required this.description, - this.project, // Made nullable to match Go's typical handling of empty strings for non-required fields - required this.status, - this.uuid, - this.urgency, - this.priority, - this.due, - this.start, - this.end, - required this.entry, - this.wait, - this.modified, - this.tags, - this.depends, - this.rtype, - this.recur, - this.annotations, - }); + TaskForC( + {required this.id, + required this.description, + required this.project, + required this.status, + required this.uuid, + required this.urgency, + required this.priority, + required this.due, + required this.end, + required this.entry, + required this.modified, + required this.tags}); - // Factory constructor for parsing JSON from API responses factory TaskForC.fromJson(Map json) { return TaskForC( - id: json['id'], - description: json['description'], - project: json['project'], - status: json['status'], - uuid: json['uuid'], - // Safely parse urgency as double, handling potential null or int from JSON - urgency: (json['urgency'] as num?)?.toDouble(), - priority: json['priority'], - due: json['due'], - start: json['start'], - end: json['end'], - entry: json['entry'], - wait: json['wait'], - modified: json['modified'], - // Ensure tags are parsed as List - tags: (json['tags'] as List?)?.map((e) => e.toString()).toList(), - // Ensure depends are parsed as List - depends: (json['depends'] as List?)?.map((e) => e.toString()).toList(), - rtype: json['rtype'], - recur: json['recur'], - // Parse list of annotation maps into list of Annotation objects - annotations: (json['annotations'] as List?) - ?.map((e) => Annotation.fromJson(e as Map)) - .toList(), - ); + id: json['id'], + description: json['description'], + project: json['project'], + status: json['status'], + uuid: json['uuid'], + urgency: json['urgency'].toDouble(), + priority: json['priority'], + due: json['due'], + end: json['end'], + entry: json['entry'], + modified: json['modified'], + tags: json['tags']); + } + factory TaskForC.fromDbJson(Map json) { + debugPrint("FROM: $json"); + return TaskForC( + id: json['id'], + description: json['description'], + project: json['project'], + status: json['status'], + uuid: json['uuid'], + urgency: json['urgency'].toDouble(), + priority: json['priority'], + due: json['due'], + end: json['end'], + entry: json['entry'], + modified: json['modified'], + tags: json['tags'].toString().split(' ')); } - // Method to convert TaskForC object to a JSON map for API requests Map toJson() { - debugPrint("TAGS TO JSON: $tags"); + debugPrint("TAGS: $tags"); + return { + 'id': id, + 'description': description, + 'project': project, + 'status': status, + 'uuid': uuid, + 'urgency': urgency, + 'priority': priority, + 'due': due, + 'end': end, + 'entry': entry, + 'modified': modified, + 'tags': tags + }; + } + + Map toDbJson() { return { 'id': id, 'description': description, @@ -85,17 +88,10 @@ class TaskForC { 'urgency': urgency, 'priority': priority, 'due': due, - 'start': start, 'end': end, 'entry': entry, - 'wait': wait, 'modified': modified, - 'tags': tags, - 'depends': depends, - 'rtype': rtype, - 'recur': recur, - // Convert list of Annotation objects to list of JSON maps - 'annotations': annotations?.map((e) => e.toJson()).toList(), + 'tags': tags != null ? tags?.join(" ") : "" }; } } From 18901582e56e348a7ef0aa7d97591ec283e60f7b Mon Sep 17 00:00:00 2001 From: Shubham Ingale Date: Sun, 31 Aug 2025 18:42:18 +0530 Subject: [PATCH 2/7] ccsync tags fn added --- .../modules/home/views/home_page_app_bar.dart | 1 + lib/app/modules/home/views/show_details.dart | 1 + .../controllers/taskc_details_controller.dart | 58 +++--- .../views/taskc_details_view.dart | 16 -- lib/app/v3/db/task_database.dart | 197 ++++++++++++------ lib/app/v3/db/update.dart | 26 ++- lib/app/v3/models/task.dart | 87 +++----- lib/app/v3/net/modify.dart | 63 ++++-- 8 files changed, 259 insertions(+), 190 deletions(-) diff --git a/lib/app/modules/home/views/home_page_app_bar.dart b/lib/app/modules/home/views/home_page_app_bar.dart index 52fade53..f4b22218 100644 --- a/lib/app/modules/home/views/home_page_app_bar.dart +++ b/lib/app/modules/home/views/home_page_app_bar.dart @@ -157,6 +157,7 @@ class HomePageAppBar extends StatelessWidget implements PreferredSizeWidget { .homePageFetchingTasks, false); } catch (e) { + debugPrint('Error refreshing tasks: $e'); ScaffoldMessenger.of(context) .hideCurrentSnackBar(); _showResultSnackBar( diff --git a/lib/app/modules/home/views/show_details.dart b/lib/app/modules/home/views/show_details.dart index 2a0dea77..c2f60f48 100644 --- a/lib/app/modules/home/views/show_details.dart +++ b/lib/app/modules/home/views/show_details.dart @@ -432,6 +432,7 @@ class _TaskDetailsState extends State { priority, status, widget.task.uuid!, + widget.task.id.toString(), ); } } diff --git a/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart b/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart index 7dd803b2..0bb18159 100644 --- a/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart +++ b/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart @@ -48,13 +48,13 @@ class TaskcDetailsController extends GetxController { status = task.status.obs; priority = (task.priority ?? '-').obs; due = formatDate(task.due).obs; - start = formatDate(task.start).obs; - wait = formatDate(task.wait).obs; - tags = (task.tags ?? []).obs; - depends = (task.depends ?? []).obs; - rtype = (task.rtype ?? '-').obs; - recur = (task.recur ?? '-').obs; - annotations = (task.annotations ?? []).obs; + start = "".obs; + wait = "".obs; + tags = "".split(",").obs; + depends = "".split(",").obs; + rtype = "".obs; + recur = "".obs; + annotations = [].obs; } String formatDate(String? dateString) { @@ -86,30 +86,30 @@ class TaskcDetailsController extends GetxController { } Future saveTask() async { - final updatedTask = TaskForC( - id: initialTask.id, - description: description.value, - project: project.value == '-' ? null : project.value, - status: status.value, - uuid: initialTask.uuid, - urgency: initialTask.urgency, // Urgency is typically calculated - priority: priority.value == '-' ? null : priority.value, - due: due.value == '-' ? null : due.value, - start: start.value == '-' ? null : start.value, - end: initialTask.end, // 'end' is usually set when completed - entry: initialTask.entry, // 'entry' is static - wait: wait.value == '-' ? null : wait.value, - modified: DateFormat('yyyy-MM-dd HH:mm:ss') - .format(DateTime.now()), // Update modified time - tags: tags.isEmpty ? null : tags.toList(), - depends: depends.isEmpty ? null : depends.toList(), - rtype: rtype.value == '-' ? null : rtype.value, - recur: recur.value == '-' ? null : recur.value, - annotations: annotations.isEmpty ? null : annotations.toList(), + if (tags.length == 1 && tags[0] == "") { + tags.clear(); + } + await taskDatabase.saveEditedTaskInDB( + initialTask.uuid!, + description.string, + project.string, + status.string, + priority.string, + due.string, + tags.toList(), ); - await TaskDatabase().updateTask(updatedTask); hasChanges.value = false; - await modifyTaskOnTaskwarrior(updatedTask); + debugPrint('Task saved in local DB ${description.string}'); + await modifyTaskOnTaskwarrior( + description.string, + project.string, + due.string, + priority.string, + status.string, + initialTask.uuid!, + initialTask.id.toString(), + tags.toList(), + ); } Future handleWillPop() async { diff --git a/lib/app/modules/taskc_details/views/taskc_details_view.dart b/lib/app/modules/taskc_details/views/taskc_details_view.dart index 7c65e854..b155c5ee 100644 --- a/lib/app/modules/taskc_details/views/taskc_details_view.dart +++ b/lib/app/modules/taskc_details/views/taskc_details_view.dart @@ -84,13 +84,6 @@ class TaskcDetailsView extends GetView { controller.tags.join(', '), (value) => controller.updateListField(controller.tags, value), ), - _buildEditableDetail( - context, - 'Depends:', - controller.depends.join(', '), - (value) => - controller.updateListField(controller.depends, value), - ), _buildEditableDetail( context, 'Rtype:', @@ -125,15 +118,6 @@ class TaskcDetailsView extends GetView { '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageModified}:', controller.formatDate(controller.initialTask.modified), ), - _buildDetail( - context, - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences}:', - controller.annotations.isNotEmpty - ? controller.annotations - .map((e) => e.description) - .join('\n') - : '-', - ), ], ), ), diff --git a/lib/app/v3/db/task_database.dart b/lib/app/v3/db/task_database.dart index 0216e810..136672d8 100644 --- a/lib/app/v3/db/task_database.dart +++ b/lib/app/v3/db/task_database.dart @@ -10,35 +10,49 @@ class TaskDatabase { var databasesPath = await getDatabasesPath(); String path = join(databasesPath, 'tasks.db'); - _database = await openDatabase(path, - version: 1, - onOpen: (db) async => await addTagsColumnIfNeeded(db), + _database = await openDatabase(path, version: 3, onCreate: (Database db, version) async { - await db.execute(''' - CREATE TABLE Tasks ( - uuid TEXT PRIMARY KEY, - id INTEGER, - description TEXT, - project TEXT, - status TEXT, - urgency REAL, - priority TEXT, - due TEXT, - end TEXT, - entry TEXT, - modified TEXT - ) - '''); - }); - } - - Future addTagsColumnIfNeeded(Database db) async { - try { - await db.rawQuery("SELECT tags FROM Tasks LIMIT 0"); - } catch (e) { - await db.execute("ALTER TABLE Tasks ADD COLUMN tags TEXT"); - debugPrint("Added Column tags"); - } + await db.execute(''' + CREATE TABLE Tasks ( + uuid TEXT PRIMARY KEY, + id INTEGER, + description TEXT, + project TEXT, + status TEXT, + urgency REAL, + priority TEXT, + due TEXT, + end TEXT, + entry TEXT, + modified TEXT + ) + '''); + await db.execute(''' + CREATE TABLE Tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + task_uuid TEXT NOT NULL, + task_id INTEGER NOT NULL, + FOREIGN KEY (task_uuid, task_id) + REFERENCES Tasks (uuid, id) + ON DELETE CASCADE + ON UPDATE CASCADE + ) + '''); + await db.execute(''' + CREATE TABLE Annotations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + entry TEXT NOT NULL, + description TEXT NOT NULL, + task_uuid TEXT NOT NULL, + task_id INTEGER NOT NULL, + FOREIGN KEY (task_uuid, task_id) + REFERENCES Tasks (uuid, id) + ON DELETE CASCADE + ON UPDATE CASCADE + ) + '''); + }); } Future ensureDatabaseIsOpen() async { @@ -51,34 +65,23 @@ class TaskDatabase { await ensureDatabaseIsOpen(); final List> maps = await _database!.query('Tasks'); + List a = await Future.wait( + maps.map((mapItem) => getObjectForTask(mapItem)).toList(), + ); debugPrint("Database fetch ${maps.last}"); - var a = List.generate(maps.length, (i) { - return TaskForC( - id: maps[i]['id'], - description: maps[i]['description'], - project: maps[i]['project'], - status: maps[i]['status'], - uuid: maps[i]['uuid'], - urgency: maps[i]['urgency'], - priority: maps[i]['priority'], - due: maps[i]['due'], - end: maps[i]['end'], - entry: maps[i]['entry'], - modified: maps[i]['modified'], - tags: maps[i]['tags'] != null ? maps[i]['tags'].split(' ') : []); - }); - // debugPrint('Tasks from db'); - // debugPrint(a.toString()); + for (int i = 0; i < maps.length; i++) { + debugPrint("Database fetch ${maps[i]}"); + } + debugPrint('Tasks from db'); + debugPrint(a.toString()); return a; } Future deleteAllTasksInDB() async { await ensureDatabaseIsOpen(); - - await _database!.delete('Tasks'); - debugPrint('Deleted all tasks'); - await open(); - debugPrint('Created new task table'); + await _database!.delete( + 'Tasks', + ); } Future printDatabaseContents() async { @@ -95,23 +98,34 @@ class TaskDatabase { Future insertTask(TaskForC task) async { await ensureDatabaseIsOpen(); debugPrint("Database Insert"); + List taskTags = task.tags?.map((e) => e.toString()).toList() ?? []; + debugPrint("Database Insert $taskTags"); + var map = task.toJson(); + map.remove("tags"); var dbi = await _database!.insert( 'Tasks', - task.toDbJson(), + map, conflictAlgorithm: ConflictAlgorithm.replace, ); - debugPrint("Database Insert ${task.toDbJson()} $dbi"); + if (taskTags.isNotEmpty) { + await setTagsForTask(task.uuid ?? '', dbi, taskTags.toList()); + } } Future updateTask(TaskForC task) async { await ensureDatabaseIsOpen(); - + List taskTags = task.tags?.map((e) => e.toString()).toList() ?? []; + var map = task.toJson(); + map.remove("tags"); await _database!.update( 'Tasks', - task.toDbJson(), + map, where: 'uuid = ?', whereArgs: [task.uuid], ); + if (taskTags.isNotEmpty) { + await setTagsForTask(task.uuid ?? "", task.id, taskTags.toList()); + } } Future getTaskByUuid(String uuid) async { @@ -124,7 +138,7 @@ class TaskDatabase { ); if (maps.isNotEmpty) { - return TaskForC.fromDbJson(maps.first); + return getObjectForTask(maps.first); } else { return null; } @@ -162,10 +176,11 @@ class TaskDatabase { String newStatus, String newPriority, String newDue, + List newTags, ) async { await ensureDatabaseIsOpen(); - debugPrint('task${uuid}deleted'); + debugPrint('task in saveEditedTaskInDB: $uuid'); await _database!.update( 'Tasks', { @@ -174,6 +189,7 @@ class TaskDatabase { 'status': newStatus, 'priority': newPriority, 'due': newDue, + 'modified': DateTime.now().toIso8601String(), }, where: 'uuid = ?', whereArgs: [uuid], @@ -190,9 +206,9 @@ class TaskDatabase { whereArgs: [''], ); - return List.generate(maps.length, (i) { - return TaskForC.fromDbJson(maps[i]); - }); + return await Future.wait( + maps.map((mapItem) => getObjectForTask(mapItem)).toList(), + ); } Future> getTasksByProject(String project) async { @@ -237,9 +253,9 @@ class TaskDatabase { where: 'description LIKE ? OR project LIKE ?', whereArgs: ['%$query%', '%$query%'], ); - return List.generate(maps.length, (i) { - return TaskForC.fromDbJson(maps[i]); - }); + return await Future.wait( + maps.map((mapItem) => getObjectForTask(mapItem)).toList(), + ); } Future close() async { @@ -253,4 +269,61 @@ class TaskDatabase { whereArgs: [description, due, project, priority], ); } + +// Get tags using a composite key + Future> getTagsForTask(String uuid, int id) async { + ensureDatabaseIsOpen(); + final db = _database; + if (db == null) { + return []; + } + final List> maps = await db.query( + 'Tags', + columns: ['name'], + where: 'task_uuid = ? AND task_id = ?', + whereArgs: [uuid, id], + ); + return List.generate(maps.length, (i) { + return maps[i]['name'] as String; + }); + } + + // Set tags using a composite key + Future setTagsForTask(String uuid, int id, List tags) async { + try { + ensureDatabaseIsOpen(); + final db = _database; + if (db == null) { + return; + } + await db.transaction((txn) async { + // Delete existing tags for the task + await txn.delete( + 'Tags', + where: 'task_uuid = ? AND task_id = ?', + whereArgs: [uuid, id], + ); + // Insert new tags + for (String tag in tags) { + if (tag.trim().isNotEmpty) { + await txn.insert( + 'Tags', + {'name': tag, 'task_uuid': uuid, 'task_id': id}, + ); + } + } + }); + } catch (e) { + debugPrint('Error setting tags for task $uuid: $e'); + } + } + + // Assemble TaskForC object + Future getObjectForTask(Map map) async { + final mutableMap = Map.from(map); + mutableMap['tags'] = + await getTagsForTask(mutableMap['uuid'], mutableMap['id']); + TaskForC task = TaskForC.fromJson(mutableMap); + return task; + } } diff --git a/lib/app/v3/db/update.dart b/lib/app/v3/db/update.dart index cc0f1008..38ad5861 100644 --- a/lib/app/v3/db/update.dart +++ b/lib/app/v3/db/update.dart @@ -5,6 +5,7 @@ import 'package:taskwarrior/app/v3/net/add_task.dart'; import 'package:taskwarrior/app/v3/net/complete.dart'; import 'package:taskwarrior/app/v3/net/delete.dart'; import 'package:taskwarrior/app/v3/net/modify.dart'; +import 'package:timezone/timezone.dart'; Future updateTasksInDatabase(List tasks) async { var taskDatabase = TaskDatabase(); @@ -15,10 +16,15 @@ Future updateTasksInDatabase(List tasks) async { //add tasks without UUID to the server and delete them from database for (var task in tasksWithoutUUID) { try { - await addTaskAndDeleteFromDatabase(task.description, task.project!, - task.due!, task.priority!, task.tags != null ? task.tags! : []); + await addTaskAndDeleteFromDatabase( + task.description, + task.project != null ? task.project! : '', + task.due!, + task.priority!, + task.tags != null ? task.tags! : []); } catch (e) { - debugPrint('Failed to add task without UUID to server: $e'); + debugPrint( + 'Failed to add task without UUID to server: $e ${task.tags} ${task.project}'); } } @@ -53,7 +59,19 @@ Future updateTasksInDatabase(List tasks) async { await taskDatabase.updateTask(serverTask); } else if (serverTaskModifiedDate.isBefore(localTaskModifiedDate)) { // local task is newer, update server - await modifyTaskOnTaskwarrior(localTask); + debugPrint( + 'Updating task on server: ${localTask.description}, modified: ${localTask.modified}'); + await modifyTaskOnTaskwarrior( + localTask.description, + localTask.project!, + localTask.due!, + localTask.priority!, + localTask.status, + localTask.uuid!, + localTask.id.toString(), + localTask.tags != null + ? localTask.tags!.map((e) => e.toString()).toList() + : []); if (localTask.status == 'completed') { completeTask('email', localTask.uuid!); } else if (localTask.status == 'deleted') { diff --git a/lib/app/v3/models/task.dart b/lib/app/v3/models/task.dart index c93884f0..aaf962d3 100644 --- a/lib/app/v3/models/task.dart +++ b/lib/app/v3/models/task.dart @@ -14,50 +14,36 @@ class TaskForC { final String? modified; final List? tags; - TaskForC( - {required this.id, - required this.description, - required this.project, - required this.status, - required this.uuid, - required this.urgency, - required this.priority, - required this.due, - required this.end, - required this.entry, - required this.modified, - required this.tags}); + TaskForC({ + required this.id, + required this.description, + required this.project, + required this.status, + required this.uuid, + required this.urgency, + required this.priority, + required this.due, + required this.end, + required this.entry, + required this.modified, + required this.tags, + }); factory TaskForC.fromJson(Map json) { return TaskForC( - id: json['id'], - description: json['description'], - project: json['project'], - status: json['status'], - uuid: json['uuid'], - urgency: json['urgency'].toDouble(), - priority: json['priority'], - due: json['due'], - end: json['end'], - entry: json['entry'], - modified: json['modified'], - tags: json['tags']); - } - factory TaskForC.fromDbJson(Map json) { - debugPrint("FROM: $json"); - return TaskForC( - id: json['id'], - description: json['description'], - project: json['project'], - status: json['status'], - uuid: json['uuid'], - urgency: json['urgency'].toDouble(), - priority: json['priority'], - due: json['due'], - end: json['end'], - entry: json['entry'], - modified: json['modified'], - tags: json['tags'].toString().split(' ')); + id: json['id'], + description: json['description'], + project: json['project'], + status: json['status'], + uuid: json['uuid'], + urgency: json['urgency'].toDouble(), + priority: json['priority'], + due: json['due'], + end: json['end'], + entry: json['entry'], + modified: json['modified'], + tags: json['tags'], + ); } Map toJson() { @@ -74,24 +60,7 @@ class TaskForC { 'end': end, 'entry': entry, 'modified': modified, - 'tags': tags - }; - } - - Map toDbJson() { - return { - 'id': id, - 'description': description, - 'project': project, - 'status': status, - 'uuid': uuid, - 'urgency': urgency, - 'priority': priority, - 'due': due, - 'end': end, - 'entry': entry, - 'modified': modified, - 'tags': tags != null ? tags?.join(" ") : "" + 'tags': tags, }; } } diff --git a/lib/app/v3/net/modify.dart b/lib/app/v3/net/modify.dart index 1f435963..eb33e7ae 100644 --- a/lib/app/v3/net/modify.dart +++ b/lib/app/v3/net/modify.dart @@ -5,15 +5,38 @@ import 'package:path/path.dart'; import 'package:flutter/material.dart'; import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; import 'package:taskwarrior/app/v3/db/task_database.dart'; -import 'package:taskwarrior/app/v3/models/task.dart'; -Future modifyTaskOnTaskwarrior(TaskForC tskc) async { +Future modifyTaskOnTaskwarrior( + String description, + String project, + String due, + String priority, + String status, + String taskuuid, + String id, + List newTags) async { var baseUrl = await CredentialsStorage.getApiUrl(); var c = await CredentialsStorage.getClientId(); var e = await CredentialsStorage.getEncryptionSecret(); - String apiUrl = '$baseUrl/modify-task'; + String apiUrl = '$baseUrl/edit-task'; debugPrint(c); debugPrint(e); + debugPrint("modifyTaskOnTaskwarrior called"); + debugPrint("description: $description project: $project due: $due " + "priority: $priority status: $status taskuuid: $taskuuid id: $id tags: $newTags" + "body: ${jsonEncode({ + "email": "e", + "encryptionSecret": e, + "UUID": c, + "description": description, + "priority": priority, + "project": project, + "due": due, + "status": status, + "taskuuid": taskuuid, + "taskId": id, + "tags": newTags.isNotEmpty ? newTags : null + })}"); final response = await http.post( Uri.parse(apiUrl), headers: { @@ -23,28 +46,28 @@ Future modifyTaskOnTaskwarrior(TaskForC tskc) async { "email": "e", "encryptionSecret": e, "UUID": c, - "description": tskc.description, - "priority": tskc.priority, - "project": tskc.project, - "due": tskc.due, - "status": tskc.status, - "taskuuid": tskc.uuid, + "description": description, + "priority": priority, + "project": project, + "due": due, + "status": status, + "taskuuid": taskuuid, + "taskId": id, + "tags": newTags.isNotEmpty ? newTags : null }), ); - - if (response.statusCode != 200) { - Get.snackbar( - 'Error', - 'Failed to modify task: ${response.reasonPhrase}', - snackPosition: SnackPosition.BOTTOM, - ); + debugPrint('Modify task response body: ${response.body}'); + if (response.statusCode < 200 || response.statusCode >= 300) { + Get.showSnackbar(GetSnackBar( + title: 'Error', + message: + 'Failed to modify task on Taskwarrior server. ${response.statusCode}', + duration: Duration(seconds: 3), + )); } var taskDatabase = TaskDatabase(); await taskDatabase.open(); await taskDatabase.deleteTask( - description: tskc.description, - due: tskc.due, - project: tskc.project, - priority: tskc.priority); + description: description, due: due, project: project, priority: priority); } From 4779a31910616f1acccb6613e1dc228015191d23 Mon Sep 17 00:00:00 2001 From: Shubham Ingale Date: Mon, 1 Sep 2025 11:15:14 +0530 Subject: [PATCH 3/7] added other attrs --- .../home/views/add_task_bottom_sheet_new.dart | 8 +- lib/app/modules/home/views/show_details.dart | 440 ------------------ .../controllers/taskc_details_controller.dart | 13 +- .../views/taskc_details_view.dart | 12 +- lib/app/v3/db/task_database.dart | 184 +++++++- lib/app/v3/models/annotation.dart | 12 + lib/app/v3/models/task.dart | 54 ++- 7 files changed, 242 insertions(+), 481 deletions(-) delete mode 100644 lib/app/modules/home/views/show_details.dart diff --git a/lib/app/modules/home/views/add_task_bottom_sheet_new.dart b/lib/app/modules/home/views/add_task_bottom_sheet_new.dart index c7e4495e..6449d389 100644 --- a/lib/app/modules/home/views/add_task_bottom_sheet_new.dart +++ b/lib/app/modules/home/views/add_task_bottom_sheet_new.dart @@ -321,7 +321,13 @@ class AddTaskBottomSheet extends StatelessWidget { due: getDueDate(homeController.selectedDates).toString(), end: '', modified: 'r', - tags: homeController.tags); + tags: homeController.tags, + start: '', + wait: '', + rtype: '', + recur: '', + depends: [], + annotations: []); await homeController.taskdb.insertTask(task); homeController.namecontroller.text = ''; homeController.due.value = null; diff --git a/lib/app/modules/home/views/show_details.dart b/lib/app/modules/home/views/show_details.dart deleted file mode 100644 index c2f60f48..00000000 --- a/lib/app/modules/home/views/show_details.dart +++ /dev/null @@ -1,440 +0,0 @@ -// ignore_for_file: deprecated_member_use, use_build_context_synchronously -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:intl/intl.dart'; -import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; -import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; -import 'package:taskwarrior/app/utils/constants/utilites.dart'; -import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; -import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; -import 'package:taskwarrior/app/v3/db/task_database.dart'; -import 'package:taskwarrior/app/v3/models/task.dart'; -import 'package:taskwarrior/app/v3/net/modify.dart'; - -class TaskDetails extends StatefulWidget { - final TaskForC task; - const TaskDetails({super.key, required this.task}); - - @override - State createState() => _TaskDetailsState(); -} - -class _TaskDetailsState extends State { - late String description; - late String project; - late String status; - late String priority; - late String due; - late TaskDatabase taskDatabase; - - bool hasChanges = false; - - @override - void initState() { - super.initState(); - description = widget.task.description; - project = widget.task.project!; - status = widget.task.status; - priority = widget.task.priority!; - due = widget.task.due ?? '-'; - due = _buildDate(due); - setState(() { - taskDatabase = TaskDatabase(); - taskDatabase.open(); - }); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - taskDatabase = TaskDatabase(); - taskDatabase.open(); - } - - @override - Widget build(BuildContext context) { - TaskwarriorColorTheme tColors = - Theme.of(context).extension()!; - return WillPopScope( - onWillPop: () async { - if (hasChanges) { - final action = await _showUnsavedChangesDialog(context); - if (action == UnsavedChangesAction.cancel) { - return Future.value(false); - } else if (action == UnsavedChangesAction.save) { - await _saveTask(); - } - } - return Future.value(true); - }, - child: Scaffold( - backgroundColor: tColors.primaryBackgroundColor, - appBar: AppBar( - foregroundColor: TaskWarriorColors.lightGrey, - backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, - title: Text( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.task}: ${widget.task.description}', - style: GoogleFonts.poppins(color: TaskWarriorColors.white), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: ListView( - children: [ - _buildEditableDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageDescription}:', - description, (value) { - setState(() { - description = value; - hasChanges = true; - }); - }), - _buildEditableDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.project}:', - project, (value) { - setState(() { - project = value; - hasChanges = true; - }); - }), - _buildSelectableDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageStatus}:', - status, - ['pending', 'completed'], (value) { - setState(() { - status = value; - hasChanges = true; - }); - }), - _buildSelectableDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPagePriority}:', - priority, - ['H', 'M', 'L'], (value) { - setState(() { - priority = value; - hasChanges = true; - }); - }), - _buildDatePickerDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.homePageDue}:', - due, (value) { - setState(() { - due = value; - hasChanges = true; - }); - }), - _buildDetail('UUID:', widget.task.uuid!), - _buildDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageUrgency}:', - widget.task.urgency.toString()), - _buildDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageEnd}:', - _buildDate(widget.task.end)), - _buildDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageEntry}:', - _buildDate(widget.task.entry)), - _buildDetail( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageModified}:', - _buildDate(widget.task.modified)), - ], - ), - ), - floatingActionButton: hasChanges - ? FloatingActionButton( - onPressed: () async { - await _saveTask(); - }, - child: const Icon(Icons.save), - ) - : null, - ), - ); - } - - Widget _buildEditableDetail( - String label, String value, Function(String) onChanged) { - return InkWell( - onTap: () async { - final result = await _showEditDialog(context, label, value); - if (result != null) { - onChanged(result); - } - }, - child: _buildDetail(label, value), - ); - } - - Widget _buildSelectableDetail(String label, String value, - List options, Function(String) onChanged) { - return InkWell( - onTap: () async { - final result = await _showSelectDialog(context, label, value, options); - if (result != null) { - onChanged(result); - } - }, - child: _buildDetail(label, value), - ); - } - - Widget _buildDatePickerDetail( - String label, String value, Function(String) onChanged) { - return InkWell( - onTap: () async { - final DateTime? pickedDate = await showDatePicker( - context: context, - initialDate: value != '-' ? DateTime.parse(value) : DateTime.now(), - firstDate: DateTime(2000), - lastDate: DateTime(2101), - builder: (BuildContext context, Widget? child) { - return Theme( - data: Theme.of(context), - child: child!, - ); - }, - ); - if (pickedDate != null) { - final TimeOfDay? pickedTime = await showTimePicker( - context: context, - initialTime: TimeOfDay.fromDateTime( - value != '-' ? DateTime.parse(value) : DateTime.now()), - ); - if (pickedTime != null) { - final DateTime fullDateTime = DateTime( - pickedDate.year, - pickedDate.month, - pickedDate.day, - pickedTime.hour, - pickedTime.minute); - onChanged(DateFormat('yyyy-MM-dd HH:mm:ss').format(fullDateTime)); - } - } - }, - child: _buildDetail(label, value), - ); - } - - Widget _buildDetail(String label, String value) { - TaskwarriorColorTheme tColors = - Theme.of(context).extension()!; - return Container( - width: double.infinity, - decoration: BoxDecoration( - color: tColors.secondaryBackgroundColor, - borderRadius: BorderRadius.circular(8.0), - boxShadow: const [ - BoxShadow( - color: Colors.black12, - blurRadius: 4.0, - offset: Offset(0, 2), - ), - ], - ), - padding: const EdgeInsets.all(16.0), - margin: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - color: tColors.primaryTextColor, - ), - ), - const SizedBox(width: 8), - Expanded( - child: Text( - value, - style: TextStyle( - fontSize: 18, - color: tColors.primaryTextColor, - ), - ), - ), - ], - ), - ); - } - - Future _showEditDialog( - BuildContext context, String label, String initialValue) async { - TaskwarriorColorTheme tColors = - Theme.of(context).extension()!; - final TextEditingController controller = - TextEditingController(text: initialValue); - return await showDialog( - context: context, - builder: (context) { - return Utils.showAlertDialog( - title: Text( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.edit} $label', - style: TextStyle(color: tColors.primaryTextColor), - ), - content: TextField( - style: TextStyle(color: tColors.primaryTextColor), - controller: controller, - decoration: InputDecoration( - hintText: - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.enterNew} $label', - hintStyle: TextStyle(color: tColors.primaryTextColor), - ), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .cancel, - style: TextStyle(color: tColors.primaryTextColor), - ), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(controller.text); - }, - child: Text( - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .save, - style: TextStyle(color: tColors.primaryTextColor), - ), - ), - ], - ); - }, - ); - } - - Future _showSelectDialog(BuildContext context, String label, - String initialValue, List options) async { - TaskwarriorColorTheme tColors = - Theme.of(context).extension()!; - return await showDialog( - context: context, - builder: (context) { - return Utils.showAlertDialog( - title: Text( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.select} $label', - style: TextStyle(color: tColors.primaryTextColor), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: options.map((option) { - return RadioListTile( - title: Text( - option, - style: TextStyle(color: tColors.primaryTextColor), - ), - value: option, - groupValue: initialValue, - onChanged: (value) { - Navigator.of(context).pop(value); - }, - ); - }).toList(), - ), - ); - }, - ); - } - - String _buildDate(String? dateString) { - if (dateString == null || dateString.isEmpty || dateString == '-') { - return '-'; - } - - try { - DateTime parsedDate = DateTime.parse(dateString); - return DateFormat('yyyy-MM-dd HH:mm:ss').format(parsedDate); - } catch (e) { - debugPrint('Error parsing date: $dateString'); - return '-'; - } - } - - Future _showUnsavedChangesDialog( - BuildContext context) async { - TaskwarriorColorTheme tColors = - Theme.of(context).extension()!; - return showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return Utils.showAlertDialog( - title: Text( - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .unsavedChanges, - style: TextStyle(color: tColors.primaryTextColor), - ), - content: Text( - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .unsavedChangesWarning, - style: TextStyle(color: tColors.primaryTextColor), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(UnsavedChangesAction.cancel); - }, - child: Text( - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .cancel), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(UnsavedChangesAction.discard); - }, - child: Text( - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .dontSave), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(UnsavedChangesAction.save); - }, - child: Text( - SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .save), - ), - ], - ); - }, - ); - } - - Future _saveTask() async { - await taskDatabase.saveEditedTaskInDB( - widget.task.uuid!, - description, - project, - status, - priority, - due, - ); - setState(() { - hasChanges = false; - }); - modifyTaskOnTaskwarrior( - description, - project, - due, - priority, - status, - widget.task.uuid!, - widget.task.id.toString(), - ); - } -} - -enum UnsavedChangesAction { save, discard, cancel } diff --git a/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart b/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart index 0bb18159..a2d5a579 100644 --- a/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart +++ b/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart @@ -32,6 +32,7 @@ class TaskcDetailsController extends GetxController { late RxString rtype; late RxString recur; late RxList annotations; + late RxList previousTags = [].obs; @override void onInit() { @@ -50,7 +51,10 @@ class TaskcDetailsController extends GetxController { due = formatDate(task.due).obs; start = "".obs; wait = "".obs; - tags = "".split(",").obs; + tags = initialTask.tags != null + ? initialTask.tags!.map((e) => e.toString()).toList().obs + : [].obs; + previousTags = tags.toList().obs; depends = "".split(",").obs; rtype = "".obs; recur = "".obs; @@ -85,6 +89,12 @@ class TaskcDetailsController extends GetxController { } } + void processTagsLists() { + final itemsToMove = previousTags.toSet().difference(tags.toSet()); + tags.addAll(itemsToMove.map((item) => '-$item')); + previousTags.removeWhere((item) => itemsToMove.contains(item)); + } + Future saveTask() async { if (tags.length == 1 && tags[0] == "") { tags.clear(); @@ -100,6 +110,7 @@ class TaskcDetailsController extends GetxController { ); hasChanges.value = false; debugPrint('Task saved in local DB ${description.string}'); + processTagsLists(); await modifyTaskOnTaskwarrior( description.string, project.string, diff --git a/lib/app/modules/taskc_details/views/taskc_details_view.dart b/lib/app/modules/taskc_details/views/taskc_details_view.dart index b155c5ee..75e0dad5 100644 --- a/lib/app/modules/taskc_details/views/taskc_details_view.dart +++ b/lib/app/modules/taskc_details/views/taskc_details_view.dart @@ -66,17 +66,15 @@ class TaskcDetailsView extends GetView { controller.due.value, () => controller.pickDateTime(controller.due), ), - _buildDatePickerDetail( + _buildDetail( context, '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageStart}:', controller.start.value, - () => controller.pickDateTime(controller.start), ), - _buildDatePickerDetail( + _buildDetail( context, '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageWait}:', controller.wait.value, - () => controller.pickDateTime(controller.wait), ), _buildEditableDetail( context, @@ -84,17 +82,15 @@ class TaskcDetailsView extends GetView { controller.tags.join(', '), (value) => controller.updateListField(controller.tags, value), ), - _buildEditableDetail( + _buildDetail( context, 'Rtype:', controller.rtype.value, - (value) => controller.updateField(controller.rtype, value), ), - _buildEditableDetail( + _buildDetail( context, 'Recur:', controller.recur.value, - (value) => controller.updateField(controller.recur, value), ), _buildDetail( context, 'UUID:', controller.initialTask.uuid ?? '-'), diff --git a/lib/app/v3/db/task_database.dart b/lib/app/v3/db/task_database.dart index 136672d8..2e2e65d0 100644 --- a/lib/app/v3/db/task_database.dart +++ b/lib/app/v3/db/task_database.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; +import 'package:taskwarrior/app/v3/models/annotation.dart'; import 'package:taskwarrior/app/v3/models/task.dart'; class TaskDatabase { @@ -10,7 +11,7 @@ class TaskDatabase { var databasesPath = await getDatabasesPath(); String path = join(databasesPath, 'tasks.db'); - _database = await openDatabase(path, version: 3, + _database = await openDatabase(path, version: 2, onCreate: (Database db, version) async { await db.execute(''' CREATE TABLE Tasks ( @@ -24,7 +25,11 @@ class TaskDatabase { due TEXT, end TEXT, entry TEXT, - modified TEXT + modified TEXT, + start TEXT, + wait TEXT, + rtype TEXT, + recur TEXT ) '''); await db.execute(''' @@ -52,7 +57,20 @@ class TaskDatabase { ON UPDATE CASCADE ) '''); + await db.execute(''' + CREATE TABLE Depends ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + d_uuid TEXT NOT NULL, + task_uuid TEXT NOT NULL, + task_id INTEGER NOT NULL, + FOREIGN KEY (task_uuid, task_id) + REFERENCES Tasks (uuid, id) + ON DELETE CASCADE + ON UPDATE CASCADE + ) + '''); }); + debugPrint("Database opened at $path"); } Future ensureDatabaseIsOpen() async { @@ -100,8 +118,18 @@ class TaskDatabase { debugPrint("Database Insert"); List taskTags = task.tags?.map((e) => e.toString()).toList() ?? []; debugPrint("Database Insert $taskTags"); + List taskDepends = + task.tags?.map((e) => e.toString()).toList() ?? []; + debugPrint("Database Insert $taskDepends"); + List> taskAnnotations = task.annotations != null + ? task.annotations! + .map((a) => {"entry": a.entry, "description": a.description}) + .toList() + : []; var map = task.toJson(); map.remove("tags"); + map.remove("depends"); + map.remove("annotations"); var dbi = await _database!.insert( 'Tasks', map, @@ -110,13 +138,32 @@ class TaskDatabase { if (taskTags.isNotEmpty) { await setTagsForTask(task.uuid ?? '', dbi, taskTags.toList()); } + if (taskDepends.isNotEmpty) { + await setDependsForTask(task.uuid ?? '', dbi, taskDepends.toList()); + } + if (taskAnnotations.isNotEmpty) { + await setAnnotationsForTask( + task.uuid ?? '', dbi, taskAnnotations.toList()); + } } Future updateTask(TaskForC task) async { await ensureDatabaseIsOpen(); + debugPrint("Database Insert"); List taskTags = task.tags?.map((e) => e.toString()).toList() ?? []; + debugPrint("Database Insert $taskTags"); + List taskDepends = + task.tags?.map((e) => e.toString()).toList() ?? []; + debugPrint("Database Insert $taskDepends"); + List> taskAnnotations = task.annotations != null + ? task.annotations! + .map((a) => {"entry": a.entry, "description": a.description}) + .toList() + : []; var map = task.toJson(); map.remove("tags"); + map.remove("depends"); + map.remove("annotations"); await _database!.update( 'Tasks', map, @@ -124,7 +171,14 @@ class TaskDatabase { whereArgs: [task.uuid], ); if (taskTags.isNotEmpty) { - await setTagsForTask(task.uuid ?? "", task.id, taskTags.toList()); + await setTagsForTask(task.uuid ?? '', task.id, taskTags.toList()); + } + if (taskDepends.isNotEmpty) { + await setDependsForTask(task.uuid ?? '', task.id, taskDepends.toList()); + } + if (taskAnnotations.isNotEmpty) { + await setAnnotationsForTask( + task.uuid ?? '', task.id, taskAnnotations.toList()); } } @@ -218,21 +272,9 @@ class TaskDatabase { whereArgs: [project], ); debugPrint("DB Stored for $maps"); - return List.generate(maps.length, (i) { - return TaskForC( - uuid: maps[i]['uuid'], - id: maps[i]['id'], - description: maps[i]['description'], - project: maps[i]['project'], - status: maps[i]['status'], - urgency: maps[i]['urgency'], - priority: maps[i]['priority'], - due: maps[i]['due'], - end: maps[i]['end'], - entry: maps[i]['entry'], - modified: maps[i]['modified'], - tags: maps[i]['tags'].toString().split(' ')); - }); + return await Future.wait( + maps.map((mapItem) => getObjectForTask(mapItem)).toList(), + ); } Future> fetchUniqueProjects() async { @@ -318,11 +360,117 @@ class TaskDatabase { } } + // depends methods + Future> getDependsForTask(String uuid, int id) async { + ensureDatabaseIsOpen(); + final db = _database; + if (db == null) { + return []; + } + final List> maps = await db.query( + 'Depends', + columns: ['d_uuid'], + where: 'task_uuid = ? AND task_id = ?', + whereArgs: [uuid, id], + ); + return List.generate(maps.length, (i) { + return maps[i]['d_uuid'] as String; + }); + } + + Future setDependsForTask( + String uuid, int id, List depends) async { + try { + ensureDatabaseIsOpen(); + final db = _database; + if (db == null) { + return; + } + await db.transaction((txn) async { + await txn.delete( + 'Depends', + where: 'task_uuid = ? AND task_id = ?', + whereArgs: [uuid, id], + ); + for (String dUuid in depends) { + if (dUuid.trim().isNotEmpty) { + await txn.insert( + 'Depends', + {'d_uuid': dUuid, 'task_uuid': uuid, 'task_id': id}, + ); + } + } + }); + } catch (e) { + debugPrint('Error setting depends for task $uuid: $e'); + } + } + + // annotations methods + Future>> getAnnotationsForTask( + String uuid, int id) async { + ensureDatabaseIsOpen(); + final db = _database; + if (db == null) { + return >[]; + } + final List> maps = await db.query( + 'Annotations', + columns: ['entry', 'description'], + where: 'task_uuid = ? AND task_id = ?', + whereArgs: [uuid, id], + ); + return List.generate(maps.length, (i) { + return { + 'entry': maps[i]['entry'] as String, + 'description': maps[i]['description'] as String, + }; + }); + } + + Future setAnnotationsForTask( + String uuid, int id, List> annotations) async { + try { + ensureDatabaseIsOpen(); + final db = _database; + if (db == null) { + return; + } + await db.transaction((txn) async { + await txn.delete( + 'Annotations', + where: 'task_uuid = ? AND task_id = ?', + whereArgs: [uuid, id], + ); + for (Map annotation in annotations) { + if (annotation['entry'] != null && + annotation['description'] != null) { + await txn.insert( + 'Annotations', + { + 'entry': annotation['entry'], + 'description': annotation['description'], + 'task_uuid': uuid, + 'task_id': id + }, + ); + } + } + }); + } catch (e) { + debugPrint('Error setting annotations for task $uuid: $e'); + } + } + // Assemble TaskForC object Future getObjectForTask(Map map) async { final mutableMap = Map.from(map); mutableMap['tags'] = await getTagsForTask(mutableMap['uuid'], mutableMap['id']); + mutableMap['depends'] = + await getDependsForTask(mutableMap['uuid'], mutableMap['id']); + mutableMap['annotations'] = + await getAnnotationsForTask(mutableMap['uuid'], mutableMap['id']); TaskForC task = TaskForC.fromJson(mutableMap); return task; } diff --git a/lib/app/v3/models/annotation.dart b/lib/app/v3/models/annotation.dart index 9f8036e7..e312b393 100644 --- a/lib/app/v3/models/annotation.dart +++ b/lib/app/v3/models/annotation.dart @@ -2,4 +2,16 @@ class Annotation { final String? entry; final String? description; Annotation({this.entry, this.description}); + factory Annotation.fromJson(Map json) { + return Annotation( + entry: json['entry'], + description: json['description'], + ); + } + Map toJson() { + return { + 'entry': entry, + 'description': description, + }; + } } diff --git a/lib/app/v3/models/task.dart b/lib/app/v3/models/task.dart index aaf962d3..922ae69e 100644 --- a/lib/app/v3/models/task.dart +++ b/lib/app/v3/models/task.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import "./annotation.dart"; class TaskForC { final int id; @@ -13,6 +14,13 @@ class TaskForC { final String entry; final String? modified; final List? tags; + // newer feilds in CCSync Model + final String? start; + final String? wait; + final String? rtype; + final String? recur; + final List? depends; + final List? annotations; TaskForC({ required this.id, @@ -27,23 +35,35 @@ class TaskForC { required this.entry, required this.modified, required this.tags, + required this.start, + required this.wait, + required this.rtype, + required this.recur, + required this.depends, + required this.annotations, }); factory TaskForC.fromJson(Map json) { + debugPrint("Annotation fromJson: ${json['annotations'] == null}"); return TaskForC( - id: json['id'], - description: json['description'], - project: json['project'], - status: json['status'], - uuid: json['uuid'], - urgency: json['urgency'].toDouble(), - priority: json['priority'], - due: json['due'], - end: json['end'], - entry: json['entry'], - modified: json['modified'], - tags: json['tags'], - ); + id: json['id'], + description: json['description'], + project: json['project'], + status: json['status'], + uuid: json['uuid'], + urgency: json['urgency'].toDouble(), + priority: json['priority'], + due: json['due'], + end: json['end'], + entry: json['entry'], + modified: json['modified'], + tags: json['tags'], + start: json['start'], + wait: json['wait'], + rtype: json['rtype'], + recur: json['recur'], + depends: json['depends'], + annotations: []); } Map toJson() { @@ -61,6 +81,14 @@ class TaskForC { 'entry': entry, 'modified': modified, 'tags': tags, + 'start': start, + 'wait': wait, + 'rtype': rtype, + 'recur': recur, + 'depends': depends, + 'annotations': annotations != null + ? annotations?.map((a) => a.toJson()).toList() + : >[], }; } } From c47d02e5bc4109d33cf5e525108de27361eda6a9 Mon Sep 17 00:00:00 2001 From: Shubham Ingale Date: Mon, 1 Sep 2025 11:50:19 +0530 Subject: [PATCH 4/7] fixed tags deletion ccsync problem --- lib/app/v3/db/task_database.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/app/v3/db/task_database.dart b/lib/app/v3/db/task_database.dart index 2e2e65d0..ad613909 100644 --- a/lib/app/v3/db/task_database.dart +++ b/lib/app/v3/db/task_database.dart @@ -149,12 +149,12 @@ class TaskDatabase { Future updateTask(TaskForC task) async { await ensureDatabaseIsOpen(); - debugPrint("Database Insert"); + debugPrint("Database update"); List taskTags = task.tags?.map((e) => e.toString()).toList() ?? []; - debugPrint("Database Insert $taskTags"); + debugPrint("Database update $taskTags"); List taskDepends = task.tags?.map((e) => e.toString()).toList() ?? []; - debugPrint("Database Insert $taskDepends"); + debugPrint("Database update $taskDepends"); List> taskAnnotations = task.annotations != null ? task.annotations! .map((a) => {"entry": a.entry, "description": a.description}) @@ -192,7 +192,7 @@ class TaskDatabase { ); if (maps.isNotEmpty) { - return getObjectForTask(maps.first); + return await getObjectForTask(maps.first); } else { return null; } @@ -249,6 +249,10 @@ class TaskDatabase { whereArgs: [uuid], ); debugPrint('task${uuid}edited'); + if (newTags.isNotEmpty) { + TaskForC? task = await getTaskByUuid(uuid); + await setTagsForTask(uuid, task?.id ?? 0, newTags.toList()); + } } Future> findTasksWithoutUUIDs() async { @@ -332,6 +336,7 @@ class TaskDatabase { // Set tags using a composite key Future setTagsForTask(String uuid, int id, List tags) async { + debugPrint('Setting tags for task $uuid: $tags'); try { ensureDatabaseIsOpen(); final db = _database; From 4b1eb09d659eeffb38cf7e5cb2ee6aa7d68c795f Mon Sep 17 00:00:00 2001 From: Shubham Ingale Date: Mon, 1 Sep 2025 11:55:28 +0530 Subject: [PATCH 5/7] api_service_test update --- test/api_service_test.dart | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/test/api_service_test.dart b/test/api_service_test.dart index 1b14d90b..cfa115f8 100644 --- a/test/api_service_test.dart +++ b/test/api_service_test.dart @@ -72,7 +72,13 @@ void main() { end: null, entry: '2024-01-01', modified: '2024-11-01', - tags: ['t1']); + tags: ['t1'], + start: '', + wait: '', + rtype: '', + recur: '', + depends: [], + annotations: []); final json = task.toJson(); @@ -129,10 +135,16 @@ void main() { urgency: 5.0, priority: 'H', due: '2024-12-31', - end: null, + end: '', entry: '2024-01-01', modified: '2024-11-01', - tags: ['t1']); + tags: ['t1'], + start: '', + wait: '', + rtype: '', + recur: '', + depends: [], + annotations: []); await taskDatabase.insertTask(task); @@ -155,7 +167,13 @@ void main() { end: null, entry: '2024-01-01', modified: '2024-11-01', - tags: ['t1']); + tags: ['t1'], + start: '', + wait: '', + rtype: '', + recur: '', + depends: [], + annotations: []); await taskDatabase.insertTask(task); await taskDatabase.deleteAllTasksInDB(); From 29c50fbc93b1c467fc0d0ea6c31171a78ec370be Mon Sep 17 00:00:00 2001 From: Shubham Ingale Date: Mon, 1 Sep 2025 18:06:55 +0530 Subject: [PATCH 6/7] fixed insert tags hidden error --- lib/app/v3/db/task_database.dart | 19 +++++++++---------- lib/app/v3/db/update.dart | 4 ++++ lib/app/v3/models/task.dart | 7 ++++--- lib/app/v3/net/fetch.dart | 7 +++++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/app/v3/db/task_database.dart b/lib/app/v3/db/task_database.dart index ad613909..aab48885 100644 --- a/lib/app/v3/db/task_database.dart +++ b/lib/app/v3/db/task_database.dart @@ -115,35 +115,34 @@ class TaskDatabase { Future insertTask(TaskForC task) async { await ensureDatabaseIsOpen(); - debugPrint("Database Insert"); - List taskTags = task.tags?.map((e) => e.toString()).toList() ?? []; - debugPrint("Database Insert $taskTags"); - List taskDepends = - task.tags?.map((e) => e.toString()).toList() ?? []; - debugPrint("Database Insert $taskDepends"); + List taskTags = task.tags ?? []; + List taskDepends = task.depends ?? []; List> taskAnnotations = task.annotations != null ? task.annotations! .map((a) => {"entry": a.entry, "description": a.description}) .toList() : []; + debugPrint( + "Database insert ${task.description} for task tags are $taskTags"); var map = task.toJson(); map.remove("tags"); map.remove("depends"); map.remove("annotations"); - var dbi = await _database!.insert( + await _database!.insert( 'Tasks', map, conflictAlgorithm: ConflictAlgorithm.replace, ); if (taskTags.isNotEmpty) { - await setTagsForTask(task.uuid ?? '', dbi, taskTags.toList()); + // Use the ID from the task object itself for consistency + await setTagsForTask(task.uuid ?? '', task.id, taskTags.toList()); } if (taskDepends.isNotEmpty) { - await setDependsForTask(task.uuid ?? '', dbi, taskDepends.toList()); + await setDependsForTask(task.uuid ?? '', task.id, taskDepends.toList()); } if (taskAnnotations.isNotEmpty) { await setAnnotationsForTask( - task.uuid ?? '', dbi, taskAnnotations.toList()); + task.uuid ?? '', task.id, taskAnnotations.toList()); } } diff --git a/lib/app/v3/db/update.dart b/lib/app/v3/db/update.dart index 38ad5861..7d0f549f 100644 --- a/lib/app/v3/db/update.dart +++ b/lib/app/v3/db/update.dart @@ -8,6 +8,8 @@ import 'package:taskwarrior/app/v3/net/modify.dart'; import 'package:timezone/timezone.dart'; Future updateTasksInDatabase(List tasks) async { + debugPrint( + "Updating tasks in database... Total tasks from server: ${tasks.length}"); var taskDatabase = TaskDatabase(); await taskDatabase.open(); // find tasks without UUID @@ -49,6 +51,8 @@ Future updateTasksInDatabase(List tasks) async { if (localTask == null) { // Task doesn't exist in the local database, insert it + debugPrint( + 'Inserting new task from server: ${serverTask.description}, modified: ${serverTask.modified}'); await taskDatabase.insertTask(serverTask); } else { var serverTaskModifiedDate = DateTime.parse(serverTask.modified!); diff --git a/lib/app/v3/models/task.dart b/lib/app/v3/models/task.dart index 922ae69e..19503c58 100644 --- a/lib/app/v3/models/task.dart +++ b/lib/app/v3/models/task.dart @@ -13,7 +13,7 @@ class TaskForC { final String? end; final String entry; final String? modified; - final List? tags; + final List? tags; // newer feilds in CCSync Model final String? start; final String? wait; @@ -57,12 +57,13 @@ class TaskForC { end: json['end'], entry: json['entry'], modified: json['modified'], - tags: json['tags'], + tags: json['tags']?.map((tag) => tag.toString()).toList() ?? [], start: json['start'], wait: json['wait'], rtype: json['rtype'], recur: json['recur'], - depends: json['depends'], + depends: + json['depends']?.map((d) => d.toString()).toList() ?? [], annotations: []); } diff --git a/lib/app/v3/net/fetch.dart b/lib/app/v3/net/fetch.dart index e1cff2c6..c38ef639 100644 --- a/lib/app/v3/net/fetch.dart +++ b/lib/app/v3/net/fetch.dart @@ -15,6 +15,8 @@ Future> fetchTasks(String uuid, String encryptionSecret) async { var response = await http.get(Uri.parse(url), headers: { "Content-Type": "application/json", }).timeout(const Duration(seconds: 10000)); + debugPrint("Fetch tasks response: ${response.statusCode}"); + debugPrint("Fetch tasks body: ${response.body}"); if (response.statusCode == 200) { List allTasks = jsonDecode(response.body); debugPrint(allTasks.toString()); @@ -22,8 +24,9 @@ Future> fetchTasks(String uuid, String encryptionSecret) async { } else { throw Exception('Failed to load tasks'); } - } catch (e) { - debugPrint('Error fetching tasks: $e'); + } catch (e, s) { + debugPrint('Error fetching tasks: $e\n $s'); + return []; } } From 51ad950fcd0daea1953669dd78c2491687585579 Mon Sep 17 00:00:00 2001 From: Shubham Ingale Date: Mon, 1 Sep 2025 19:40:41 +0530 Subject: [PATCH 7/7] changed from edit-task to modify-task to make sure all changes are reflected --- .../taskc_details/controllers/taskc_details_controller.dart | 4 ++-- lib/app/v3/db/task_database.dart | 2 +- lib/app/v3/net/modify.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart b/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart index a2d5a579..dfec84da 100644 --- a/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart +++ b/lib/app/modules/taskc_details/controllers/taskc_details_controller.dart @@ -105,7 +105,7 @@ class TaskcDetailsController extends GetxController { project.string, status.string, priority.string, - due.string, + DateTime.parse(due.string).toIso8601String(), tags.toList(), ); hasChanges.value = false; @@ -114,7 +114,7 @@ class TaskcDetailsController extends GetxController { await modifyTaskOnTaskwarrior( description.string, project.string, - due.string, + DateTime.parse(due.string).toIso8601String(), priority.string, status.string, initialTask.uuid!, diff --git a/lib/app/v3/db/task_database.dart b/lib/app/v3/db/task_database.dart index aab48885..6928da9a 100644 --- a/lib/app/v3/db/task_database.dart +++ b/lib/app/v3/db/task_database.dart @@ -233,7 +233,7 @@ class TaskDatabase { ) async { await ensureDatabaseIsOpen(); - debugPrint('task in saveEditedTaskInDB: $uuid'); + debugPrint('task in saveEditedTaskInDB: $uuid with due $newDue'); await _database!.update( 'Tasks', { diff --git a/lib/app/v3/net/modify.dart b/lib/app/v3/net/modify.dart index eb33e7ae..1d32976a 100644 --- a/lib/app/v3/net/modify.dart +++ b/lib/app/v3/net/modify.dart @@ -18,7 +18,7 @@ Future modifyTaskOnTaskwarrior( var baseUrl = await CredentialsStorage.getApiUrl(); var c = await CredentialsStorage.getClientId(); var e = await CredentialsStorage.getEncryptionSecret(); - String apiUrl = '$baseUrl/edit-task'; + String apiUrl = '$baseUrl/modify-task'; debugPrint(c); debugPrint(e); debugPrint("modifyTaskOnTaskwarrior called");