diff --git a/lib/app/modules/detailRoute/views/tags_widget.dart b/lib/app/modules/detailRoute/views/tags_widget.dart index d8bcafd5..12561753 100644 --- a/lib/app/modules/detailRoute/views/tags_widget.dart +++ b/lib/app/modules/detailRoute/views/tags_widget.dart @@ -99,17 +99,27 @@ class TagsRouteState extends State { Map? _pendingTags; ListBuilder? draftTags; - void _addTag(String tag) { - if (tag.isNotEmpty) { - // Add this condition to ensure the tag is not empty - if (draftTags == null) { - draftTags = ListBuilder([tag]); - } else { + List _parseTags(String input) { + return input + .split(',') + .map((e) => e.trim()) + .where((e) => e.isNotEmpty) + .toList(); + } + + void _addTags(List tags) { + if (tags.isEmpty) return; + + draftTags ??= ListBuilder(); + + for (final tag in tags) { + if (!draftTags!.build().contains(tag)) { draftTags!.add(tag); } - widget.callback(draftTags); - setState(() {}); } + + widget.callback(draftTags); + setState(() {}); } void _removeTag(String tag) { @@ -197,7 +207,7 @@ class TagsRouteState extends State { !(draftTags?.build().contains(tag.key) ?? false))) FilterChip( backgroundColor: TaskWarriorColors.grey, - onSelected: (_) => _addTag(tag.key), + onSelected: (_) => _addTags([tag.key]), label: Text( '${tag.key} ${tag.value.frequency}', ), @@ -234,11 +244,22 @@ class TagsRouteState extends State { color: tColors.primaryTextColor, ), validator: (value) { - if (value != null) { - if (value.isNotEmpty && value.contains(" ")) { + final tags = _parseTags(value ?? ''); + + if (tags.isEmpty) { + return "Please enter a tag"; + } + + for (final tag in tags) { + if (tag.contains(' ')) { return "Tags cannot contain spaces"; } + + if (draftTags?.build().contains(tag) ?? false) { + return "Tag already exists"; + } } + return null; }, autofocus: true, @@ -264,9 +285,8 @@ class TagsRouteState extends State { onPressed: () { if (formKey.currentState!.validate()) { try { - validateTaskTags(controller.text); - _addTag(controller.text); - // Navigator.of(context).pop(); + final tags = _parseTags(controller.text); + _addTags(tags); Get.back(); } on FormatException catch (e, trace) { logError(e, trace); diff --git a/lib/app/modules/taskc_details/views/tag_editor.dart b/lib/app/modules/taskc_details/views/tag_editor.dart new file mode 100644 index 00000000..26d40704 --- /dev/null +++ b/lib/app/modules/taskc_details/views/tag_editor.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; +import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; +import 'package:taskwarrior/app/utils/add_task_dialogue/tags_input.dart'; + +class TagEditor extends StatelessWidget { + final List suggestions; + final List initialTags; + final void Function(List) onSave; + + const TagEditor({ + super.key, + required this.suggestions, + required this.initialTags, + required this.onSave, + }); + + @override + Widget build(BuildContext context) { + final RxList tags = RxList(initialTags); + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .cancel, + ), + ), + Text( + '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.edit}:${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.tags}', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + TextButton( + onPressed: () { + onSave(tags); + Get.back(); + }, + child: Text( + SentenceManager( + currentLanguage: AppSettings.selectedLanguage) + .sentences + .save, + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: AddTaskTagsInput( + initialTags: initialTags, + suggestions: suggestions, + onTagsChanges: (newTags) => tags.value = newTags, + ), + ), + const Padding(padding: EdgeInsets.all(20)), + ], + ), + ); + } +} diff --git a/lib/app/modules/taskc_details/views/taskc_details_view.dart b/lib/app/modules/taskc_details/views/taskc_details_view.dart index a3938743..c974dcac 100644 --- a/lib/app/modules/taskc_details/views/taskc_details_view.dart +++ b/lib/app/modules/taskc_details/views/taskc_details_view.dart @@ -3,9 +3,12 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:taskwarrior/app/modules/home/controllers/home_controller.dart'; +import 'package:taskwarrior/app/modules/taskc_details/views/tag_editor.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_colors.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; +import 'package:taskwarrior/app/utils/home_path/impl/home.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; import '../controllers/taskc_details_controller.dart'; @@ -102,7 +105,7 @@ class TaskcDetailsView extends GetView { controller.wait.value, ), ], - _buildEditableDetail( + _buildTagEditorDetail( context, '${SentenceManager(currentLanguage: AppSettings.selectedLanguage).sentences.detailPageTags}:', controller.tags.join(', '), @@ -182,6 +185,38 @@ class TaskcDetailsView extends GetView { ); } + Widget _buildTagEditorDetail(BuildContext context, String label, String value, + Function(String) onChanged) { + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; + Iterable suggestions = + Get.find().allTagsInCurrentTasks; + return InkWell( + onTap: () async { + showModalBottomSheet( + backgroundColor: tColors.dialogBackgroundColor, + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(0), + topRight: Radius.circular(0), + ), + ), + builder: (context) => TagEditor( + suggestions: + suggestions.toList(), // You can pass tag suggestions here + initialTags: value.split(',').map((e) => e.trim()).toList(), + onSave: (List newTags) { + onChanged(newTags.join(', ')); + }, + ), + ); + }, + child: _buildDetail(context, label, value), + ); + } + Widget _buildSelectableDetail(BuildContext context, String label, String value, List options, Function(String) onChanged) { return InkWell( diff --git a/lib/app/utils/add_task_dialogue/tags_input.dart b/lib/app/utils/add_task_dialogue/tags_input.dart index 21b9e8fb..fe4acbab 100644 --- a/lib/app/utils/add_task_dialogue/tags_input.dart +++ b/lib/app/utils/add_task_dialogue/tags_input.dart @@ -5,10 +5,13 @@ import 'package:textfield_tags/textfield_tags.dart'; class AddTaskTagsInput extends StatefulWidget { final Iterable suggestions; + final Iterable initialTags; final Function(List)? onTagsChanges; + const AddTaskTagsInput( {super.key, this.suggestions = const Iterable.empty(), + this.initialTags = const Iterable.empty(), this.onTagsChanges}); @override @@ -73,6 +76,7 @@ class _AddTaskTagsInputState extends State { fieldViewBuilder: (context, textEditingController, focusNode, onFieldSubmitted) { return TextFieldTags( + initialTags: [...widget.initialTags], textEditingController: textEditingController, focusNode: focusNode, textfieldTagsController: stringTagController,