diff --git a/lib/api_service.dart b/lib/api_service.dart deleted file mode 100644 index 62f5ba94..00000000 --- a/lib/api_service.dart +++ /dev/null @@ -1,583 +0,0 @@ -// ignore_for_file: depend_on_referenced_packages, unnecessary_null_in_if_null_operators - -import 'dart:convert'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:http/http.dart' as http; -import 'package:sqflite/sqflite.dart'; -import 'package:path/path.dart'; -import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; - -class Tasks { - final int id; - final String description; - final String? project; - final String status; - final String? uuid; - final double? urgency; - final String? priority; - final String? due; - final String? end; - final String entry; - final String? modified; - final List? tags; - - Tasks( - {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 Tasks.fromJson(Map json) { - return Tasks( - 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 Tasks.fromDbJson(Map json) { - debugPrint("FROM: $json"); - return Tasks( - 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(' ')); - } - - Map toJson() { - 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, - 'project': project, - 'status': status, - 'uuid': uuid, - 'urgency': urgency, - 'priority': priority, - 'due': due, - 'end': end, - 'entry': entry, - 'modified': modified, - 'tags': tags != null ? tags?.join(" ") : "" - }; - } -} - -String origin = 'http://localhost:8080'; - -Future> fetchTasks(String uuid, String encryptionSecret) async { - var baseUrl = await CredentialsStorage.getApiUrl(); - try { - String url = - '$baseUrl/tasks?email=email&origin=$origin&UUID=$uuid&encryptionSecret=$encryptionSecret'; - - var response = await http.get(Uri.parse(url), headers: { - "Content-Type": "application/json", - }).timeout(const Duration(seconds: 10000)); - if (response.statusCode == 200) { - List allTasks = jsonDecode(response.body); - debugPrint(allTasks.toString()); - return allTasks.map((task) => Tasks.fromJson(task)).toList(); - } else { - throw Exception('Failed to load tasks'); - } - } catch (e) { - debugPrint('Error fetching tasks: $e'); - return []; - } -} - -Future updateTasksInDatabase(List tasks) async { - var taskDatabase = TaskDatabase(); - await taskDatabase.open(); - // find tasks without UUID - List tasksWithoutUUID = await taskDatabase.findTasksWithoutUUIDs(); - - //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! : []); - } catch (e) { - debugPrint('Failed to add task without UUID to server: $e'); - } - } - - // update existing tasks in db - for (var task in tasks) { - var existingTask = await taskDatabase.getTaskByUuid(task.uuid!); - if (existingTask != null) { - if (task.modified!.compareTo(existingTask.modified!) > 0) { - await taskDatabase.updateTask(task); - } - } else { - // add new tasks to db - await taskDatabase.insertTask(task); - } - } - - var localTasks = await taskDatabase.fetchTasksFromDatabase(); - var localTasksMap = {for (var task in localTasks) task.uuid: task}; - - for (var serverTask in tasks) { - var localTask = localTasksMap[serverTask.uuid]; - - if (localTask == null) { - // Task doesn't exist in the local database, insert it - await taskDatabase.insertTask(serverTask); - } else { - var serverTaskModifiedDate = DateTime.parse(serverTask.modified!); - var localTaskModifiedDate = DateTime.parse(localTask.modified!); - - if (serverTaskModifiedDate.isAfter(localTaskModifiedDate)) { - // Server task is newer, update local database - await taskDatabase.updateTask(serverTask); - } else if (serverTaskModifiedDate.isBefore(localTaskModifiedDate)) { - // local task is newer, update server - await modifyTaskOnTaskwarrior( - localTask.description, - localTask.project!, - localTask.due!, - localTask.priority!, - localTask.status, - localTask.uuid!, - ); - if (localTask.status == 'completed') { - completeTask('email', localTask.uuid!); - } else if (localTask.status == 'deleted') { - deleteTask('email', localTask.uuid!); - } - } - } - } -} - -Future deleteTask(String email, String taskUuid) async { - var baseUrl = await CredentialsStorage.getApiUrl(); - var c = await CredentialsStorage.getClientId(); - var e = await CredentialsStorage.getEncryptionSecret(); - final url = Uri.parse('$baseUrl/delete-task'); - final body = jsonEncode({ - 'email': email, - 'encryptionSecret': e, - 'UUID': c, - 'taskuuid': taskUuid, - }); - - try { - final response = await http.post( - url, - headers: { - 'Content-Type': 'application/json', - }, - body: body, - ); - - if (response.statusCode == 200) { - debugPrint('Task deleted successfully on server'); - } else { - debugPrint('Failed to delete task: ${response.statusCode}'); - } - } catch (e) { - debugPrint('Error deleting task: $e'); - } -} - -Future completeTask(String email, String taskUuid) async { - var c = await CredentialsStorage.getClientId(); - var e = await CredentialsStorage.getEncryptionSecret(); - var baseUrl = await CredentialsStorage.getApiUrl(); - final url = Uri.parse('$baseUrl/complete-task'); - final body = jsonEncode({ - 'email': email, - 'encryptionSecret': e, - 'UUID': c, - 'taskuuid': taskUuid, - }); - - try { - final response = await http.post( - url, - headers: { - 'Content-Type': 'application/json', - }, - body: body, - ); - - if (response.statusCode == 200) { - debugPrint('Task completed successfully on server'); - } else { - debugPrint('Failed to complete task: ${response.statusCode}'); - ScaffoldMessenger.of(context as BuildContext).showSnackBar(const SnackBar( - content: Text( - "Failed to complete task!", - style: TextStyle(color: Colors.red), - ))); - } - } catch (e) { - debugPrint('Error completing task: $e'); - } -} - -Future addTaskAndDeleteFromDatabase(String description, String project, - String due, String priority, List tags) async { - var baseUrl = await CredentialsStorage.getApiUrl(); - String apiUrl = '$baseUrl/add-task'; - var c = await CredentialsStorage.getClientId(); - var e = await CredentialsStorage.getEncryptionSecret(); - debugPrint("Database Adding Tags $tags $description"); - debugPrint(c); - debugPrint(e); - var res = await http.post( - Uri.parse(apiUrl), - headers: { - 'Content-Type': 'text/plain', - }, - body: jsonEncode({ - 'email': 'email', - 'encryptionSecret': e, - 'UUID': c, - 'description': description, - 'project': project, - 'due': due, - 'priority': priority, - 'tags': tags - }), - ); - debugPrint('Database res ${res.body}'); - var taskDatabase = TaskDatabase(); - await taskDatabase.open(); - await taskDatabase._database!.delete( - 'Tasks', - where: 'description = ? AND due = ? AND project = ? AND priority = ?', - whereArgs: [description, due, project, priority], - ); -} - -Future modifyTaskOnTaskwarrior(String description, String project, - String due, String priority, String status, String taskuuid) async { - var baseUrl = await CredentialsStorage.getApiUrl(); - var c = await CredentialsStorage.getClientId(); - var e = await CredentialsStorage.getEncryptionSecret(); - String apiUrl = '$baseUrl/modify-task'; - debugPrint(c); - debugPrint(e); - final response = await http.post( - Uri.parse(apiUrl), - headers: { - 'Content-Type': 'text/plain', - }, - body: jsonEncode({ - "email": "e", - "encryptionSecret": e, - "UUID": c, - "description": description, - "priority": priority, - "project": project, - "due": due, - "status": status, - "taskuuid": taskuuid, - }), - ); - - if (response.statusCode != 200) { - ScaffoldMessenger.of(context as BuildContext).showSnackBar(const SnackBar( - content: Text( - "Failed to update task!", - style: TextStyle(color: Colors.red), - ))); - } - - var taskDatabase = TaskDatabase(); - await taskDatabase.open(); - await taskDatabase._database!.delete( - 'Tasks', - where: 'description = ? AND due = ? AND project = ? AND priority = ?', - whereArgs: [description, due, project, priority], - ); -} - -class TaskDatabase { - Database? _database; - - Future open() async { - var databasesPath = await getDatabasesPath(); - String path = join(databasesPath, 'tasks.db'); - - _database = await openDatabase(path, - version: 1, - onOpen: (db) async => await addTagsColumnIfNeeded(db), - 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"); - } - } - - Future ensureDatabaseIsOpen() async { - if (_database == null) { - await open(); - } - } - - Future> fetchTasksFromDatabase() async { - await ensureDatabaseIsOpen(); - - final List> maps = await _database!.query('Tasks'); - debugPrint("Database fetch ${maps.last}"); - var a = List.generate(maps.length, (i) { - return Tasks( - 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!.delete('Tasks'); - debugPrint('Deleted all tasks'); - await open(); - debugPrint('Created new task table'); - } - - Future printDatabaseContents() async { - await ensureDatabaseIsOpen(); - - 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(Tasks task) async { - await ensureDatabaseIsOpen(); - debugPrint("Database Insert"); - var dbi = await _database!.insert( - 'Tasks', - task.toDbJson(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); - debugPrint("Database Insert ${task.toDbJson()} $dbi"); - } - - Future updateTask(Tasks task) async { - await ensureDatabaseIsOpen(); - - await _database!.update( - 'Tasks', - task.toDbJson(), - where: 'uuid = ?', - whereArgs: [task.uuid], - ); - } - - Future getTaskByUuid(String uuid) async { - await ensureDatabaseIsOpen(); - - List> maps = await _database!.query( - 'Tasks', - where: 'uuid = ?', - whereArgs: [uuid], - ); - - if (maps.isNotEmpty) { - return Tasks.fromDbJson(maps.first); - } else { - return null; - } - } - - Future markTaskAsCompleted(String uuid) async { - await ensureDatabaseIsOpen(); - - await _database!.update( - 'Tasks', - {'modified': (DateTime.now()).toIso8601String(), 'status': 'completed'}, - where: 'uuid = ?', - whereArgs: [uuid], - ); - debugPrint('task${uuid}completed'); - debugPrint({DateTime.now().toIso8601String()}.toString()); - } - - Future markTaskAsDeleted(String uuid) async { - await ensureDatabaseIsOpen(); - - await _database!.update( - 'Tasks', - {'status': 'deleted'}, - where: 'uuid = ?', - whereArgs: [uuid], - ); - debugPrint('task${uuid}deleted'); - } - - Future saveEditedTaskInDB( - String uuid, - String newDescription, - String newProject, - String newStatus, - String newPriority, - String newDue, - ) async { - await ensureDatabaseIsOpen(); - - 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 { - await ensureDatabaseIsOpen(); - - List> maps = await _database!.query( - 'Tasks', - where: 'uuid IS NULL OR uuid = ?', - whereArgs: [''], - ); - - return List.generate(maps.length, (i) { - return Tasks.fromDbJson(maps[i]); - }); - } - - Future> getTasksByProject(String project) async { - List> maps = await _database!.query( - 'Tasks', - where: 'project = ?', - whereArgs: [project], - ); - debugPrint("DB Stored for $maps"); - return List.generate(maps.length, (i) { - return Tasks( - 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 { - var taskDatabase = TaskDatabase(); - await taskDatabase.open(); - await taskDatabase.ensureDatabaseIsOpen(); - - 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 { - final List> maps = await _database!.query( - 'tasks', - where: 'description LIKE ? OR project LIKE ?', - whereArgs: ['%$query%', '%$query%'], - ); - return List.generate(maps.length, (i) { - return Tasks.fromDbJson(maps[i]); - }); - } - - Future close() async { - await _database!.close(); - } -} diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart index ae7602e5..715e1956 100644 --- a/lib/app/modules/home/controllers/home_controller.dart +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -9,7 +9,6 @@ import 'package:get/get.dart'; import 'package:home_widget/home_widget.dart'; import 'package:loggy/loggy.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/models/filters.dart'; import 'package:taskwarrior/app/models/json/task.dart'; @@ -32,6 +31,10 @@ 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/v3/db/task_database.dart'; +import 'package:taskwarrior/app/v3/db/update.dart'; +import 'package:taskwarrior/app/v3/models/task.dart'; +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'; @@ -59,7 +62,7 @@ class HomeController extends GetxController { final ScrollController scrollController = ScrollController(); final RxBool showbtn = false.obs; late TaskDatabase taskdb; - var tasks = [].obs; + var tasks = [].obs; final RxBool isRefreshing = false.obs; @override @@ -116,16 +119,17 @@ class HomeController extends GetxController { Future refreshTasks(String clientId, String encryptionSecret) async { TaskDatabase taskDatabase = TaskDatabase(); await taskDatabase.open(); - List tasksFromServer = await fetchTasks(clientId, encryptionSecret); + List tasksFromServer = + await fetchTasks(clientId, encryptionSecret); await updateTasksInDatabase(tasksFromServer); - List fetchedTasks = await taskDatabase.fetchTasksFromDatabase(); + List fetchedTasks = await taskDatabase.fetchTasksFromDatabase(); tasks.value = fetchedTasks; } Future fetchTasksFromDB() async { TaskDatabase taskDatabase = TaskDatabase(); await taskDatabase.open(); - List fetchedTasks = await taskDatabase.fetchTasksFromDatabase(); + List fetchedTasks = await taskDatabase.fetchTasksFromDatabase(); tasks.value = fetchedTasks; } 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 1fcc2687..b27ae8f0 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 @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/models/json/task.dart'; import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; import 'package:taskwarrior/app/modules/home/controllers/widget.controller.dart'; @@ -17,6 +16,7 @@ import 'package:taskwarrior/app/utils/taskfunctions/add_task_dialog_utils.dart'; import 'package:taskwarrior/app/utils/taskfunctions/tags.dart'; import 'package:taskwarrior/app/utils/taskfunctions/taskparser.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; +import 'package:taskwarrior/app/v3/models/task.dart'; class AddTaskBottomSheet extends StatelessWidget { final HomeController homeController; @@ -282,7 +282,7 @@ class AddTaskBottomSheet extends StatelessWidget { void onSaveButtonClickedTaskC(BuildContext context) async { if (homeController.formKey.currentState!.validate()) { debugPrint("tags ${homeController.tags}"); - var task = Tasks( + var task = TaskForC( description: homeController.namecontroller.text, status: 'pending', priority: homeController.priority.value, diff --git a/lib/app/modules/home/views/show_details.dart b/lib/app/modules/home/views/show_details.dart index 6e74f8a3..2a0dea77 100644 --- a/lib/app/modules/home/views/show_details.dart +++ b/lib/app/modules/home/views/show_details.dart @@ -1,18 +1,18 @@ // ignore_for_file: deprecated_member_use, use_build_context_synchronously - -import 'package:date_format/date_format.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; -import 'package:taskwarrior/api_service.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 Tasks task; + final TaskForC task; const TaskDetails({super.key, required this.task}); @override @@ -53,7 +53,8 @@ class _TaskDetailsState extends State { @override Widget build(BuildContext context) { - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; return WillPopScope( onWillPop: () async { if (hasChanges) { @@ -123,10 +124,18 @@ class _TaskDetailsState extends State { }); }), _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)), + _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)), ], ), ), @@ -206,7 +215,8 @@ class _TaskDetailsState extends State { } Widget _buildDetail(String label, String value) { - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; return Container( width: double.infinity, decoration: BoxDecoration( @@ -251,7 +261,8 @@ class _TaskDetailsState extends State { Future _showEditDialog( BuildContext context, String label, String initialValue) async { - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; final TextEditingController controller = TextEditingController(text: initialValue); return await showDialog( @@ -260,21 +271,15 @@ class _TaskDetailsState extends State { return Utils.showAlertDialog( title: Text( '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.edit} $label', - style: TextStyle( - color: tColors.primaryTextColor - ), + style: TextStyle(color: tColors.primaryTextColor), ), content: TextField( - style: TextStyle( - color: tColors.primaryTextColor - ), + style: TextStyle(color: tColors.primaryTextColor), controller: controller, decoration: InputDecoration( hintText: '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.enterNew} $label', - hintStyle: TextStyle( - color: tColors.primaryTextColor - ), + hintStyle: TextStyle(color: tColors.primaryTextColor), ), ), actions: [ @@ -286,9 +291,7 @@ class _TaskDetailsState extends State { SentenceManager(currentLanguage: AppSettings.selectedLanguage) .sentences .cancel, - style: TextStyle( - color: tColors.primaryTextColor - ), + style: TextStyle(color: tColors.primaryTextColor), ), ), TextButton( @@ -299,9 +302,7 @@ class _TaskDetailsState extends State { SentenceManager(currentLanguage: AppSettings.selectedLanguage) .sentences .save, - style: TextStyle( - color: tColors.primaryTextColor - ), + style: TextStyle(color: tColors.primaryTextColor), ), ), ], @@ -312,16 +313,15 @@ class _TaskDetailsState extends State { Future _showSelectDialog(BuildContext context, String label, String initialValue, List options) async { - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + 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 - ), + style: TextStyle(color: tColors.primaryTextColor), ), content: Column( mainAxisSize: MainAxisSize.min, @@ -329,9 +329,7 @@ class _TaskDetailsState extends State { return RadioListTile( title: Text( option, - style: TextStyle( - color: tColors.primaryTextColor - ), + style: TextStyle(color: tColors.primaryTextColor), ), value: option, groupValue: initialValue, @@ -362,7 +360,8 @@ class _TaskDetailsState extends State { Future _showUnsavedChangesDialog( BuildContext context) async { - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; return showDialog( context: context, barrierDismissible: false, @@ -372,16 +371,13 @@ class _TaskDetailsState extends State { SentenceManager(currentLanguage: AppSettings.selectedLanguage) .sentences .unsavedChanges, - style: TextStyle( - color: tColors.primaryTextColor), + style: TextStyle(color: tColors.primaryTextColor), ), content: Text( SentenceManager(currentLanguage: AppSettings.selectedLanguage) .sentences .unsavedChangesWarning, - style: TextStyle( - color: tColors.primaryTextColor - ), + style: TextStyle(color: tColors.primaryTextColor), ), actions: [ TextButton( diff --git a/lib/app/modules/home/views/show_tasks.dart b/lib/app/modules/home/views/show_tasks.dart index 0b548af1..f0cb1640 100644 --- a/lib/app/modules/home/views/show_tasks.dart +++ b/lib/app/modules/home/views/show_tasks.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; import 'package:taskwarrior/app/modules/home/views/show_details.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; @@ -10,6 +9,10 @@ import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.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/complete.dart'; +import 'package:taskwarrior/app/v3/net/delete.dart'; class TaskViewBuilder extends StatelessWidget { const TaskViewBuilder({ @@ -30,12 +33,12 @@ class TaskViewBuilder extends StatelessWidget { Theme.of(context).extension()!; return Obx(() { - List tasks = List.from(taskController.tasks); + List tasks = List.from(taskController.tasks); // Filter tasks based on the selected project if (project != 'All Projects') { tasks = tasks.where((task) => task.project == project).toList(); } else { - tasks = List.from(tasks); + tasks = List.from(tasks); } // Apply other filters and sorting @@ -109,7 +112,7 @@ class TaskViewBuilder extends StatelessWidget { ), itemCount: tasks.length, itemBuilder: (context, index) { - Tasks task = tasks[index]; + TaskForC task = tasks[index]; return Slidable( startActionPane: ActionPane( motion: const BehindMotion(), @@ -133,8 +136,8 @@ class TaskViewBuilder extends StatelessWidget { ); }, icon: Icons.done, - label: SentenceManager(currentLanguage: - AppSettings.selectedLanguage) + label: SentenceManager( + currentLanguage: AppSettings.selectedLanguage) .sentences .complete, backgroundColor: TaskWarriorColors.green, @@ -164,7 +167,7 @@ class TaskViewBuilder extends StatelessWidget { }, icon: Icons.delete, label: SentenceManager( - currentLanguage: AppSettings.selectedLanguage) + currentLanguage: AppSettings.selectedLanguage) .sentences .delete, backgroundColor: TaskWarriorColors.red, diff --git a/lib/app/modules/reports/controllers/reports_controller.dart b/lib/app/modules/reports/controllers/reports_controller.dart index 17ab277f..d46ee206 100644 --- a/lib/app/modules/reports/controllers/reports_controller.dart +++ b/lib/app/modules/reports/controllers/reports_controller.dart @@ -4,9 +4,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/models/json/task.dart'; import 'package:taskwarrior/app/models/storage.dart'; import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; @@ -19,6 +17,8 @@ import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter/services.dart'; +import 'package:taskwarrior/app/v3/db/task_database.dart'; +import 'package:taskwarrior/app/v3/models/task.dart'; import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; class ReportsController extends GetxController @@ -410,7 +410,7 @@ class ReportsController extends GetxController }); } - Future> fetchTasks() async { + Future> fetchTasks() async { await taskDatabase.open(); return await taskDatabase.fetchTasksFromDatabase(); } diff --git a/lib/app/modules/reports/views/burn_down_daily_taskc.dart b/lib/app/modules/reports/views/burn_down_daily_taskc.dart index f1114bdd..d6f0cbc8 100644 --- a/lib/app/modules/reports/views/burn_down_daily_taskc.dart +++ b/lib/app/modules/reports/views/burn_down_daily_taskc.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/models/chart.dart'; import 'package:taskwarrior/app/modules/reports/views/common_chart_indicator.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; @@ -10,6 +9,8 @@ import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.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'; class BurnDownDailyTaskc extends StatelessWidget { BurnDownDailyTaskc({super.key}); @@ -38,8 +39,10 @@ class BurnDownDailyTaskc extends StatelessWidget { fontWeight: TaskWarriorFonts.bold, ), ), - Text('${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsPending}: $pendingCount'), - Text('${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsCompleted}: $completedCount'), + Text( + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsPending}: $pendingCount'), + Text( + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsCompleted}: $completedCount'), ], ), ); @@ -49,11 +52,11 @@ class BurnDownDailyTaskc extends StatelessWidget { Future>> fetchDailyInfo() async { TaskDatabase taskDatabase = TaskDatabase(); await taskDatabase.open(); - List tasks = await taskDatabase.fetchTasksFromDatabase(); + List tasks = await taskDatabase.fetchTasksFromDatabase(); return _processData(tasks); } - Map> _processData(List tasks) { + Map> _processData(List tasks) { Map> dailyInfo = {}; // Sort tasks by entry date in ascending order @@ -83,7 +86,8 @@ class BurnDownDailyTaskc extends StatelessWidget { @override Widget build(BuildContext context) { double height = MediaQuery.of(context).size.height; // Screen height - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; return FutureBuilder>>( future: fetchDailyInfo(), builder: (context, snapshot) { @@ -92,7 +96,9 @@ class BurnDownDailyTaskc extends StatelessWidget { } if (snapshot.hasError) { - return Center(child: Text('${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsError}: ${snapshot.error}')); + return Center( + child: Text( + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsError}: ${snapshot.error}')); } Map> dailyInfo = snapshot.data ?? {}; diff --git a/lib/app/modules/reports/views/burn_down_monthly_taskc.dart b/lib/app/modules/reports/views/burn_down_monthly_taskc.dart index b408013e..712bdd27 100644 --- a/lib/app/modules/reports/views/burn_down_monthly_taskc.dart +++ b/lib/app/modules/reports/views/burn_down_monthly_taskc.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/models/chart.dart'; import 'package:taskwarrior/app/modules/reports/views/common_chart_indicator.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; @@ -10,6 +9,8 @@ import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.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'; class BurnDownMonthlyTaskc extends StatelessWidget { BurnDownMonthlyTaskc({super.key}); @@ -33,22 +34,16 @@ class BurnDownMonthlyTaskc extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .reportsMonthYear}: $monthYear', + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsMonthYear}: $monthYear', style: const TextStyle( fontWeight: TaskWarriorFonts.bold, ), ), Text( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .reportsPending}: $pendingCount', + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsPending}: $pendingCount', ), Text( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .reportsCompleted}: $completedCount', + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsCompleted}: $completedCount', ), ], ), @@ -59,11 +54,11 @@ class BurnDownMonthlyTaskc extends StatelessWidget { Future>> fetchMonthlyInfo() async { TaskDatabase taskDatabase = TaskDatabase(); await taskDatabase.open(); - List tasks = await taskDatabase.fetchTasksFromDatabase(); + List tasks = await taskDatabase.fetchTasksFromDatabase(); return sortBurnDownMonthly(tasks); } - Map> sortBurnDownMonthly(List allData) { + Map> sortBurnDownMonthly(List allData) { Map> monthlyInfo = {}; allData.sort((a, b) => a.entry.compareTo(b.entry)); @@ -96,7 +91,8 @@ class BurnDownMonthlyTaskc extends StatelessWidget { @override Widget build(BuildContext context) { final double height = MediaQuery.of(context).size.height; - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; return FutureBuilder>>( future: fetchMonthlyInfo(), builder: (context, snapshot) { @@ -106,10 +102,8 @@ class BurnDownMonthlyTaskc extends StatelessWidget { if (snapshot.hasError) { return Center( - child: Text('${SentenceManager( - currentLanguage: AppSettings.selectedLanguage) - .sentences - .reportsError}: ${snapshot.error}')); + child: Text( + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsError}: ${snapshot.error}')); } Map> monthlyInfo = snapshot.data ?? {}; diff --git a/lib/app/modules/reports/views/burn_down_weekly_taskc.dart b/lib/app/modules/reports/views/burn_down_weekly_taskc.dart index f4d5da7b..bbbb7adb 100644 --- a/lib/app/modules/reports/views/burn_down_weekly_taskc.dart +++ b/lib/app/modules/reports/views/burn_down_weekly_taskc.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/models/chart.dart'; import 'package:taskwarrior/app/modules/reports/views/common_chart_indicator.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; @@ -10,6 +9,8 @@ import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.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'; class BurnDownWeeklyTask extends StatelessWidget { BurnDownWeeklyTask({super.key}); @@ -39,14 +40,10 @@ class BurnDownWeeklyTask extends StatelessWidget { ), ), Text( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .reportsPending}: $pendingCount', + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsPending}: $pendingCount', ), Text( - '${SentenceManager(currentLanguage: AppSettings.selectedLanguage) - .sentences - .reportsCompleted}: $completedCount', + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsCompleted}: $completedCount', ), ], ), @@ -57,11 +54,11 @@ class BurnDownWeeklyTask extends StatelessWidget { Future>> fetchWeeklyInfo() async { TaskDatabase taskDatabase = TaskDatabase(); await taskDatabase.open(); - List tasks = await taskDatabase.fetchTasksFromDatabase(); + List tasks = await taskDatabase.fetchTasksFromDatabase(); return sortBurnDownWeekly(tasks); } - Map> sortBurnDownWeekly(List allData) { + Map> sortBurnDownWeekly(List allData) { // Initialize weeklyInfo map Map> weeklyInfo = {}; @@ -101,7 +98,8 @@ class BurnDownWeeklyTask extends StatelessWidget { @override Widget build(BuildContext context) { - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; double height = MediaQuery.of(context).size.height; // Screen height return FutureBuilder>>( future: fetchWeeklyInfo(), @@ -112,10 +110,8 @@ class BurnDownWeeklyTask extends StatelessWidget { if (snapshot.hasError) { return Center( - child: Text('${SentenceManager( - currentLanguage: AppSettings.selectedLanguage) - .sentences - .reportsError}: ${snapshot.error}')); + child: Text( + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.reportsError}: ${snapshot.error}')); } Map> weeklyInfo = snapshot.data ?? {}; diff --git a/lib/app/modules/reports/views/reports_view_taskc.dart b/lib/app/modules/reports/views/reports_view_taskc.dart index ede90e04..bdb77374 100644 --- a/lib/app/modules/reports/views/reports_view_taskc.dart +++ b/lib/app/modules/reports/views/reports_view_taskc.dart @@ -8,9 +8,10 @@ import 'package:taskwarrior/app/modules/reports/views/burn_down_weekly_taskc.dar 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/taskwarrior_fonts.dart'; -import 'package:taskwarrior/api_service.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; +import 'package:taskwarrior/app/v3/db/task_database.dart'; +import 'package:taskwarrior/app/v3/models/task.dart'; class ReportsHomeTaskc extends StatelessWidget { final ReportsController reportsController = Get.put(ReportsController()); @@ -18,7 +19,7 @@ class ReportsHomeTaskc extends StatelessWidget { ReportsHomeTaskc({super.key}); - Future> fetchTasks() async { + Future> fetchTasks() async { await taskDatabase.open(); return await taskDatabase.fetchTasksFromDatabase(); } @@ -28,11 +29,12 @@ class ReportsHomeTaskc extends StatelessWidget { double height = MediaQuery.of(context).size.height; reportsController.initReportsTour(); reportsController.showReportsTour(context); - return FutureBuilder>( + return FutureBuilder>( future: fetchTasks(), builder: (context, snapshot) { - List allTasks = snapshot.data ?? []; - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + List allTasks = snapshot.data ?? []; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; return Scaffold( appBar: AppBar( backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, diff --git a/lib/app/v3/db/task_database.dart b/lib/app/v3/db/task_database.dart new file mode 100644 index 00000000..0216e810 --- /dev/null +++ b/lib/app/v3/db/task_database.dart @@ -0,0 +1,256 @@ +import 'package:flutter/material.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:path/path.dart'; +import 'package:taskwarrior/app/v3/models/task.dart'; + +class TaskDatabase { + Database? _database; + + Future open() async { + var databasesPath = await getDatabasesPath(); + String path = join(databasesPath, 'tasks.db'); + + _database = await openDatabase(path, + version: 1, + onOpen: (db) async => await addTagsColumnIfNeeded(db), + 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"); + } + } + + Future ensureDatabaseIsOpen() async { + if (_database == null) { + await open(); + } + } + + Future> fetchTasksFromDatabase() async { + await ensureDatabaseIsOpen(); + + 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!.delete('Tasks'); + debugPrint('Deleted all tasks'); + await open(); + debugPrint('Created new task table'); + } + + Future printDatabaseContents() async { + await ensureDatabaseIsOpen(); + + 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"); + var dbi = await _database!.insert( + 'Tasks', + task.toDbJson(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + debugPrint("Database Insert ${task.toDbJson()} $dbi"); + } + + Future updateTask(TaskForC task) async { + await ensureDatabaseIsOpen(); + + await _database!.update( + 'Tasks', + task.toDbJson(), + where: 'uuid = ?', + whereArgs: [task.uuid], + ); + } + + Future getTaskByUuid(String uuid) async { + await ensureDatabaseIsOpen(); + + List> maps = await _database!.query( + 'Tasks', + where: 'uuid = ?', + whereArgs: [uuid], + ); + + if (maps.isNotEmpty) { + return TaskForC.fromDbJson(maps.first); + } else { + return null; + } + } + + Future markTaskAsCompleted(String uuid) async { + await ensureDatabaseIsOpen(); + + await _database!.update( + 'Tasks', + {'modified': (DateTime.now()).toIso8601String(), 'status': 'completed'}, + where: 'uuid = ?', + whereArgs: [uuid], + ); + debugPrint('task${uuid}completed'); + debugPrint({DateTime.now().toIso8601String()}.toString()); + } + + Future markTaskAsDeleted(String uuid) async { + await ensureDatabaseIsOpen(); + + await _database!.update( + 'Tasks', + {'status': 'deleted'}, + where: 'uuid = ?', + whereArgs: [uuid], + ); + debugPrint('task${uuid}deleted'); + } + + Future saveEditedTaskInDB( + String uuid, + String newDescription, + String newProject, + String newStatus, + String newPriority, + String newDue, + ) async { + await ensureDatabaseIsOpen(); + + 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 { + await ensureDatabaseIsOpen(); + + List> maps = await _database!.query( + 'Tasks', + where: 'uuid IS NULL OR uuid = ?', + whereArgs: [''], + ); + + return List.generate(maps.length, (i) { + return TaskForC.fromDbJson(maps[i]); + }); + } + + Future> getTasksByProject(String project) async { + List> maps = await _database!.query( + 'Tasks', + where: 'project = ?', + 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(' ')); + }); + } + + Future> fetchUniqueProjects() async { + var taskDatabase = TaskDatabase(); + await taskDatabase.open(); + await taskDatabase.ensureDatabaseIsOpen(); + + 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 { + final List> maps = await _database!.query( + 'tasks', + where: 'description LIKE ? OR project LIKE ?', + whereArgs: ['%$query%', '%$query%'], + ); + return List.generate(maps.length, (i) { + return TaskForC.fromDbJson(maps[i]); + }); + } + + Future close() async { + await _database!.close(); + } + + 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/db/update.dart b/lib/app/v3/db/update.dart new file mode 100644 index 00000000..c8675632 --- /dev/null +++ b/lib/app/v3/db/update.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/v3/db/task_database.dart'; +import 'package:taskwarrior/app/v3/models/task.dart'; +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'; + +Future updateTasksInDatabase(List tasks) async { + var taskDatabase = TaskDatabase(); + await taskDatabase.open(); + // find tasks without UUID + List tasksWithoutUUID = await taskDatabase.findTasksWithoutUUIDs(); + + //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! : []); + } catch (e) { + debugPrint('Failed to add task without UUID to server: $e'); + } + } + + // update existing tasks in db + for (var task in tasks) { + var existingTask = await taskDatabase.getTaskByUuid(task.uuid!); + if (existingTask != null) { + if (task.modified!.compareTo(existingTask.modified!) > 0) { + await taskDatabase.updateTask(task); + } + } else { + // add new tasks to db + await taskDatabase.insertTask(task); + } + } + + var localTasks = await taskDatabase.fetchTasksFromDatabase(); + var localTasksMap = {for (var task in localTasks) task.uuid: task}; + + for (var serverTask in tasks) { + var localTask = localTasksMap[serverTask.uuid]; + + if (localTask == null) { + // Task doesn't exist in the local database, insert it + await taskDatabase.insertTask(serverTask); + } else { + var serverTaskModifiedDate = DateTime.parse(serverTask.modified!); + var localTaskModifiedDate = DateTime.parse(localTask.modified!); + + if (serverTaskModifiedDate.isAfter(localTaskModifiedDate)) { + // Server task is newer, update local database + await taskDatabase.updateTask(serverTask); + } else if (serverTaskModifiedDate.isBefore(localTaskModifiedDate)) { + // local task is newer, update server + await modifyTaskOnTaskwarrior( + localTask.description, + localTask.project!, + localTask.due!, + localTask.priority!, + localTask.status, + localTask.uuid!, + ); + if (localTask.status == 'completed') { + completeTask('email', localTask.uuid!); + } else if (localTask.status == 'deleted') { + deleteTask('email', localTask.uuid!); + } + } + } + } +} diff --git a/lib/app/v3/models/annotation.dart b/lib/app/v3/models/annotation.dart new file mode 100644 index 00000000..9f8036e7 --- /dev/null +++ b/lib/app/v3/models/annotation.dart @@ -0,0 +1,5 @@ +class Annotation { + 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 new file mode 100644 index 00000000..c93884f0 --- /dev/null +++ b/lib/app/v3/models/task.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +class TaskForC { + final int id; + final String description; + final String? project; + final String status; + final String? uuid; + final double? urgency; + final String? priority; + final String? due; + final String? end; + final String entry; + 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}); + + 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(' ')); + } + + Map toJson() { + 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, + 'project': project, + 'status': status, + 'uuid': uuid, + 'urgency': urgency, + 'priority': priority, + 'due': due, + 'end': end, + 'entry': entry, + 'modified': modified, + 'tags': tags != null ? tags?.join(" ") : "" + }; + } +} diff --git a/lib/app/v3/net/add_task.dart b/lib/app/v3/net/add_task.dart new file mode 100644 index 00000000..370a5c27 --- /dev/null +++ b/lib/app/v3/net/add_task.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; +import 'package:taskwarrior/app/v3/db/task_database.dart'; + +Future addTaskAndDeleteFromDatabase(String description, String project, + String due, String priority, List tags) async { + var baseUrl = await CredentialsStorage.getApiUrl(); + String apiUrl = '$baseUrl/add-task'; + var c = await CredentialsStorage.getClientId(); + var e = await CredentialsStorage.getEncryptionSecret(); + debugPrint("Database Adding Tags $tags $description"); + debugPrint(c); + debugPrint(e); + var res = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'text/plain', + }, + body: jsonEncode({ + 'email': 'email', + 'encryptionSecret': e, + 'UUID': c, + 'description': description, + 'project': project, + 'due': due, + 'priority': priority, + 'tags': tags + }), + ); + debugPrint('Database res ${res.body}'); + var taskDatabase = TaskDatabase(); + await taskDatabase.open(); + await taskDatabase.deleteTask( + description: description, due: due, project: project, priority: priority); +} diff --git a/lib/app/v3/net/complete.dart b/lib/app/v3/net/complete.dart new file mode 100644 index 00000000..b3718146 --- /dev/null +++ b/lib/app/v3/net/complete.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; +import 'package:path/path.dart'; + +Future completeTask(String email, String taskUuid) async { + var c = await CredentialsStorage.getClientId(); + var e = await CredentialsStorage.getEncryptionSecret(); + var baseUrl = await CredentialsStorage.getApiUrl(); + final url = Uri.parse('$baseUrl/complete-task'); + final body = jsonEncode({ + 'email': email, + 'encryptionSecret': e, + 'UUID': c, + 'taskuuid': taskUuid, + }); + + try { + final response = await http.post( + url, + headers: { + 'Content-Type': 'application/json', + }, + body: body, + ); + + if (response.statusCode == 200) { + debugPrint('Task completed successfully on server'); + } else { + debugPrint('Failed to complete task: ${response.statusCode}'); + ScaffoldMessenger.of(context as BuildContext).showSnackBar(const SnackBar( + content: Text( + "Failed to complete task!", + style: TextStyle(color: Colors.red), + ))); + } + } catch (e) { + debugPrint('Error completing task: $e'); + } +} diff --git a/lib/app/v3/net/delete.dart b/lib/app/v3/net/delete.dart new file mode 100644 index 00000000..8873377b --- /dev/null +++ b/lib/app/v3/net/delete.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; + +Future deleteTask(String email, String taskUuid) async { + var baseUrl = await CredentialsStorage.getApiUrl(); + var c = await CredentialsStorage.getClientId(); + var e = await CredentialsStorage.getEncryptionSecret(); + final url = Uri.parse('$baseUrl/delete-task'); + final body = jsonEncode({ + 'email': email, + 'encryptionSecret': e, + 'UUID': c, + 'taskuuid': taskUuid, + }); + + try { + final response = await http.post( + url, + headers: { + 'Content-Type': 'application/json', + }, + body: body, + ); + + if (response.statusCode == 200) { + debugPrint('Task deleted successfully on server'); + } else { + debugPrint('Failed to delete task: ${response.statusCode}'); + } + } catch (e) { + debugPrint('Error deleting task: $e'); + } +} diff --git a/lib/app/v3/net/fetch.dart b/lib/app/v3/net/fetch.dart new file mode 100644 index 00000000..e1cff2c6 --- /dev/null +++ b/lib/app/v3/net/fetch.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; +import 'package:taskwarrior/app/v3/models/task.dart'; +import 'package:taskwarrior/app/v3/net/origin.dart'; +import 'package:http/http.dart' as http; + +Future> fetchTasks(String uuid, String encryptionSecret) async { + var baseUrl = await CredentialsStorage.getApiUrl(); + try { + String url = + '$baseUrl/tasks?email=email&origin=$origin&UUID=$uuid&encryptionSecret=$encryptionSecret'; + + var response = await http.get(Uri.parse(url), headers: { + "Content-Type": "application/json", + }).timeout(const Duration(seconds: 10000)); + if (response.statusCode == 200) { + List allTasks = jsonDecode(response.body); + debugPrint(allTasks.toString()); + return allTasks.map((task) => TaskForC.fromJson(task)).toList(); + } else { + throw Exception('Failed to load tasks'); + } + } catch (e) { + debugPrint('Error fetching tasks: $e'); + return []; + } +} diff --git a/lib/app/v3/net/modify.dart b/lib/app/v3/net/modify.dart new file mode 100644 index 00000000..928f531f --- /dev/null +++ b/lib/app/v3/net/modify.dart @@ -0,0 +1,46 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +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'; + +Future modifyTaskOnTaskwarrior(String description, String project, + String due, String priority, String status, String taskuuid) async { + var baseUrl = await CredentialsStorage.getApiUrl(); + var c = await CredentialsStorage.getClientId(); + var e = await CredentialsStorage.getEncryptionSecret(); + String apiUrl = '$baseUrl/modify-task'; + debugPrint(c); + debugPrint(e); + final response = await http.post( + Uri.parse(apiUrl), + headers: { + 'Content-Type': 'text/plain', + }, + body: jsonEncode({ + "email": "e", + "encryptionSecret": e, + "UUID": c, + "description": description, + "priority": priority, + "project": project, + "due": due, + "status": status, + "taskuuid": taskuuid, + }), + ); + + if (response.statusCode != 200) { + ScaffoldMessenger.of(context as BuildContext).showSnackBar(const SnackBar( + content: Text( + "Failed to update task!", + style: TextStyle(color: Colors.red), + ))); + } + + var taskDatabase = TaskDatabase(); + await taskDatabase.open(); + await taskDatabase.deleteTask( + description: description, due: due, project: project, priority: priority); +} diff --git a/lib/app/v3/net/origin.dart b/lib/app/v3/net/origin.dart new file mode 100644 index 00000000..4cc70540 --- /dev/null +++ b/lib/app/v3/net/origin.dart @@ -0,0 +1 @@ +String origin = 'http://localhost:8080'; diff --git a/test/api_service_test.dart b/test/api_service_test.dart index ae9973f6..1b14d90b 100644 --- a/test/api_service_test.dart +++ b/test/api_service_test.dart @@ -6,8 +6,11 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:http/http.dart' as http; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; -import 'package:taskwarrior/api_service.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'; +import 'package:taskwarrior/app/v3/net/fetch.dart'; +import 'package:taskwarrior/app/v3/net/origin.dart'; import 'api_service_test.mocks.dart'; @@ -42,7 +45,7 @@ void main() { 'modified': '2024-11-01', }; - final task = Tasks.fromJson(json); + final task = TaskForC.fromJson(json); expect(task.id, 1); expect(task.description, 'Task 1'); @@ -57,7 +60,7 @@ void main() { }); test('toJson converts Tasks object to JSON', () { - final task = Tasks( + final task = TaskForC( id: 1, description: 'Task 1', project: 'Project 1', @@ -97,7 +100,7 @@ void main() { final result = await fetchTasks('123', 'secret'); - expect(result, isA>()); + expect(result, isA>()); }); test('fetchTasks returns empty array', () async { @@ -117,7 +120,7 @@ void main() { }); test('insertTask adds a task to the database', () async { - final task = Tasks( + final task = TaskForC( id: 1, description: 'Task 1', project: 'Project 1', @@ -140,7 +143,7 @@ void main() { }); test('deleteAllTasksInDB removes all tasks', () async { - final task = Tasks( + final task = TaskForC( id: 1, description: 'Task 1', project: 'Project 1',