diff --git a/lib/api_service.dart b/lib/api_service.dart new file mode 100644 index 00000000..3cb5f2cd --- /dev/null +++ b/lib/api_service.dart @@ -0,0 +1,509 @@ +// ignore_for_file: depend_on_referenced_packages, unnecessary_null_in_if_null_operators + +import 'dart:convert'; +import 'package:flutter/foundation.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; + + 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, + }); + + 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'], + ); + } + + Map toJson() { + return { + 'id': id, + 'description': description, + 'project': project, + 'status': status, + 'uuid': uuid, + 'urgency': urgency, + 'priority': priority, + 'due': due, + 'end': end, + 'entry': entry, + 'modified': modified, + }; + } +} + +String baseUrl = 'http://YOUR_IP:8000'; +String origin = 'http://localhost:8080'; + +Future> fetchTasks(String uuid, String encryptionSecret) async { + 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'); + } +} + +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!); + } 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 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(); + 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}'); + } + } catch (e) { + debugPrint('Error completing task: $e'); + } +} + +Future addTaskAndDeleteFromDatabase( + String description, String project, String due, String priority) async { + String apiUrl = '$baseUrl/add-task'; + var c = await CredentialsStorage.getClientId(); + var e = await CredentialsStorage.getEncryptionSecret(); + debugPrint(c); + debugPrint(e); + 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, + }), + ); + + 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 { + String apiUrl = '$baseUrl/modify-task'; + var c = await CredentialsStorage.getClientId(); + var e = await CredentialsStorage.getEncryptionSecret(); + debugPrint(c); + debugPrint(e); + 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, + }), + ); + + 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, + 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 ensureDatabaseIsOpen() async { + if (_database == null) { + await open(); + } + } + + Future> fetchTasksFromDatabase() async { + await ensureDatabaseIsOpen(); + + final List> maps = await _database!.query('Tasks'); + 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'], + ); + }); + // 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(); + + await _database!.insert( + 'Tasks', + task.toJson(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + Future updateTask(Tasks task) async { + await ensureDatabaseIsOpen(); + + await _database!.update( + 'Tasks', + task.toJson(), + 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.fromJson(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.fromJson(maps[i]); + }); + } + + Future> getTasksByProject(String project) async { + List> maps = await _database!.query( + 'Tasks', + where: 'project = ?', + whereArgs: [project], + ); + + 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'], + ); + }); + } + + 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.fromJson(maps[i]); + }); + } + + Future close() async { + await _database!.close(); + } +} diff --git a/lib/app/modules/detailRoute/controllers/detail_route_controller.dart b/lib/app/modules/detailRoute/controllers/detail_route_controller.dart index 216805eb..e40c047d 100644 --- a/lib/app/modules/detailRoute/controllers/detail_route_controller.dart +++ b/lib/app/modules/detailRoute/controllers/detail_route_controller.dart @@ -1,3 +1,5 @@ +// ignore_for_file: depend_on_referenced_packages + import 'package:built_collection/built_collection.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; diff --git a/lib/app/modules/detailRoute/views/detail_route_view.dart b/lib/app/modules/detailRoute/views/detail_route_view.dart index 6271fb68..478da44f 100644 --- a/lib/app/modules/detailRoute/views/detail_route_view.dart +++ b/lib/app/modules/detailRoute/views/detail_route_view.dart @@ -1,3 +1,5 @@ +// ignore_for_file: depend_on_referenced_packages, deprecated_member_use + import 'package:built_collection/built_collection.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart index 3713ca4f..163720dc 100644 --- a/lib/app/modules/home/controllers/home_controller.dart +++ b/lib/app/modules/home/controllers/home_controller.dart @@ -9,6 +9,7 @@ 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'; @@ -22,6 +23,7 @@ import 'package:taskwarrior/app/tour/filter_drawer_tour.dart'; import 'package:taskwarrior/app/tour/home_page_tour.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:taskwarrior/app/utils/language/supported_language.dart'; +import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; import 'package:taskwarrior/app/utils/taskfunctions/comparator.dart'; import 'package:taskwarrior/app/utils/taskfunctions/projects.dart'; import 'package:taskwarrior/app/utils/taskfunctions/query.dart'; @@ -49,6 +51,8 @@ class HomeController extends GetxController { final Rx selectedLanguage = SupportedLanguage.english.obs; final ScrollController scrollController = ScrollController(); final RxBool showbtn = false.obs; + late TaskDatabase taskdb; + var tasks = [].obs; @override void onInit() { @@ -66,6 +70,45 @@ class HomeController extends GetxController { if (Platform.isAndroid) { handleHomeWidgetClicked(); } + taskdb = TaskDatabase(); + taskdb.open(); + getUniqueProjects(); + _loadTaskChampion(); + fetchTasksFromDB(); + } + + Future> getUniqueProjects() async { + var taskDatabase = TaskDatabase(); + List uniqueProjects = await taskDatabase.fetchUniqueProjects(); + debugPrint('Unique projects: $uniqueProjects'); + return uniqueProjects; + } + + Future deleteAllTasksInDB() async { + var taskDatabase = TaskDatabase(); + await taskDatabase.deleteAllTasksInDB(); + debugPrint('Deleted all tasks from db'); + } + + Future refreshTasks(String clientId, String encryptionSecret) async { + TaskDatabase taskDatabase = TaskDatabase(); + await taskDatabase.open(); + List tasksFromServer = await fetchTasks(clientId, encryptionSecret); + await updateTasksInDatabase(tasksFromServer); + List fetchedTasks = await taskDatabase.fetchTasksFromDatabase(); + tasks.value = fetchedTasks; + } + + Future fetchTasksFromDB() async { + TaskDatabase taskDatabase = TaskDatabase(); + await taskDatabase.open(); + List fetchedTasks = await taskDatabase.fetchTasksFromDatabase(); + tasks.value = fetchedTasks; + } + + Future _loadTaskChampion() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + taskchampion.value = prefs.getBool('taskchampion') ?? false; } void addListenerToScrollController() { @@ -425,9 +468,12 @@ class HomeController extends GetxController { final SharedPreferences prefs = await SharedPreferences.getInstance(); bool? value; value = prefs.getBool('sync-onStart') ?? false; - + String? clientId, encryptionSecret; + clientId = await CredentialsStorage.getClientId(); + encryptionSecret = await CredentialsStorage.getEncryptionSecret(); if (value) { synchronize(context, false); + refreshTasks(clientId!, encryptionSecret!); } else {} } @@ -435,11 +481,12 @@ class HomeController extends GetxController { RxBool syncOnTaskCreate = false.obs; RxBool delaytask = false.obs; RxBool change24hr = false.obs; + RxBool taskchampion = false.obs; // dialogue box - final formKey = GlobalKey(); final namecontroller = TextEditingController(); + final projectcontroller = TextEditingController(); var due = Rxn(); RxString dueString = ''.obs; RxString priority = 'M'.obs; @@ -505,7 +552,7 @@ class HomeController extends GetxController { void initLanguageAndDarkMode() { isDarkModeOn.value = AppSettings.isDarkMode; selectedLanguage.value = AppSettings.selectedLanguage; - print("called and value is${isDarkModeOn.value}"); + // print("called and value is${isDarkModeOn.value}"); } final addKey = GlobalKey(); @@ -557,6 +604,7 @@ class HomeController extends GetxController { final GlobalKey statusKey = GlobalKey(); final GlobalKey projectsKey = GlobalKey(); + final GlobalKey projectsKeyTaskc = GlobalKey(); final GlobalKey filterTagKey = GlobalKey(); final GlobalKey sortByKey = GlobalKey(); @@ -565,6 +613,7 @@ class HomeController extends GetxController { targets: filterDrawer( statusKey: statusKey, projectsKey: projectsKey, + projectsKeyTaskc: projectsKeyTaskc, filterTagKey: filterTagKey, sortByKey: sortByKey, ), diff --git a/lib/app/modules/home/views/add_task_bottom_sheet.dart b/lib/app/modules/home/views/add_task_bottom_sheet.dart index d2d8d3b0..48fb08e1 100644 --- a/lib/app/modules/home/views/add_task_bottom_sheet.dart +++ b/lib/app/modules/home/views/add_task_bottom_sheet.dart @@ -269,7 +269,7 @@ class AddTaskBottomSheet extends StatelessWidget { initialTime: TimeOfDay.fromDateTime( homeController.due.value ?? DateTime.now()), ); - print("date$date Time : $time"); + // print("date$date Time : $time"); if (time != null) { var dateTime = date.add( Duration( @@ -277,13 +277,13 @@ class AddTaskBottomSheet extends StatelessWidget { minutes: time.minute, ), ); - print(dateTime); + // print(dateTime); homeController.due.value = dateTime.toUtc(); - print("due value ${homeController.due}"); + // print("due value ${homeController.due}"); homeController.dueString.value = DateFormat("dd-MM-yyyy HH:mm").format(dateTime); - print(homeController.dueString.value); + // print(homeController.dueString.value); if (dateTime.isBefore(DateTime.now())) { //Try changing the color. in the settings and Due display. @@ -391,7 +391,6 @@ class AddTaskBottomSheet extends StatelessWidget { ); Widget buildAddButton(BuildContext context) { - return TextButton( child: Text( "Add", @@ -402,7 +401,7 @@ class AddTaskBottomSheet extends StatelessWidget { ), ), onPressed: () async { - print(homeController.formKey.currentState); + // print(homeController.formKey.currentState); if (homeController.formKey.currentState!.validate()) { try { var task = taskParser(homeController.namecontroller.text) diff --git a/lib/app/modules/home/views/add_task_to_taskc_bottom_sheet.dart b/lib/app/modules/home/views/add_task_to_taskc_bottom_sheet.dart new file mode 100644 index 00000000..00e6587a --- /dev/null +++ b/lib/app/modules/home/views/add_task_to_taskc_bottom_sheet.dart @@ -0,0 +1,376 @@ +// ignore_for_file: use_build_context_synchronously, file_names +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/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class AddTaskToTaskcBottomSheet extends StatelessWidget { + final HomeController homeController; + const AddTaskToTaskcBottomSheet({super.key, required this.homeController}); + + @override + Widget build(BuildContext context) { + const title = 'Add Task'; + return Scaffold( + backgroundColor: Colors.transparent, + body: Center( + child: SingleChildScrollView( + child: AlertDialog( + surfaceTintColor: AppSettings.isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + shadowColor: AppSettings.isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + title: Center( + child: Text( + title, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + content: Form( + key: homeController.formKey, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 8), + buildName(), + const SizedBox(height: 8), + buildProject(), + const SizedBox(height: 12), + buildDueDate(context), + const SizedBox(height: 8), + buildPriority(), + ], + ), + ), + ), + actions: [ + buildCancelButton(context), + buildAddButton(context), + ], + ), + ), + ), + ); + } + + Widget buildName() => TextFormField( + autofocus: true, + controller: homeController.namecontroller, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + decoration: InputDecoration( + hintText: 'Enter Task', + hintStyle: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + validator: (name) => name != null && name.isEmpty + ? 'You cannot leave this field empty!' + : null, + ); + + Widget buildProject() => TextFormField( + autofocus: true, + controller: homeController.projectcontroller, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + decoration: InputDecoration( + hintText: 'Enter Project', + hintStyle: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ); + + Widget buildDueDate(BuildContext context) => Row( + children: [ + Text( + "Due : ", + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + fontWeight: TaskWarriorFonts.bold, + height: 3.3, + ), + ), + Expanded( + child: GestureDetector( + child: TextFormField( + style: homeController.inThePast.value + ? TextStyle(color: TaskWarriorColors.red) + : TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + readOnly: true, + controller: TextEditingController( + text: (homeController.due.value != null) + ? homeController.dueString.value + : null, + ), + decoration: InputDecoration( + hintText: 'Select due date', + hintStyle: homeController.inThePast.value + ? TextStyle(color: TaskWarriorColors.red) + : TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onTap: () async { + var date = await showDatePicker( + builder: (BuildContext context, Widget? child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: AppSettings.isDarkMode + ? ColorScheme( + brightness: Brightness.dark, + primary: TaskWarriorColors.white, + onPrimary: TaskWarriorColors.black, + secondary: TaskWarriorColors.black, + onSecondary: TaskWarriorColors.white, + error: TaskWarriorColors.red, + onError: TaskWarriorColors.black, + surface: TaskWarriorColors.black, + onSurface: TaskWarriorColors.white, + ) + : ColorScheme( + brightness: Brightness.light, + primary: TaskWarriorColors.black, + onPrimary: TaskWarriorColors.white, + secondary: TaskWarriorColors.white, + onSecondary: TaskWarriorColors.black, + error: TaskWarriorColors.red, + onError: TaskWarriorColors.white, + surface: TaskWarriorColors.white, + onSurface: TaskWarriorColors.black, + ), + ), + child: child!, + ); + }, + fieldHintText: "Month/Date/Year", + context: context, + initialDate: homeController.due.value ?? DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime(2037, 12, 31), + ); + if (date != null) { + var time = await showTimePicker( + builder: (BuildContext context, Widget? child) { + return Theme( + data: Theme.of(context).copyWith( + textTheme: const TextTheme(), + colorScheme: AppSettings.isDarkMode + ? ColorScheme( + brightness: Brightness.dark, + primary: TaskWarriorColors.white, + onPrimary: TaskWarriorColors.black, + secondary: TaskWarriorColors.black, + onSecondary: TaskWarriorColors.white, + error: TaskWarriorColors.red, + onError: TaskWarriorColors.black, + surface: TaskWarriorColors.black, + onSurface: TaskWarriorColors.white, + ) + : ColorScheme( + brightness: Brightness.light, + primary: TaskWarriorColors.black, + onPrimary: TaskWarriorColors.white, + secondary: TaskWarriorColors.white, + onSecondary: TaskWarriorColors.black, + error: TaskWarriorColors.red, + onError: TaskWarriorColors.white, + surface: TaskWarriorColors.white, + onSurface: TaskWarriorColors.black, + ), + ), + child: MediaQuery( + data: MediaQuery.of(context).copyWith( + alwaysUse24HourFormat: + homeController.change24hr.value, + ), + child: child!), + ); + }, + context: context, + initialTime: TimeOfDay.fromDateTime( + homeController.due.value ?? DateTime.now()), + ); + if (time != null) { + var dateTime = date.add( + Duration( + hours: time.hour, + minutes: time.minute, + ), + ); + homeController.due.value = dateTime.toUtc(); + homeController.dueString.value = + DateFormat("yyyy-MM-dd").format(dateTime); + if (dateTime.isBefore(DateTime.now())) { + homeController.inThePast.value = true; + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + "The selected time is in the past.", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors + .kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + } else { + homeController.inThePast.value = false; + } + } + } + }, + ), + ), + ), + ], + ); + + Widget buildPriority() => Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Priority : ', + style: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + textAlign: TextAlign.left, + ), + DropdownButton( + dropdownColor: AppSettings.isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + value: homeController.priority.value, + elevation: 16, + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + underline: Container( + height: 1.5, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kdialogBackGroundColor + : TaskWarriorColors.kLightDialogBackGroundColor, + ), + onChanged: (String? newValue) { + homeController.priority.value = newValue!; + }, + items: ['H', 'M', 'L', 'None'] + .map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(' $value'), + ); + }).toList(), + ) + ], + ), + ], + ); + + Widget buildCancelButton(BuildContext context) => TextButton( + child: Text( + 'Cancel', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onPressed: () => Navigator.of(context).pop("cancel"), + ); + + Widget buildAddButton(BuildContext context) { + return TextButton( + child: Text( + "Add", + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onPressed: () async { + if (homeController.formKey.currentState!.validate()) { + var task = Tasks( + description: homeController.namecontroller.text, + status: 'pending', + priority: homeController.priority.value, + entry: DateTime.now().toIso8601String(), + id: 0, + project: homeController.projectcontroller.text, + uuid: '', + urgency: 0, + due: homeController.dueString.value, + // dueString.toIso8601String(), + end: '', + modified: 'r'); + await homeController.taskdb.insertTask(task); + homeController.namecontroller.text = ''; + homeController.due.value = null; + homeController.priority.value = 'M'; + homeController.projectcontroller.text = ''; + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Task Added Successfully!', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2))); + Navigator.of(context).pop(); + } + }, + ); + } +} diff --git a/lib/app/modules/home/views/filter_drawer_home_page.dart b/lib/app/modules/home/views/filter_drawer_home_page.dart index f7589d0a..bcf0a06c 100644 --- a/lib/app/modules/home/views/filter_drawer_home_page.dart +++ b/lib/app/modules/home/views/filter_drawer_home_page.dart @@ -1,8 +1,11 @@ +// ignore_for_file: unrelated_type_equality_checks + import 'package:flutter/material.dart'; import 'package:get/get_state_manager/get_state_manager.dart'; import 'package:taskwarrior/app/models/filters.dart'; import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; import 'package:taskwarrior/app/modules/home/views/project_column_home_page.dart'; +import 'package:taskwarrior/app/modules/home/views/project_column_taskc.dart'; import 'package:taskwarrior/app/services/tag_filter.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; @@ -13,7 +16,6 @@ import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; class FilterDrawer extends StatelessWidget { final Filters filters; final HomeController homeController; - const FilterDrawer( {required this.filters, required this.homeController, super.key}); @@ -165,81 +167,120 @@ class FilterDrawer extends StatelessWidget { const Divider( color: Color.fromARGB(0, 48, 46, 46), ), - Container( - key: homeController.projectsKey, - width: MediaQuery.of(context).size.width * 1, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: TaskWarriorColors.borderColor, + Visibility( + visible: !homeController.taskchampion.value, + child: Container( + key: homeController.projectsKey, + width: MediaQuery.of(context).size.width * 1, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: TaskWarriorColors.borderColor, + ), + ), + child: ProjectsColumn( + projects: filters.projects, + projectFilter: filters.projectFilter, + callback: filters.toggleProjectFilter, ), ), - child: ProjectsColumn( - projects: filters.projects, - projectFilter: filters.projectFilter, - callback: filters.toggleProjectFilter, + ), + Visibility( + visible: homeController.taskchampion.value, + child: FutureBuilder>( + future: homeController.getUniqueProjects(), + builder: (BuildContext context, + AsyncSnapshot> snapshot) { + if (snapshot.hasData) { + return Container( + key: homeController.projectsKeyTaskc, + width: MediaQuery.of(context).size.width * 1, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(8), + border: + Border.all(color: TaskWarriorColors.borderColor), + ), + child: ProjectColumnTaskc( + callback: filters.toggleProjectFilter, + projects: snapshot.data!, + projectFilter: filters.projectFilter, + ), + ); + } else { + return const Center( + child: Text('No projects available.')); + } + }, ), ), const Divider( color: Color.fromARGB(0, 48, 46, 46), ), - Container( - key: homeController.filterTagKey, - width: MediaQuery.of(context).size.width * 1, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: tileColor, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: TaskWarriorColors.borderColor), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 0.0), - child: Text( - SentenceManager( - currentLanguage: - homeController.selectedLanguage.value) - .sentences - .filterDrawerFilterTagBy, - // style: GoogleFonts.poppins( - // color: (AppSettings.isDarkMode - // ? TaskWarriorColors.kprimaryTextColor - // : TaskWarriorColors.kLightSecondaryTextColor), - // // - // fontSize: TaskWarriorFonts.fontSizeLarge), - //textAlign: TextAlign.right, - style: TextStyle( - fontFamily: FontFamily.poppins, - fontSize: TaskWarriorFonts.fontSizeMedium, - color: AppSettings.isDarkMode - ? TaskWarriorColors.kprimaryTextColor - : TaskWarriorColors.kLightSecondaryTextColor, + Visibility( + visible: !homeController.taskchampion.value, + child: Container( + key: homeController.filterTagKey, + width: MediaQuery.of(context).size.width * 1, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: tileColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: TaskWarriorColors.borderColor), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 0.0), + child: Text( + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .filterDrawerFilterTagBy, + // style: GoogleFonts.poppins( + // color: (AppSettings.isDarkMode + // ? TaskWarriorColors.kprimaryTextColor + // : TaskWarriorColors.kLightSecondaryTextColor), + // // + // fontSize: TaskWarriorFonts.fontSizeLarge), + //textAlign: TextAlign.right, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeMedium, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + ), ), ), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: TagFiltersWrap(filters.tagFilters), - ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), - ), - ], + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TagFiltersWrap(filters.tagFilters), + ), + const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), + ], + ), ), ), - const Divider( - color: Color.fromARGB(0, 48, 46, 46), + Visibility( + visible: !homeController.taskchampion.value, + child: const Divider( + color: Color.fromARGB(0, 48, 46, 46), + ), ), Container( key: homeController.sortByKey, 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 f1a91f89..f5aac06d 100644 --- a/lib/app/modules/home/views/home_page_app_bar.dart +++ b/lib/app/modules/home/views/home_page_app_bar.dart @@ -1,11 +1,14 @@ +// ignore_for_file: use_build_context_synchronously + import 'package:flutter/material.dart'; import 'package:get/get.dart'; - import 'package:taskwarrior/app/routes/app_pages.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/taskchampion/credentials_storage.dart'; +import 'package:taskwarrior/app/utils/taskchampion/taskchampion.dart'; import 'package:taskwarrior/app/utils/taskserver/taskserver.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; @@ -55,14 +58,71 @@ class HomePageAppBar extends StatelessWidget implements PreferredSizeWidget { builder: (context) => IconButton( key: controller.refreshKey, icon: Icon(Icons.refresh, color: TaskWarriorColors.white), - onPressed: () { - if (server != null || credentials != null) { - controller.synchronize(context, true); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Obx( - () => Text( + onPressed: () async { + if (controller.taskchampion.value) { + var c = await CredentialsStorage.getClientId(); + var e = await CredentialsStorage.getEncryptionSecret(); + if (c != null && e != null) { + try { + // List tasks = await fetchTasks(c, e); + // print( + // '///////////////////////////////////////////////////////////'); + // print(tasks.toList()); + // await updateTasksInDatabase(tasks); + // print('Tasks updated successfully'); + // controller.fetchTasksFromDB(); + controller.refreshTasks(c, e); + // Navigator.pushReplacement( + // context, + // PageRouteBuilder( + // pageBuilder: (context, animation1, animation2) => + // const HomeView(), + // transitionDuration: Duration.zero, + // reverseTransitionDuration: Duration.zero, + // )); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + content: Text( + SentenceManager( + currentLanguage: + controller.selectedLanguage.value) + .sentences + .homePageTaskWarriorNotConfigured, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + action: SnackBarAction( + label: SentenceManager( + currentLanguage: + controller.selectedLanguage.value) + .sentences + .homePageSetup, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ManageTaskChampionCreds(), + )).then((value) {}); + }, + textColor: TaskWarriorColors.purple, + ), + ), + ); + } + } else if (c == null || e == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + content: Text( SentenceManager( currentLanguage: controller.selectedLanguage.value) @@ -74,20 +134,58 @@ class HomePageAppBar extends StatelessWidget implements PreferredSizeWidget { : TaskWarriorColors.black, ), ), + action: SnackBarAction( + label: SentenceManager( + currentLanguage: + controller.selectedLanguage.value) + .sentences + .homePageSetup, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ManageTaskChampionCreds(), + )).then((value) {}); + }, + textColor: TaskWarriorColors.purple, + ), ), - action: SnackBarAction( - label: SentenceManager( - currentLanguage: - controller.selectedLanguage.value) - .sentences - .homePageSetup, - onPressed: () { - Get.toNamed(Routes.MANAGE_TASK_SERVER); - }, - textColor: TaskWarriorColors.purple, + ); + } + } else { + if (server != null || credentials != null) { + controller.synchronize(context, true); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Obx( + () => Text( + SentenceManager( + currentLanguage: + controller.selectedLanguage.value) + .sentences + .homePageTaskWarriorNotConfigured, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + action: SnackBarAction( + label: SentenceManager( + currentLanguage: + controller.selectedLanguage.value) + .sentences + .homePageSetup, + onPressed: () { + Get.toNamed(Routes.MANAGE_TASK_SERVER); + }, + textColor: TaskWarriorColors.purple, + ), ), - ), - ); + ); + } } }, ), diff --git a/lib/app/modules/home/views/home_page_body.dart b/lib/app/modules/home/views/home_page_body.dart index e8f5dc17..5f1a3700 100644 --- a/lib/app/modules/home/views/home_page_body.dart +++ b/lib/app/modules/home/views/home_page_body.dart @@ -2,6 +2,7 @@ import 'package:double_back_to_close_app/double_back_to_close_app.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/home/views/show_tasks.dart'; import 'package:taskwarrior/app/modules/home/views/tasks_builder.dart'; import 'package:taskwarrior/app/utils/constants/palette.dart'; @@ -20,7 +21,6 @@ class HomePageBody extends StatelessWidget { controller.showInAppTour(context); return DoubleBackToCloseApp( snackBar: const SnackBar(content: Text('Tap back again to exit')), - // ignore: avoid_unnecessary_containers child: Container( color: AppSettings.isDarkMode ? Palette.kToDark.shade200 @@ -88,23 +88,36 @@ class HomePageBody extends StatelessWidget { hintText: 'Search', ), ), - Expanded( - child: Scrollbar( - child: Obx( - () => TasksBuilder( - // darkmode: AppSettings.isDarkMode, - useDelayTask: controller.useDelayTask.value, - taskData: controller.searchedTasks, - pendingFilter: controller.pendingFilter.value, - waitingFilter: controller.waitingFilter.value, - searchVisible: controller.searchVisible.value, - selectedLanguage: controller.selectedLanguage.value, - scrollController: controller.scrollController, - showbtn: controller.showbtn.value, + Visibility( + visible: !controller.taskchampion.value, + child: Expanded( + child: Scrollbar( + child: Obx( + () => TasksBuilder( + // darkmode: AppSettings.isDarkMode, + useDelayTask: controller.useDelayTask.value, + taskData: controller.searchedTasks, + pendingFilter: controller.pendingFilter.value, + waitingFilter: controller.waitingFilter.value, + searchVisible: controller.searchVisible.value, + selectedLanguage: controller.selectedLanguage.value, + scrollController: controller.scrollController, + showbtn: controller.showbtn.value, + ), ), ), ), ), + Visibility( + visible: controller.taskchampion.value, + child: Expanded( + child: Scrollbar( + child: TaskViewBuilder( + pendingFilter: controller.pendingFilter.value, + selectedSort: controller.selectedSort.value, + project: controller.projectFilter.value, + ), + ))) ], ), ), diff --git a/lib/app/modules/home/views/home_page_floating_action_button.dart b/lib/app/modules/home/views/home_page_floating_action_button.dart index 238c5114..8a0c32aa 100644 --- a/lib/app/modules/home/views/home_page_floating_action_button.dart +++ b/lib/app/modules/home/views/home_page_floating_action_button.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:taskwarrior/app/modules/home/views/add_task_bottom_sheet.dart'; +import 'package:taskwarrior/app/modules/home/views/add_task_to_taskc_bottom_sheet.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; @@ -9,7 +10,7 @@ import '../controllers/home_controller.dart'; class HomePageFloatingActionButton extends StatelessWidget { final HomeController controller; - const HomePageFloatingActionButton({required this.controller,super.key}); + const HomePageFloatingActionButton({required this.controller, super.key}); @override Widget build(BuildContext context) { @@ -28,16 +29,27 @@ class HomePageFloatingActionButton extends StatelessWidget { : TaskWarriorColors.white, ), ), - onPressed: () => showDialog( - context: context, - builder: (context) => AddTaskBottomSheet( - homeController: controller, - ), - ).then((value) { - if (controller.isSyncNeeded.value && value != "cancel") { - controller.isNeededtoSyncOnStart(context); - } - }) + onPressed: () => (controller.taskchampion.value) + ? (showDialog( + context: context, + builder: (context) => AddTaskToTaskcBottomSheet( + homeController: controller, + ), + ).then((value) { + if (controller.isSyncNeeded.value && value != "cancel") { + controller.isNeededtoSyncOnStart(context); + } + })) + : (showDialog( + context: context, + builder: (context) => AddTaskBottomSheet( + homeController: controller, + ), + ).then((value) { + if (controller.isSyncNeeded.value && value != "cancel") { + controller.isNeededtoSyncOnStart(context); + } + })) // .then((value) { // // print(value); diff --git a/lib/app/modules/home/views/home_view.dart b/lib/app/modules/home/views/home_view.dart index 744a13c5..e2f62bb1 100644 --- a/lib/app/modules/home/views/home_view.dart +++ b/lib/app/modules/home/views/home_view.dart @@ -33,12 +33,6 @@ class HomeView extends GetView { controller.checkForSync(context); - // var taskData = controller.searchedTasks; - - // var pendingFilter = controller.pendingFilter; - // var waitingFilter = controller.waitingFilter; - // var pendingTags = controller.pendingTags; - return Obx( () => Scaffold( appBar: HomePageAppBar( @@ -50,7 +44,9 @@ class HomeView extends GetView { ? TaskWarriorColors.kprimaryBackgroundColor : TaskWarriorColors.kLightPrimaryBackgroundColor, drawer: NavDrawer(homeController: controller), - body: HomePageBody(controller: controller), + body: HomePageBody( + controller: controller, + ), endDrawer: Obx( () => FilterDrawer( filters: controller.getFilters(), diff --git a/lib/app/modules/home/views/nav_drawer.dart b/lib/app/modules/home/views/nav_drawer.dart index 7c28437c..b164c10e 100644 --- a/lib/app/modules/home/views/nav_drawer.dart +++ b/lib/app/modules/home/views/nav_drawer.dart @@ -6,11 +6,14 @@ import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; import 'package:taskwarrior/app/modules/home/views/home_page_nav_drawer_menu_item.dart'; import 'package:taskwarrior/app/modules/home/views/theme_clipper.dart'; +import 'package:taskwarrior/app/modules/reports/views/reports_view_taskc.dart'; import 'package:taskwarrior/app/routes/app_pages.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; import 'package:taskwarrior/app/utils/constants/utilites.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/taskchampion/taskchampion.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; class NavDrawer extends StatelessWidget { final HomeController homeController; @@ -41,9 +44,11 @@ class NavDrawer extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - SentenceManager(currentLanguage: homeController.selectedLanguage.value) - .sentences - .homePageMenu, + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value) + .sentences + .homePageMenu, style: TextStyle( fontSize: TaskWarriorFonts.fontSizeExtraLarge, fontWeight: TaskWarriorFonts.bold, @@ -82,26 +87,130 @@ class NavDrawer extends StatelessWidget { : TaskWarriorColors.kLightPrimaryBackgroundColor, height: Get.height * 0.03, ), - Obx( - () => NavDrawerMenuItem( - icon: Icons.person_rounded, + Visibility( + visible: homeController.taskchampion.value, + child: NavDrawerMenuItem( + icon: Icons.task_alt, text: SentenceManager( currentLanguage: homeController.selectedLanguage.value, - ).sentences.navDrawerProfile, + ).sentences.ccsyncCredentials, onTap: () { - Get.toNamed(Routes.PROFILE); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ManageTaskChampionCreds(), + ), + ); }, ), ), - Obx( - () => NavDrawerMenuItem( - icon: Icons.summarize, - text: SentenceManager( - currentLanguage: homeController.selectedLanguage.value, - ).sentences.navDrawerReports, - onTap: () { - Get.toNamed(Routes.REPORTS); - }, + Visibility( + visible: homeController.taskchampion.value, + child: NavDrawerMenuItem( + icon: Icons.delete, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.deleteTaskTitle, + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return Utils.showAlertDialog( + title: Text( + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value, + ).sentences.deleteTaskConfirmation, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + content: Text( + SentenceManager( + currentLanguage: + homeController.selectedLanguage.value, + ).sentences.deleteTaskWarning, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + actions: [ + TextButton( + child: Text( + 'Cancel', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + ), + TextButton( + child: Text( + 'Confirm', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + onPressed: () { + homeController.deleteAllTasksInDB(); + Navigator.of(context).pop(); // Close the dialog + }, + ), + ], + ); + }, + ); + }), + ), + Visibility( + visible: !homeController.taskchampion.value, + child: Obx( + () => NavDrawerMenuItem( + icon: Icons.person_rounded, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.navDrawerProfile, + onTap: () { + Get.toNamed(Routes.PROFILE); + }, + ), + ), + ), + Visibility( + visible: !homeController.taskchampion.value, + child: Obx( + () => NavDrawerMenuItem( + icon: Icons.summarize, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.navDrawerReports, + onTap: () { + Get.toNamed(Routes.REPORTS); + }, + ), + ), + ), + Visibility( + visible: homeController.taskchampion.value, + child: Obx( + () => NavDrawerMenuItem( + icon: Icons.summarize, + text: SentenceManager( + currentLanguage: homeController.selectedLanguage.value, + ).sentences.navDrawerReports, + onTap: () { + Get.to(() => ReportsHomeTaskc()); + }, + ), ), ), Obx( @@ -162,7 +271,8 @@ class NavDrawer extends StatelessWidget { builder: (BuildContext context) { return Utils.showAlertDialog( title: Text( - SentenceManager(currentLanguage: homeController.selectedLanguage.value) + SentenceManager( + currentLanguage: homeController.selectedLanguage.value) .sentences .homePageExitApp, style: TextStyle( @@ -172,7 +282,8 @@ class NavDrawer extends StatelessWidget { ), ), content: Text( - SentenceManager(currentLanguage: homeController.selectedLanguage.value) + SentenceManager( + currentLanguage: homeController.selectedLanguage.value) .sentences .homePageAreYouSureYouWantToExit, style: TextStyle( @@ -184,9 +295,10 @@ class NavDrawer extends StatelessWidget { actions: [ TextButton( child: Text( - SentenceManager(currentLanguage: homeController.selectedLanguage.value) - .sentences - .homePageCancel, + SentenceManager( + currentLanguage: homeController.selectedLanguage.value) + .sentences + .homePageCancel, style: TextStyle( color: AppSettings.isDarkMode ? TaskWarriorColors.white @@ -199,9 +311,10 @@ class NavDrawer extends StatelessWidget { ), TextButton( child: Text( - SentenceManager(currentLanguage: homeController.selectedLanguage.value) - .sentences - .homePageExit, + SentenceManager( + currentLanguage: homeController.selectedLanguage.value) + .sentences + .homePageExit, style: TextStyle( color: AppSettings.isDarkMode ? TaskWarriorColors.white diff --git a/lib/app/modules/home/views/project_column_home_page.dart b/lib/app/modules/home/views/project_column_home_page.dart index 57b90238..bfc1c665 100644 --- a/lib/app/modules/home/views/project_column_home_page.dart +++ b/lib/app/modules/home/views/project_column_home_page.dart @@ -32,13 +32,6 @@ class ProjectsColumn extends StatelessWidget { children: [ Text( "Project : ", - // style: GoogleFonts.poppins( - // fontWeight: TaskWarriorFonts.bold, - // fontSize: TaskWarriorFonts.fontSizeMedium, - // color: AppSettings.isDarkMode - // ? TaskWarriorColors.white - // : TaskWarriorColors.black, - // ), style: TextStyle( fontFamily: FontFamily.poppins, fontWeight: TaskWarriorFonts.bold, @@ -57,12 +50,6 @@ class ProjectsColumn extends StatelessWidget { children: [ Text( projectFilter == "" ? "Not selected" : projectFilter, - // style: GoogleFonts.poppins( - // fontSize: TaskWarriorFonts.fontSizeSmall, - // color: AppSettings.isDarkMode - // ? TaskWarriorColors.white - // : TaskWarriorColors.black, - // ), style: TextStyle( fontFamily: FontFamily.poppins, fontSize: TaskWarriorFonts.fontSizeSmall, @@ -84,13 +71,6 @@ class ProjectsColumn extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ Text("All Projects", - // style: GoogleFonts.poppins( - // fontWeight: TaskWarriorFonts.semiBold, - // fontSize: TaskWarriorFonts.fontSizeSmall, - // color: AppSettings.isDarkMode - // ? TaskWarriorColors.white - // : TaskWarriorColors.black, - // ), style: TextStyle( fontFamily: FontFamily.poppins, fontSize: TaskWarriorFonts.fontSizeSmall, @@ -115,13 +95,6 @@ class ProjectsColumn extends StatelessWidget { children: [ Text( "No Projects Found", - // style: GoogleFonts.poppins( - // color: AppSettings.isDarkMode - // ? TaskWarriorColors.white - // : TaskWarriorColors.black, - // fontSize: TaskWarriorFonts.fontSizeSmall, - // ), - style: TextStyle( fontFamily: FontFamily.poppins, fontSize: TaskWarriorFonts.fontSizeSmall, diff --git a/lib/app/modules/home/views/project_column_taskc.dart b/lib/app/modules/home/views/project_column_taskc.dart new file mode 100644 index 00000000..134a260e --- /dev/null +++ b/lib/app/modules/home/views/project_column_taskc.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/gen/fonts.gen.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; // Import your necessary dependencies + +class ProjectColumnTaskc extends StatelessWidget { + const ProjectColumnTaskc({ + super.key, + required this.projects, + required this.projectFilter, + required this.callback, + }); + + final List projects; + final String projectFilter; + final void Function(String) callback; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "Project : ", + style: TextStyle( + fontFamily: FontFamily.poppins, + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Text( + projectFilter.isEmpty ? "Not selected" : projectFilter, + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ), + ), + ], + ), + ), + ProjectTileTaskc( + project: 'All Projects', + projectFilter: projectFilter, + callback: callback), + if (projects.isNotEmpty) + ...projects.map((entry) => ProjectTileTaskc( + project: entry, + projectFilter: projectFilter, + callback: callback, + )) + else + Column( + children: [ + Text( + "No Projects Found", + style: TextStyle( + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + const SizedBox( + height: 10, + ), + ], + ), + ], + ); + } +} + +class ProjectTileTaskc extends StatelessWidget { + const ProjectTileTaskc({ + super.key, + required this.project, + required this.projectFilter, + required this.callback, + }); + + final String project; + final String projectFilter; + final void Function(String) callback; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => callback(project), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Radio( + value: project, + groupValue: projectFilter, + onChanged: (_) => callback(project), + ), + Text( + project, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + const Spacer(), + ], + ), + ); + } +} diff --git a/lib/app/modules/home/views/show_details.dart b/lib/app/modules/home/views/show_details.dart new file mode 100644 index 00000000..038daab1 --- /dev/null +++ b/lib/app/modules/home/views/show_details.dart @@ -0,0 +1,439 @@ +// 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/api_service.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class TaskDetails extends StatefulWidget { + final Tasks 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) { + 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: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + appBar: AppBar( + foregroundColor: TaskWarriorColors.lightGrey, + backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, + title: Text( + 'Task: ${widget.task.description}', + style: GoogleFonts.poppins(color: TaskWarriorColors.white), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: ListView( + children: [ + _buildEditableDetail('Description:', description, (value) { + setState(() { + description = value; + hasChanges = true; + }); + }), + _buildEditableDetail('Project:', project, (value) { + setState(() { + project = value; + hasChanges = true; + }); + }), + _buildSelectableDetail( + 'Status:', status, ['pending', 'completed'], (value) { + setState(() { + status = value; + hasChanges = true; + }); + }), + _buildSelectableDetail('Priority:', priority, ['H', 'M', 'L'], + (value) { + setState(() { + priority = value; + hasChanges = true; + }); + }), + _buildDatePickerDetail('Due:', due, (value) { + setState(() { + due = value; + hasChanges = true; + }); + }), + _buildDetail('UUID:', widget.task.uuid!), + _buildDetail('Urgency:', widget.task.urgency.toString()), + _buildDetail('End:', _buildDate(widget.task.end)), + _buildDetail('Entry:', _buildDate(widget.task.entry)), + _buildDetail('Modified:', _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).copyWith( + colorScheme: AppSettings.isDarkMode + ? ColorScheme.dark( + primary: TaskWarriorColors.white, + onPrimary: TaskWarriorColors.black, + surface: TaskWarriorColors.black, + onSurface: TaskWarriorColors.white, + ) + : ColorScheme.light( + primary: TaskWarriorColors.black, + onPrimary: TaskWarriorColors.white, + surface: TaskWarriorColors.white, + onSurface: TaskWarriorColors.black, + ), + ), + 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) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + 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: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: 18, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightSecondaryTextColor, + ), + ), + ), + ], + ), + ); + } + + Future _showEditDialog( + BuildContext context, String label, String initialValue) async { + final TextEditingController controller = + TextEditingController(text: initialValue); + return await showDialog( + context: context, + builder: (context) { + return Utils.showAlertDialog( + title: Text( + 'Edit $label', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + ), + content: TextField( + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + controller: controller, + decoration: InputDecoration( + hintText: 'Enter new $label', + hintStyle: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Cancel', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + ), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(controller.text); + }, + child: Text( + 'Save', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + ), + ), + ], + ); + }, + ); + } + + Future _showSelectDialog(BuildContext context, String label, + String initialValue, List options) async { + return await showDialog( + context: context, + builder: (context) { + return Utils.showAlertDialog( + title: Text( + 'Select $label', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: options.map((option) { + return RadioListTile( + title: Text( + option, + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + ), + 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 { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return Utils.showAlertDialog( + title: Text( + 'Unsaved Changes', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + ), + content: Text( + 'You have unsaved changes. What would you like to do?', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(UnsavedChangesAction.cancel); + }, + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(UnsavedChangesAction.discard); + }, + child: const Text('Don\'t Save'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(UnsavedChangesAction.save); + }, + child: const Text('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!, + ); + } +} + +enum UnsavedChangesAction { save, discard, cancel } diff --git a/lib/app/modules/home/views/show_tasks.dart b/lib/app/modules/home/views/show_tasks.dart new file mode 100644 index 00000000..2b45bdc5 --- /dev/null +++ b/lib/app/modules/home/views/show_tasks.dart @@ -0,0 +1,261 @@ +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/constants/palette.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class TaskViewBuilder extends StatelessWidget { + const TaskViewBuilder({ + super.key, + this.project, + required this.pendingFilter, + required this.selectedSort, + }); + + final String selectedSort; + final bool pendingFilter; + final String? project; + + @override + Widget build(BuildContext context) { + final HomeController taskController = Get.find(); + + return Obx(() { + 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); + } + + // Apply other filters and sorting + tasks.sort((a, b) => b.id.compareTo(a.id)); + + tasks = tasks.where((task) { + if (pendingFilter) { + return task.status == 'pending'; + } else { + return task.status == 'completed'; + } + }).toList(); + // Apply sorting based on selectedSort + tasks.sort((a, b) { + switch (selectedSort) { + case 'Created+': + return a.entry.compareTo(b.entry); + case 'Created-': + return b.entry.compareTo(a.entry); + case 'Modified+': + return a.modified!.compareTo(b.modified!); + case 'Modified-': + return b.modified!.compareTo(a.modified!); + case 'Due till+': + return a.due!.compareTo(b.due!); + case 'Due till-': + return b.due!.compareTo(a.due!); + case 'Priority-': + return b.priority!.compareTo(a.priority!); + case 'Priority+': + return a.priority!.compareTo(b.priority!); + case 'Project+': + return a.project!.compareTo(b.project!); + case 'Project-': + return b.project!.compareTo(a.project!); + case 'Urgency-': + return b.urgency!.compareTo(a.urgency!); + case 'Urgency+': + return a.urgency!.compareTo(b.urgency!); + default: + return 0; + } + }); + + return Scaffold( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + body: tasks.isEmpty + ? Padding( + padding: const EdgeInsets.all(16.0), + child: Center( + child: Text( + 'Click on the bottom right button to start adding tasks', + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: TaskWarriorFonts.fontSizeLarge, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kLightPrimaryBackgroundColor + : TaskWarriorColors.kprimaryBackgroundColor, + ), + ), + ), + ) + : ListView.builder( + shrinkWrap: true, + padding: EdgeInsets.only( + top: 4, + left: 2, + right: 2, + bottom: MediaQuery.of(context).size.height * 0.1, + ), + itemCount: tasks.length, + itemBuilder: (context, index) { + Tasks task = tasks[index]; + return Slidable( + startActionPane: ActionPane( + motion: const BehindMotion(), + children: [ + SlidableAction( + onPressed: (context) { + _markTaskAsCompleted(task.uuid!); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Task Marked As Completed. Refresh to view changes!', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors + .kLightPrimaryTextColor, + ), + ), + ), + ); + }, + icon: Icons.done, + label: "COMPLETE", + backgroundColor: TaskWarriorColors.green, + ), + ], + ), + endActionPane: ActionPane( + motion: const DrawerMotion(), + children: [ + SlidableAction( + onPressed: (context) { + _markTaskAsDeleted(task.uuid!); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Task Marked As Deleted. Refresh to view changes!', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors + .kLightPrimaryTextColor, + ), + ), + ), + ); + }, + icon: Icons.delete, + label: "DELETE", + backgroundColor: TaskWarriorColors.red, + ), + ], + ), + child: Card( + color: AppSettings.isDarkMode + ? Palette.kToDark + : TaskWarriorColors.white, + child: InkWell( + splashColor: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.borderColor, + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TaskDetails(task: task), + ), + ), + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: AppSettings.isDarkMode + ? TaskWarriorColors.borderColor + : TaskWarriorColors.black, + ), + color: AppSettings.isDarkMode + ? Palette.kToDark + : TaskWarriorColors.white, + borderRadius: BorderRadius.circular(8.0), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 4.0, + offset: Offset(0, 2), + ), + ], + ), + child: ListTile( + leading: CircleAvatar( + backgroundColor: + _getPriorityColor(task.priority!), + radius: 8, + ), + title: Text( + task.description, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors + .kLightDialogBackGroundColor + : TaskWarriorColors.kprimaryBackgroundColor, + ), + ), + subtitle: Text( + 'Urgency: ${task.urgency!.floorToDouble()} | Status: ${task.status}', + style: GoogleFonts.poppins( + color: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryTextColor + : TaskWarriorColors + .kLightSecondaryTextColor, + ), + ), + ), + ), + ), + ), + ); + }, + ), + ); + }); + } + + void _markTaskAsCompleted(String uuid) async { + TaskDatabase taskDatabase = TaskDatabase(); + await taskDatabase.open(); + taskDatabase.markTaskAsCompleted(uuid); + completeTask('email', uuid); + } + + void _markTaskAsDeleted(String uuid) async { + TaskDatabase taskDatabase = TaskDatabase(); + await taskDatabase.open(); + taskDatabase.markTaskAsDeleted(uuid); + deleteTask('email', uuid); + } + + Color _getPriorityColor(String priority) { + switch (priority) { + case 'H': + return Colors.red; + case 'M': + return Colors.yellow; + case 'L': + return Colors.green; + default: + return Colors.grey; + } + } +} diff --git a/lib/app/modules/home/views/tas_list_item.dart b/lib/app/modules/home/views/tas_list_item.dart index cbe7e14f..38911a47 100644 --- a/lib/app/modules/home/views/tas_list_item.dart +++ b/lib/app/modules/home/views/tas_list_item.dart @@ -32,6 +32,7 @@ class TaskListItem extends StatelessWidget { @override Widget build(BuildContext context) { + // ignore: unused_element void saveChanges() async { var now = DateTime.now().toUtc(); modify.save( diff --git a/lib/app/modules/manageTaskServer/views/manage_task_server_page_app_bar.dart b/lib/app/modules/manageTaskServer/views/manage_task_server_page_app_bar.dart index e65022de..091aa444 100644 --- a/lib/app/modules/manageTaskServer/views/manage_task_server_page_app_bar.dart +++ b/lib/app/modules/manageTaskServer/views/manage_task_server_page_app_bar.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use_build_context_synchronously + import 'dart:math'; import 'package:flutter/foundation.dart'; diff --git a/lib/app/modules/onboarding/views/onboarding_page_start_button.dart b/lib/app/modules/onboarding/views/onboarding_page_start_button.dart index 972a1314..b7b2cbdc 100644 --- a/lib/app/modules/onboarding/views/onboarding_page_start_button.dart +++ b/lib/app/modules/onboarding/views/onboarding_page_start_button.dart @@ -16,11 +16,7 @@ class OnboardingPageStartButton extends StatelessWidget { child: ElevatedButton( onPressed: () { controller.markOnboardingAsCompleted(); - // Navigator.of(context).pushAndRemoveUntil( - // MaterialPageRoute(builder: (context) => const HomeView()), - // (Route route) => false, - // ); - Get.toNamed(Routes.HOME); + Get.offNamed(Routes.HOME); }, style: ElevatedButton.styleFrom( backgroundColor: TaskWarriorColors.black, diff --git a/lib/app/modules/reports/controllers/reports_controller.dart b/lib/app/modules/reports/controllers/reports_controller.dart index 12c3dce2..e33e2d66 100644 --- a/lib/app/modules/reports/controllers/reports_controller.dart +++ b/lib/app/modules/reports/controllers/reports_controller.dart @@ -1,8 +1,11 @@ +// ignore_for_file: prefer_typing_uninitialized_variables + import 'dart:io'; import 'package:flutter/material.dart'; import 'package:get/get.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'; @@ -22,7 +25,7 @@ class ReportsController extends GetxController final GlobalKey daily = GlobalKey(); final GlobalKey weekly = GlobalKey(); final GlobalKey monthly = GlobalKey(); - + late TaskDatabase taskDatabase; var isSaved = false.obs; late TutorialCoachMark tutorialCoachMark; @@ -342,6 +345,11 @@ class ReportsController extends GetxController }); } + Future> fetchTasks() async { + await taskDatabase.open(); + return await taskDatabase.fetchTasksFromDatabase(); + } + // monthly report late TooltipBehavior monthlyBurndownTooltipBehaviour; RxMap> monthlyInfo = diff --git a/lib/app/modules/reports/views/burn_down_daily_taskc.dart b/lib/app/modules/reports/views/burn_down_daily_taskc.dart new file mode 100644 index 00000000..ed9e946c --- /dev/null +++ b/lib/app/modules/reports/views/burn_down_daily_taskc.dart @@ -0,0 +1,174 @@ +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/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class BurnDownDailyTaskc extends StatelessWidget { + BurnDownDailyTaskc({super.key}); + + final TooltipBehavior _dailyBurndownTooltipBehaviour = TooltipBehavior( + enable: true, + builder: (dynamic data, dynamic point, dynamic series, int pointIndex, + int seriesIndex) { + final String date = data.x; + final int pendingCount = data.y1; + final int completedCount = data.y2; + + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Date: $date', + style: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + ), + ), + Text('Pending: $pendingCount'), + Text('Completed: $completedCount'), + ], + ), + ); + }, + ); + + Future>> fetchDailyInfo() async { + TaskDatabase taskDatabase = TaskDatabase(); + await taskDatabase.open(); + List tasks = await taskDatabase.fetchTasksFromDatabase(); + return _processData(tasks); + } + + Map> _processData(List tasks) { + Map> dailyInfo = {}; + + // Sort tasks by entry date in ascending order + tasks.sort((a, b) => a.entry.compareTo(b.entry)); + + for (var task in tasks) { + final String date = Utils.formatDate(DateTime.parse(task.entry), 'MM-dd'); + + if (dailyInfo.containsKey(date)) { + if (task.status == 'pending') { + dailyInfo[date]!['pending'] = (dailyInfo[date]!['pending'] ?? 0) + 1; + } else if (task.status == 'completed') { + dailyInfo[date]!['completed'] = + (dailyInfo[date]!['completed'] ?? 0) + 1; + } + } else { + dailyInfo[date] = { + 'pending': task.status == 'pending' ? 1 : 0, + 'completed': task.status == 'completed' ? 1 : 0, + }; + } + } + + return dailyInfo; + } + + @override + Widget build(BuildContext context) { + double height = MediaQuery.of(context).size.height; // Screen height + + return FutureBuilder>>( + future: fetchDailyInfo(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } + + Map> dailyInfo = snapshot.data ?? {}; + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: height * 0.6, + child: SfCartesianChart( + primaryXAxis: CategoryAxis( + title: AxisTitle( + text: 'Day - Month', + textStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + ), + primaryYAxis: NumericAxis( + title: AxisTitle( + text: 'Tasks', + textStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + ), + ), + ), + tooltipBehavior: _dailyBurndownTooltipBehaviour, + series: [ + StackedColumnSeries( + groupName: 'Group A', + enableTooltip: true, + color: TaskWarriorColors.green, + dataSource: dailyInfo.entries + .map((entry) => ChartData( + entry.key, + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y2, + name: 'Completed', + ), + StackedColumnSeries( + groupName: 'Group A', + color: TaskWarriorColors.yellow, + enableTooltip: true, + dataSource: dailyInfo.entries + .map((entry) => ChartData( + entry.key, + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y1, + name: 'Pending', + ), + ], + ), + ), + ), + const CommonChartIndicator( + title: 'Daily Burndown Chart', + ), + ], + ); + }, + ); + } +} diff --git a/lib/app/modules/reports/views/burn_down_monthly_taskc.dart b/lib/app/modules/reports/views/burn_down_monthly_taskc.dart new file mode 100644 index 00000000..2d1477f9 --- /dev/null +++ b/lib/app/modules/reports/views/burn_down_monthly_taskc.dart @@ -0,0 +1,180 @@ +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/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class BurnDownMonthlyTaskc extends StatelessWidget { + BurnDownMonthlyTaskc({super.key}); + + final _monthlyBurndownTooltipBehaviour = TooltipBehavior( + enable: true, + builder: (dynamic data, dynamic point, dynamic series, int pointIndex, + int seriesIndex) { + final String monthYear = data.x; + final int pendingCount = data.y1; + final int completedCount = data.y2; + + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Month-Year: $monthYear', + style: const TextStyle( + fontWeight: TaskWarriorFonts.bold, + ), + ), + Text( + 'Pending: $pendingCount', + ), + Text( + 'Completed: $completedCount', + ), + ], + ), + ); + }, + ); + + Future>> fetchMonthlyInfo() async { + TaskDatabase taskDatabase = TaskDatabase(); + await taskDatabase.open(); + List tasks = await taskDatabase.fetchTasksFromDatabase(); + return sortBurnDownMonthly(tasks); + } + + Map> sortBurnDownMonthly(List allData) { + Map> monthlyInfo = {}; + + allData.sort((a, b) => a.entry.compareTo(b.entry)); + + for (int i = 0; i < allData.length; i++) { + final DateTime entryDate = DateTime.parse(allData[i].entry); + final String monthYear = + '${Utils.getMonthName(entryDate.month)} ${entryDate.year}'; + + if (monthlyInfo.containsKey(monthYear)) { + if (allData[i].status == 'pending') { + monthlyInfo[monthYear]!['pending'] = + (monthlyInfo[monthYear]!['pending'] ?? 0) + 1; + } else if (allData[i].status == 'completed') { + monthlyInfo[monthYear]!['completed'] = + (monthlyInfo[monthYear]!['completed'] ?? 0) + 1; + } + } else { + monthlyInfo[monthYear] = { + 'pending': allData[i].status == 'pending' ? 1 : 0, + 'completed': allData[i].status == 'completed' ? 1 : 0, + }; + } + } + + debugPrint("monthlyInfo: $monthlyInfo"); + return monthlyInfo; + } + + @override + Widget build(BuildContext context) { + final double height = MediaQuery.of(context).size.height; + + return FutureBuilder>>( + future: fetchMonthlyInfo(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } + + Map> monthlyInfo = snapshot.data ?? {}; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: height * 0.6, + child: SfCartesianChart( + primaryXAxis: CategoryAxis( + title: AxisTitle( + text: 'Month - Year', + textStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + ), + ), + ), + primaryYAxis: NumericAxis( + title: AxisTitle( + text: 'Tasks', + textStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + ), + ), + ), + tooltipBehavior: _monthlyBurndownTooltipBehaviour, + series: [ + StackedColumnSeries( + groupName: 'Group A', + enableTooltip: true, + color: TaskWarriorColors.green, + dataSource: monthlyInfo.entries + .map((entry) => ChartData( + entry.key, + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y2, + name: 'Completed', + ), + StackedColumnSeries( + groupName: 'Group A', + color: TaskWarriorColors.yellow, + enableTooltip: true, + dataSource: monthlyInfo.entries + .map((entry) => ChartData( + entry.key, + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y1, + name: 'Pending', + ), + ], + ), + ), + ), + const CommonChartIndicator( + title: 'Monthly Burndown Chart', + ), + ], + ); + }, + ); + } +} diff --git a/lib/app/modules/reports/views/burn_down_weekly_taskc.dart b/lib/app/modules/reports/views/burn_down_weekly_taskc.dart new file mode 100644 index 00000000..6d9f5821 --- /dev/null +++ b/lib/app/modules/reports/views/burn_down_weekly_taskc.dart @@ -0,0 +1,190 @@ +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/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/constants/utilites.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; + +class BurnDownWeeklyTask extends StatelessWidget { + BurnDownWeeklyTask({super.key}); + + final TooltipBehavior _weeklyBurndownTooltipBehaviour = TooltipBehavior( + enable: true, + builder: (dynamic data, dynamic point, dynamic series, int pointIndex, + int seriesIndex) { + final String weekNumber = data.x; + final int pendingCount = data.y1; + final int completedCount = data.y2; + + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + weekNumber, + style: const TextStyle( + fontWeight: TaskWarriorFonts.bold, + ), + ), + Text( + 'Pending: $pendingCount', + ), + Text( + 'Completed: $completedCount', + ), + ], + ), + ); + }, + ); + + Future>> fetchWeeklyInfo() async { + TaskDatabase taskDatabase = TaskDatabase(); + await taskDatabase.open(); + List tasks = await taskDatabase.fetchTasksFromDatabase(); + return sortBurnDownWeekly(tasks); + } + + Map> sortBurnDownWeekly(List allData) { + // Initialize weeklyInfo map + Map> weeklyInfo = {}; + + // Sort allData by entry date in ascending order + allData.sort((a, b) => a.entry.compareTo(b.entry)); + + ///loop through allData and get the week number + for (int i = 0; i < allData.length; i++) { + final int weekNumber = + Utils.getWeekNumbertoInt(DateTime.parse(allData[i].entry)); + + ///check if weeklyInfo contains the week number + if (weeklyInfo.containsKey(weekNumber.toString())) { + ///check if the status is pending or completed + if (allData[i].status == 'pending') { + ///if the status is pending then add 1 to the pending count + weeklyInfo[weekNumber.toString()]!['pending'] = + (weeklyInfo[weekNumber.toString()]!['pending'] ?? 0) + 1; + } else if (allData[i].status == 'completed') { + ///if the status is completed then add 1 to the completed count + weeklyInfo[weekNumber.toString()]!['completed'] = + (weeklyInfo[weekNumber.toString()]!['completed'] ?? 0) + 1; + } + } else { + ///if weeklyInfo does not contain the week number + // ignore: collection_methods_unrelated_type + weeklyInfo[weekNumber.toString()] = { + 'pending': allData[i].status == 'pending' ? 1 : 0, + 'completed': allData[i].status == 'completed' ? 1 : 0, + }; + } + } + + debugPrint("weeklyInfo $weeklyInfo"); + return weeklyInfo; + } + + @override + Widget build(BuildContext context) { + double height = MediaQuery.of(context).size.height; // Screen height + return FutureBuilder>>( + future: fetchWeeklyInfo(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } + + Map> weeklyInfo = snapshot.data ?? {}; + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: SizedBox( + height: height * 0.6, + child: SfCartesianChart( + primaryXAxis: CategoryAxis( + title: AxisTitle( + text: 'Weeks - Year', + textStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + ), + ), + ), + primaryYAxis: NumericAxis( + title: AxisTitle( + text: 'Tasks', + textStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.bold, + color: AppSettings.isDarkMode + ? Colors.white + : Colors.black, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + ), + ), + tooltipBehavior: _weeklyBurndownTooltipBehaviour, + series: [ + ///this is the completed tasks + StackedColumnSeries( + groupName: 'Group A', + enableTooltip: true, + color: TaskWarriorColors.green, + dataSource: weeklyInfo.entries + .map((entry) => ChartData( + 'Week ${entry.key}', + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y2, + name: 'Completed', + ), + + ///this is the pending tasks + StackedColumnSeries( + groupName: 'Group A', + color: TaskWarriorColors.yellow, + enableTooltip: true, + dataSource: weeklyInfo.entries + .map((entry) => ChartData( + 'Week ${entry.key}', + entry.value['pending'] ?? 0, + entry.value['completed'] ?? 0, + )) + .toList(), + xValueMapper: (ChartData data, _) => data.x, + yValueMapper: (ChartData data, _) => data.y1, + name: 'Pending', + ), + ], + ), + ), + ), + const CommonChartIndicator( + title: 'Weekly Burndown Chart', + ), + ], + ); + }); + } +} diff --git a/lib/app/modules/reports/views/reports_view_taskc.dart b/lib/app/modules/reports/views/reports_view_taskc.dart new file mode 100644 index 00000000..202f9eb5 --- /dev/null +++ b/lib/app/modules/reports/views/reports_view_taskc.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/modules/reports/controllers/reports_controller.dart'; +import 'package:taskwarrior/app/modules/reports/views/burn_down_daily_taskc.dart'; +import 'package:taskwarrior/app/modules/reports/views/burn_down_monthly_taskc.dart'; +import 'package:taskwarrior/app/modules/reports/views/burn_down_weekly_taskc.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; +import 'package:taskwarrior/api_service.dart'; + +class ReportsHomeTaskc extends StatelessWidget { + final ReportsController reportsController = Get.put(ReportsController()); + final TaskDatabase taskDatabase = TaskDatabase(); + + ReportsHomeTaskc({super.key}); + + Future> fetchTasks() async { + await taskDatabase.open(); + return await taskDatabase.fetchTasksFromDatabase(); + } + + @override + Widget build(BuildContext context) { + double height = MediaQuery.of(context).size.height; + reportsController.initReportsTour(); + reportsController.showReportsTour(context); + return FutureBuilder>( + future: fetchTasks(), + builder: (context, snapshot) { + List allTasks = snapshot.data ?? []; + + return Scaffold( + appBar: AppBar( + backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, + title: Text( + 'Reports', + style: GoogleFonts.poppins(color: TaskWarriorColors.white), + ), + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon( + Icons.chevron_left, + color: TaskWarriorColors.white, + ), + ), + bottom: PreferredSize( + preferredSize: Size.fromHeight(height * 0.1), + child: TabBar( + controller: reportsController.tabController, + labelColor: TaskWarriorColors.white, + labelStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.medium, + fontSize: TaskWarriorFonts.fontSizeSmall, + ), + unselectedLabelStyle: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.light, + ), + onTap: (value) { + reportsController.selectedIndex.value = value; + }, + tabs: [ + Tab( + key: reportsController.daily, + icon: const Icon(Icons.schedule), + text: 'Daily', + iconMargin: const EdgeInsets.only(bottom: 0.0), + ), + Tab( + key: reportsController.weekly, + icon: const Icon(Icons.today), + text: 'Weekly', + iconMargin: const EdgeInsets.only(bottom: 0.0), + ), + Tab( + key: reportsController.monthly, + icon: const Icon(Icons.date_range), + text: 'Monthly', + iconMargin: const EdgeInsets.only(bottom: 0.0), + ), + ], + ), + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.white, + body: snapshot.connectionState == ConnectionState.waiting + ? const Center(child: CircularProgressIndicator()) + : allTasks.isEmpty + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.heart_broken, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'No Task found', + style: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.medium, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Add a task to see reports', + style: GoogleFonts.poppins( + fontWeight: TaskWarriorFonts.light, + fontSize: TaskWarriorFonts.fontSizeSmall, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ], + ) + : Obx( + () => IndexedStack( + index: reportsController.selectedIndex.value, + children: [ + BurnDownDailyTaskc(), + BurnDownWeeklyTask(), + BurnDownMonthlyTaskc(), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/app/modules/settings/controllers/settings_controller.dart b/lib/app/modules/settings/controllers/settings_controller.dart index 37928078..ed6e8c96 100644 --- a/lib/app/modules/settings/controllers/settings_controller.dart +++ b/lib/app/modules/settings/controllers/settings_controller.dart @@ -1,3 +1,5 @@ +// ignore_for_file: depend_on_referenced_packages + import 'dart:io'; import 'package:file_picker/file_picker.dart'; @@ -163,6 +165,7 @@ class SettingsController extends GetxController { RxBool isSyncOnTaskCreateActivel = false.obs; RxBool delaytask = false.obs; RxBool change24hr = false.obs; + RxBool taskchampion = false.obs; RxBool isDarkModeOn = false.obs; void initDarkMode() { @@ -177,6 +180,7 @@ class SettingsController extends GetxController { prefs.getBool('sync-OnTaskCreate') ?? false; delaytask.value = prefs.getBool('delaytask') ?? false; change24hr.value = prefs.getBool('24hourformate') ?? false; + taskchampion.value = prefs.getBool('taskc') ?? false; initDarkMode(); baseDirectory.value = await getBaseDirectory(); super.onInit(); diff --git a/lib/app/modules/settings/views/settings_page_body.dart b/lib/app/modules/settings/views/settings_page_body.dart index 5a3e45f9..4e956ebd 100644 --- a/lib/app/modules/settings/views/settings_page_body.dart +++ b/lib/app/modules/settings/views/settings_page_body.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/modules/settings/views/settings_page_taskchampion.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; @@ -23,7 +24,7 @@ import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; class SettingsPageBody extends StatelessWidget { final SettingsController controller; - + const SettingsPageBody({required this.controller, super.key}); @override @@ -116,6 +117,20 @@ class SettingsPageBody extends StatelessWidget { ), ), const Divider(), + SettingsPageListTile( + title: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .taskchampionTileTitle, + subTitle: SentenceManager( + currentLanguage: controller.selectedLanguage.value) + .sentences + .taskchampionTileDescription, + trailing: SettingsPageTaskchampionTileListTileTrailing( + controller: controller, + ), + ), + const Divider(), SettingsPageListTile( title: SentenceManager( currentLanguage: controller.selectedLanguage.value) diff --git a/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart b/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart index 2576722b..b5afb17c 100644 --- a/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart +++ b/lib/app/modules/settings/views/settings_page_select_directory_list_tile.dart @@ -1,3 +1,5 @@ +// ignore_for_file: use_build_context_synchronously + import 'dart:io'; import 'package:flutter/material.dart'; diff --git a/lib/app/modules/settings/views/settings_page_taskchampion.dart b/lib/app/modules/settings/views/settings_page_taskchampion.dart new file mode 100644 index 00000000..2582cd00 --- /dev/null +++ b/lib/app/modules/settings/views/settings_page_taskchampion.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; + +import '../controllers/settings_controller.dart'; + +class SettingsPageTaskchampionTileListTileTrailing extends StatelessWidget { + final SettingsController controller; + const SettingsPageTaskchampionTileListTileTrailing( + {required this.controller, super.key}); + + @override + Widget build(BuildContext context) { + return Obx( + () => Switch( + value: controller.taskchampion.value, + onChanged: (bool value) async { + controller.taskchampion.value = value; + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool('taskc', value); + Get.find().taskchampion.value = value; + }, + ), + ); + } +} diff --git a/lib/app/modules/splash/bindings/splash_binding.dart b/lib/app/modules/splash/bindings/splash_binding.dart index fcb9b7ca..7252c547 100644 --- a/lib/app/modules/splash/bindings/splash_binding.dart +++ b/lib/app/modules/splash/bindings/splash_binding.dart @@ -7,6 +7,7 @@ class SplashBinding extends Bindings { void dependencies() { Get.put( SplashController(), + permanent: true, ); } } diff --git a/lib/app/modules/splash/controllers/splash_controller.dart b/lib/app/modules/splash/controllers/splash_controller.dart index 05d7d5a0..cd6fcf21 100644 --- a/lib/app/modules/splash/controllers/splash_controller.dart +++ b/lib/app/modules/splash/controllers/splash_controller.dart @@ -1,3 +1,5 @@ +// ignore_for_file: depend_on_referenced_packages + import 'dart:io'; import 'package:get/get.dart'; @@ -95,9 +97,9 @@ class SplashController extends GetxController { void sendToNextPage() async { await checkOnboardingStatus(); if (hasCompletedOnboarding.value) { - Get.toNamed(Routes.HOME); + Get.offNamed(Routes.HOME); } else { - Get.toNamed(Routes.ONBOARDING); + Get.offNamed(Routes.ONBOARDING); } } } diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index ec084a3e..f6925604 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + import 'package:get/get.dart'; import '../modules/about/bindings/about_binding.dart'; diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 5a1af88c..5cf53cc3 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -1,3 +1,5 @@ +// ignore_for_file: constant_identifier_names + part of 'app_pages.dart'; // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart diff --git a/lib/app/tour/filter_drawer_tour.dart b/lib/app/tour/filter_drawer_tour.dart index 67eb4655..4ddcfaaf 100644 --- a/lib/app/tour/filter_drawer_tour.dart +++ b/lib/app/tour/filter_drawer_tour.dart @@ -6,6 +6,7 @@ import 'package:tutorial_coach_mark/tutorial_coach_mark.dart'; List filterDrawer({ required GlobalKey statusKey, required GlobalKey projectsKey, + required GlobalKey projectsKeyTaskc, required GlobalKey filterTagKey, required GlobalKey sortByKey, }) { @@ -77,6 +78,39 @@ List filterDrawer({ ), ); + // projectsKeyTaskc + targets.add( + TargetFocus( + keyTarget: projectsKeyTaskc, + alignSkip: Alignment.topRight, + radius: 10, + shape: ShapeLightFocus.RRect, + contents: [ + TargetContent( + align: ContentAlign.top, + builder: (context, controller) { + return Container( + alignment: Alignment.center, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Filter tasks based on the projects", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + color: TaskWarriorColors.white, + ), + ), + ], + ), + ); + }, + ), + ], + ), + ); + // filterTagByKey targets.add( TargetFocus( diff --git a/lib/app/utils/language/english_sentences.dart b/lib/app/utils/language/english_sentences.dart index e4334550..4a799845 100644 --- a/lib/app/utils/language/english_sentences.dart +++ b/lib/app/utils/language/english_sentences.dart @@ -187,4 +187,23 @@ class EnglishSentences extends Sentences { String get reportsPageNoTasksFound => 'No Tasks Found'; @override String get reportsPageAddTasksToSeeReports => 'Add Tasks To See Reports'; + + @override + String get taskchampionTileDescription => + 'Switch to TaskWarrior sync with CCSync or Taskchampion Sync Server'; + @override + String get taskchampionTileTitle => 'Taskchampion sync'; + + @override + String get ccsyncCredentials => 'CCync credentials'; + + @override + String get deleteTaskConfirmation => 'Delete Tasks'; + + @override + String get deleteTaskTitle => 'Delete All Tasks?'; + + @override + String get deleteTaskWarning => + 'The action is irreversible and will delete all the tasks that are stored locally.'; } diff --git a/lib/app/utils/language/hindi_sentences.dart b/lib/app/utils/language/hindi_sentences.dart index d60f036a..99a5cadb 100644 --- a/lib/app/utils/language/hindi_sentences.dart +++ b/lib/app/utils/language/hindi_sentences.dart @@ -188,4 +188,24 @@ class HindiSentences extends Sentences { @override String get reportsPageAddTasksToSeeReports => 'रिपोर्ट देखने के लिए कार्य जोड़ें'; + + @override + String get taskchampionTileDescription => + 'CCSync या Taskchampion सिंक सर्वर के साथ TaskWarrior सिंक पर स्विच करें'; + + @override + String get taskchampionTileTitle => 'Taskchampion सिंक'; + + @override + String get ccsyncCredentials => 'CCync क्रेडेन्शियल'; + + @override + String get deleteTaskConfirmation => 'कार्य हटाएं'; + + @override + String get deleteTaskTitle => 'सभी कार्य हटाएं?'; + + @override + String get deleteTaskWarning => + 'यह क्रिया अपरिवर्तनीय है और यह सभी स्थानीय रूप से संग्रहीत कार्यों को हटा देगी।'; } diff --git a/lib/app/utils/language/marathi_sentences.dart b/lib/app/utils/language/marathi_sentences.dart index 41b9d53f..27bbaa91 100644 --- a/lib/app/utils/language/marathi_sentences.dart +++ b/lib/app/utils/language/marathi_sentences.dart @@ -187,4 +187,24 @@ class MarathiSentences extends Sentences { String get reportsPageNoTasksFound => 'कोणतेही काम सापडले नाहीत'; @override String get reportsPageAddTasksToSeeReports => 'अहवाल पाहण्यासाठी काम जोडा'; + + @override + String get taskchampionTileDescription => + 'CCSync किंवा Taskchampion Sync Server सह TaskWarrior सिंक वर स्विच करा'; + + @override + String get taskchampionTileTitle => 'Taskchampion सिंक'; + + @override + String get ccsyncCredentials => 'CCync क्रेडेन्शियल'; + + @override + String get deleteTaskConfirmation => 'कार्य हटवा'; + + @override + String get deleteTaskTitle => 'सर्व कार्य हटवायचे का?'; + + @override + String get deleteTaskWarning => + 'ही क्रिया अपरिवर्तनीय आहे आणि हे सर्व स्थानिक पातळीवर संग्रहित केलेले कार्य हटवेल.'; } diff --git a/lib/app/utils/language/sentences.dart b/lib/app/utils/language/sentences.dart index ac050117..4939cd3c 100644 --- a/lib/app/utils/language/sentences.dart +++ b/lib/app/utils/language/sentences.dart @@ -33,6 +33,9 @@ abstract class Sentences { String get settingsPageEnable24hrFormatTitle; String get settingsPageEnable24hrFormatDescription; + String get taskchampionTileTitle; + String get taskchampionTileDescription; + String get settingsPageSelectLanguage; String get settingsPageToggleNativeLanguage; @@ -43,6 +46,10 @@ abstract class Sentences { String get navDrawerReports; String get navDrawerAbout; String get navDrawerSettings; + String get ccsyncCredentials; + String get deleteTaskTitle; + String get deleteTaskConfirmation; + String get deleteTaskWarning; String get navDrawerExit; String get detailPageDescription; diff --git a/lib/app/utils/taskchampion/credentials_storage.dart b/lib/app/utils/taskchampion/credentials_storage.dart new file mode 100644 index 00000000..3e449963 --- /dev/null +++ b/lib/app/utils/taskchampion/credentials_storage.dart @@ -0,0 +1,16 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class CredentialsStorage { + static const String _encryptionSecretKey = 'encryptionSecret'; + static const String _clientIdKey = 'clientId'; + + static Future getEncryptionSecret() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.getString(_encryptionSecretKey); + } + + static Future getClientId() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.getString(_clientIdKey); + } +} diff --git a/lib/app/utils/taskchampion/taskchampion.dart b/lib/app/utils/taskchampion/taskchampion.dart new file mode 100644 index 00000000..79da30c3 --- /dev/null +++ b/lib/app/utils/taskchampion/taskchampion.dart @@ -0,0 +1,142 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; +import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/theme/app_settings.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class ManageTaskChampionCreds extends StatelessWidget { + final TextEditingController _encryptionSecretController = + TextEditingController(); + final TextEditingController _clientIdController = TextEditingController(); + + ManageTaskChampionCreds({super.key}) { + _loadCredentials(); + } + + Future _loadCredentials() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + _encryptionSecretController.text = + prefs.getString('encryptionSecret') ?? ''; + _clientIdController.text = prefs.getString('clientId') ?? ''; + } + + Future _saveCredentials(BuildContext context) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString('encryptionSecret', _encryptionSecretController.text); + await prefs.setString('clientId', _clientIdController.text); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Credentials saved successfully')), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: TaskWarriorColors.kprimaryBackgroundColor, + titleSpacing: 0, + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Configure TaskChampion", + style: GoogleFonts.poppins( + color: TaskWarriorColors.white, + fontSize: TaskWarriorFonts.fontSizeLarge, + ), + ), + ], + ), + actions: [ + IconButton( + icon: Icon( + Icons.info, + color: TaskWarriorColors.white, + ), + onPressed: () async { + String url = "https://github.com/its-me-abhishek/ccsync"; + if (!await launchUrl(Uri.parse(url))) { + throw Exception('Could not launch $url'); + } + }, + ), + ], + leading: BackButton( + color: TaskWarriorColors.white, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryBackgroundColor + : TaskWarriorColors.kLightPrimaryBackgroundColor, + body: Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: ListView( + children: [ + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + controller: _encryptionSecretController, + decoration: InputDecoration( + labelText: 'Encryption Secret', + labelStyle: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 10), + TextField( + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + controller: _clientIdController, + decoration: InputDecoration( + labelText: 'Client ID', + labelStyle: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + border: const OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () => _saveCredentials(context), + child: const Text('Save Credentials'), + ), + const SizedBox(height: 10), + Text( + 'Tip: Click on the info icon in the top right corner to get your credentials', + style: TextStyle( + fontSize: 15, + color: AppSettings.isDarkMode + ? TaskWarriorColors.white + : TaskWarriorColors.black, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b21ae466..9d87193a 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,6 +14,7 @@ import package_info_plus import path_provider_foundation import permission_handler_apple import shared_preferences_foundation +import sqflite import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { @@ -26,5 +27,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PermissionHandlerPlugin.register(with: registry.registrar(forPlugin: "PermissionHandlerPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index b6a07880..14d42c7e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1074,6 +1074,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + url: "https://pub.dev" + source: hosted + version: "2.3.3+1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + url: "https://pub.dev" + source: hosted + version: "2.5.4" stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c0e89b43..86c46f43 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,7 @@ dependencies: shared_preferences: ^2.2.2 shared_preferences_web: ^2.0.3 sizer: ^2.0.15 + sqflite: ^2.3.3+1 syncfusion_flutter_charts: ^23.2.7 timezone: ^0.9.2 tuple: ^2.0.0