diff --git a/lib/app/modules/home/views/tasks_builder.dart b/lib/app/modules/home/views/tasks_builder.dart index d17c5875..908aeee1 100644 --- a/lib/app/modules/home/views/tasks_builder.dart +++ b/lib/app/modules/home/views/tasks_builder.dart @@ -20,7 +20,7 @@ import 'package:taskwarrior/app/utils/language/supported_language.dart'; import 'package:taskwarrior/app/utils/taskfunctions/modify.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; -class TasksBuilder extends StatelessWidget { +class TasksBuilder extends StatefulWidget { const TasksBuilder({ super.key, required this.taskData, @@ -42,27 +42,103 @@ class TasksBuilder extends StatelessWidget { final ScrollController scrollController; final bool showbtn; - void setStatus(BuildContext context, String newValue, String id) { + @override + State createState() => _TasksBuilderState(); +} + +class _TasksBuilderState extends State { + final Set _selectedTasks = {}; + bool _isMultiSelectMode = false; + final Map _lastModifiedTasks = {}; + + void setStatus(BuildContext context, String newValue, String id, + {bool showSnackbar = true}) { var storageWidget = Get.find(); Modify modify = Modify( getTask: storageWidget.getTask, mergeTask: storageWidget.mergeTask, uuid: id, ); + Task task = storageWidget.getTask(id); + _lastModifiedTasks[id] = task.status; + modify.set('status', newValue); - saveChanges(context, modify, id, newValue); + saveChanges(context, modify, id, newValue, showSnackbar: showSnackbar); + if (task.due != null) { + cancelNotification(task); + } } void saveChanges( - BuildContext context, Modify modify, String id, String newValue) async { + BuildContext context, Modify modify, String id, String newValue, + {bool showSnackbar = true}) async { var now = DateTime.now().toUtc(); modify.save( modified: () => now, ); + if (showSnackbar) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Task Updated', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2), + action: SnackBarAction( + label: 'Undo', + onPressed: () { + undoChanges(context, id, 'pending'); + }, + ), + )); + } + } + + void toggleTaskSelection(String taskId) { + if (widget.pendingFilter) { + setState(() { + if (_selectedTasks.contains(taskId)) { + _selectedTasks.remove(taskId); + if (_selectedTasks.isEmpty) { + _isMultiSelectMode = false; + } + } else { + _selectedTasks.add(taskId); + _isMultiSelectMode = true; + } + }); + } + } + + void completeSelectedTasks(BuildContext context) { + _lastModifiedTasks.clear(); + + for (String taskId in _selectedTasks) { + setStatus(context, 'completed', taskId, showSnackbar: false); + } + + if (Platform.isAndroid) { + WidgetController widgetController = Get.put(WidgetController()); + widgetController.fetchAllData(); + widgetController.update(); + } + + final int taskCount = _selectedTasks.length; + setState(() { + _selectedTasks.clear(); + _isMultiSelectMode = false; + }); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( - 'Task Updated', + '$taskCount Tasks Completed', style: TextStyle( color: AppSettings.isDarkMode ? TaskWarriorColors.kprimaryTextColor @@ -72,13 +148,52 @@ class TasksBuilder extends StatelessWidget { backgroundColor: AppSettings.isDarkMode ? TaskWarriorColors.ksecondaryBackgroundColor : TaskWarriorColors.kLightSecondaryBackgroundColor, - duration: const Duration(seconds: 2), + duration: const Duration(seconds: 3), action: SnackBarAction( - label: 'Undo', + label: 'Undo All', onPressed: () { - undoChanges( - context, id, 'pending'); + undoAllChanges(context); + }, + ), + )); + } + + void deleteSelectedTasks(BuildContext context) { + _lastModifiedTasks.clear(); + + for (String taskId in _selectedTasks) { + setStatus(context, 'deleted', taskId, showSnackbar: false); + } + + if (Platform.isAndroid) { + WidgetController widgetController = Get.put(WidgetController()); + widgetController.fetchAllData(); + widgetController.update(); + } + final int taskCount = _selectedTasks.length; + setState(() { + _selectedTasks.clear(); + _isMultiSelectMode = false; + }); + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + '$taskCount Tasks Deleted', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 3), + action: SnackBarAction( + label: 'Undo All', + onPressed: () { + undoAllChanges(context); }, ), )); @@ -98,6 +213,43 @@ class TasksBuilder extends StatelessWidget { storageWidget.update(); } + void undoAllChanges(BuildContext context) { + var storageWidget = Get.find(); + + for (var entry in _lastModifiedTasks.entries) { + String taskId = entry.key; + String originalStatus = entry.value; + + Modify modify = Modify( + getTask: storageWidget.getTask, + mergeTask: storageWidget.mergeTask, + uuid: taskId, + ); + modify.set('status', originalStatus); + modify.save( + modified: () => DateTime.now().toUtc(), + ); + } + + storageWidget.update(); + _lastModifiedTasks.clear(); + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text( + 'Tasks Restored', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + duration: const Duration(seconds: 2), + )); + } + void cancelNotification(Task task) { NotificationService notificationService = NotificationService(); @@ -121,11 +273,12 @@ class TasksBuilder extends StatelessWidget { FloatingActionButtonLocation.miniStartFloat, floatingActionButton: AnimatedOpacity( duration: const Duration(milliseconds: 100), //show/hide animation - opacity: showbtn ? 1.0 : 0.0, //set obacity to 1 on visible, or hide + opacity: + widget.showbtn ? 1.0 : 0.0, //set obacity to 1 on visible, or hide child: FloatingActionButton( heroTag: "btn2", onPressed: () { - scrollController.animateTo( + widget.scrollController.animateTo( //go to top of scroll 0, //scroll offset to go duration: @@ -145,43 +298,156 @@ class TasksBuilder extends StatelessWidget { ), ), backgroundColor: Colors.transparent, + appBar: _isMultiSelectMode + ? AppBar( + backgroundColor: AppSettings.isDarkMode + ? TaskWarriorColors.ksecondaryBackgroundColor + : TaskWarriorColors.kLightSecondaryBackgroundColor, + title: Text( + '${_selectedTasks.length} selected', + style: TextStyle( + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + ), + leading: IconButton( + icon: Icon( + Icons.close, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kprimaryTextColor + : TaskWarriorColors.kLightPrimaryTextColor, + ), + onPressed: () { + setState(() { + _selectedTasks.clear(); + _isMultiSelectMode = false; + }); + }, + ), + actions: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: ElevatedButton.icon( + icon: const Icon(Icons.done_all), + label: const Text('Complete'), + style: ElevatedButton.styleFrom( + backgroundColor: TaskWarriorColors.green, + foregroundColor: Colors.white, + ), + onPressed: () => completeSelectedTasks(context), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: ElevatedButton.icon( + icon: const Icon(Icons.delete), + label: const Text('Delete'), + style: ElevatedButton.styleFrom( + backgroundColor: TaskWarriorColors.red, + foregroundColor: Colors.white, + ), + onPressed: () => deleteSelectedTasks(context), + ), + ), + ], + ) + : null, body: Obx( - () => taskData.isEmpty + () => widget.taskData.isEmpty ? Padding( padding: const EdgeInsets.all(16.0), child: Center( child: Text( - (searchVisible) - ? '${SentenceManager(currentLanguage: selectedLanguage).sentences.homePageSearchNotFound} :(' - : SentenceManager(currentLanguage: selectedLanguage) + (widget.searchVisible) + ? '${SentenceManager(currentLanguage: widget.selectedLanguage).sentences.homePageSearchNotFound} :(' + : SentenceManager( + currentLanguage: widget.selectedLanguage) .sentences .homePageClickOnTheBottomRightButtonToStartAddingTasks, textAlign: TextAlign.center, style: TextStyle( - fontFamily: FontFamily.poppins, - fontSize: TaskWarriorFonts.fontSizeLarge, - color: AppSettings.isDarkMode - ? TaskWarriorColors.kLightPrimaryBackgroundColor - : TaskWarriorColors.kprimaryBackgroundColor), - // style: GoogleFonts.poppins( - // fontSize: TaskWarriorFonts.fontSizeLarge, - // color: AppSettings.isDarkMode - // ? TaskWarriorColors.kLightPrimaryBackgroundColor - // : TaskWarriorColors.kprimaryBackgroundColor, - // ), + fontFamily: FontFamily.poppins, + fontSize: TaskWarriorFonts.fontSizeLarge, + color: AppSettings.isDarkMode + ? TaskWarriorColors.kLightPrimaryBackgroundColor + : TaskWarriorColors.kprimaryBackgroundColor, + ), ), ), ) : ListView.builder( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 2), - itemCount: taskData.length, - controller: scrollController, + itemCount: widget.taskData.length, + controller: widget.scrollController, primary: false, itemBuilder: (context, index) { - var task = taskData[index]; - return pendingFilter - ? Slidable( + var task = widget.taskData[index]; + final bool isSelected = _selectedTasks.contains(task.uuid); + + Widget taskCard = Card( + color: isSelected + ? AppSettings.isDarkMode + ? Colors.blueGrey.shade800 + : Colors.blue.shade100 + : AppSettings.isDarkMode + ? Palette.kToDark + : TaskWarriorColors.white, + child: InkWell( + splashColor: AppSettings.isDarkMode + ? TaskWarriorColors.black + : TaskWarriorColors.borderColor, + onTap: _isMultiSelectMode && widget.pendingFilter + ? () => toggleTaskSelection(task.uuid) + : () { + Get.toNamed(Routes.DETAIL_ROUTE, + arguments: ["uuid", task.uuid]); + }, + onLongPress: widget.pendingFilter + ? () { + toggleTaskSelection(task.uuid); + } + : null, + child: Stack( + children: [ + TaskListItem( + task, + pendingFilter: widget.pendingFilter, + darkmode: AppSettings.isDarkMode, + useDelayTask: widget.useDelayTask, + modify: Modify( + getTask: storageWidget.getTask, + mergeTask: storageWidget.mergeTask, + uuid: task.uuid, + ), + selectedLanguage: widget.selectedLanguage, + ), + if (isSelected && widget.pendingFilter) + Positioned( + right: 10, + top: 10, + child: Container( + decoration: BoxDecoration( + color: + TaskWarriorColors.green.withAlpha(140), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.check, + color: Colors.white, + size: 20, + ), + ), + ), + ], + ), + ), + ); + + return !widget.pendingFilter || _isMultiSelectMode + ? taskCard + : Slidable( key: ValueKey(task.uuid), startActionPane: ActionPane( motion: const BehindMotion(), @@ -230,66 +496,7 @@ class TasksBuilder extends StatelessWidget { ), ], ), - child: Card( - color: AppSettings.isDarkMode - ? Palette.kToDark - : TaskWarriorColors.white, - child: InkWell( - splashColor: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.borderColor, - onTap: () { - Get.toNamed(Routes.DETAIL_ROUTE, - arguments: ["uuid", task.uuid]); - }, - // child: Text(task.entry.toString()), - // onTap: () => Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => DetailRouteView(task.uuid), - // ), - // ), - child: TaskListItem( - task, - pendingFilter: pendingFilter, - darkmode: AppSettings.isDarkMode, - useDelayTask: useDelayTask, - modify: Modify( - getTask: storageWidget.getTask, - mergeTask: storageWidget.mergeTask, - uuid: task.uuid, - ), - selectedLanguage: selectedLanguage, - ), - ), - ), - ) - : Card( - color: AppSettings.isDarkMode - ? Palette.kToDark - : TaskWarriorColors.white, - child: InkWell( - splashColor: AppSettings.isDarkMode - ? TaskWarriorColors.black - : TaskWarriorColors.borderColor, - onTap: () { - Get.toNamed(Routes.DETAIL_ROUTE, - arguments: ["uuid", task.uuid]); - }, - // child: Text(task.entry.toString()), - child: TaskListItem( - task, - pendingFilter: pendingFilter, - darkmode: AppSettings.isDarkMode, - useDelayTask: useDelayTask, - modify: Modify( - getTask: storageWidget.getTask, - mergeTask: storageWidget.mergeTask, - uuid: task.uuid, - ), - selectedLanguage: selectedLanguage, - ), - ), + child: taskCard, ); }, ),