-
Notifications
You must be signed in to change notification settings - Fork 143
feat: Enhance TXT export readability with label:value format #526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'dart:io'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'dart:typed_data'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'dart:convert'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'package:file_selector/file_selector.dart'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'package:file_picker_writable/file_picker_writable.dart'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -22,6 +23,137 @@ Future<void> saveServerCert(String contents) async { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String formatDateValue(dynamic val) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (val == null) return '-'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final dt = DateTime.parse(val.toString()).toLocal(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return '${dt.year.toString().padLeft(4, '0')}-' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '${dt.month.toString().padLeft(2, '0')}-' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '${dt.day.toString().padLeft(2, '0')} ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '${dt.hour.toString().padLeft(2, '0')}:' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '${dt.minute.toString().padLeft(2, '0')}:' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '${dt.second.toString().padLeft(2, '0')}'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return val.toString(); // fallback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+26
to
+40
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Formats task data contained in a JSON string into a human‑readable | |
| /// plain‑text representation. | |
| /// | |
| /// The [contents] parameter is expected to be a JSON string that decodes to | |
| /// either: | |
| /// * a `List` of task maps, or | |
| /// * a single task represented as a `Map`. | |
| /// | |
| /// Each task map is rendered into labeled, line‑separated fields using a | |
| /// predefined mapping of task attribute keys (for example, `description`, | |
| /// `status`, `due`) to human‑friendly labels, and multiple tasks are | |
| /// separated by blank lines. | |
| /// | |
| /// If JSON decoding fails, a secondary attempt is made by replacing | |
| /// single quotes with double quotes to handle Dart‑style map literals. | |
| /// If parsing still fails, the original [contents] string is returned | |
| /// unchanged. If [contents] decodes successfully but does not represent | |
| /// a `List` or `Map`, the resulting value's `toString()` representation | |
| /// is returned. |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The labelMap is defined as a local variable within the function, making it recreated on every function call. Consider moving this constant map outside the function scope (as a top-level const or static const) to improve performance and code organization.
| String formatTasksAsTxt(String contents) { | |
| Map<String, String> labelMap = { | |
| 'description': 'Description', | |
| 'status': 'Status', | |
| 'due': 'Due', | |
| 'project': 'Project', | |
| 'priority': 'Priority', | |
| 'uuid': 'UUID', | |
| 'tags': 'Tags', | |
| 'depends': 'Depends', | |
| 'annotations': 'Annotations', | |
| 'entry': 'Entry', | |
| 'modified': 'Modified', | |
| 'start': 'Start', | |
| 'wait': 'Wait', | |
| 'recur': 'Recur', | |
| 'rtype': 'RType', | |
| 'urgency': 'Urgency', | |
| 'end': 'End', | |
| 'id': 'ID' | |
| }; | |
| const Map<String, String> _taskLabelMap = <String, String>{ | |
| 'description': 'Description', | |
| 'status': 'Status', | |
| 'due': 'Due', | |
| 'project': 'Project', | |
| 'priority': 'Priority', | |
| 'uuid': 'UUID', | |
| 'tags': 'Tags', | |
| 'depends': 'Depends', | |
| 'annotations': 'Annotations', | |
| 'entry': 'Entry', | |
| 'modified': 'Modified', | |
| 'start': 'Start', | |
| 'wait': 'Wait', | |
| 'recur': 'Recur', | |
| 'rtype': 'RType', | |
| 'urgency': 'Urgency', | |
| 'end': 'End', | |
| 'id': 'ID', | |
| }; | |
| String formatTasksAsTxt(String contents) { |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order list is defined as a local variable within the nested function, making it recreated on every task formatting. Consider moving this constant list outside the function scope (as a top-level const or static const) to improve performance and code organization.
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The annotation entry field uses labelMap['entry'] which resolves to 'Entry', but the description field uses a hardcoded string 'Description'. For consistency, consider either using labelMap['description'] or making both hardcoded. This ensures consistent labeling throughout the formatting.
| lines.add(' Description: $desc'); | |
| lines.add(' ${labelMap['description'] ?? 'Description'}: $desc'); |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback logic that converts single quotes to double quotes is fragile and may produce incorrect results. This approach will incorrectly transform legitimate single quotes within string values (e.g., "It's a task" becomes "It"s a task" which is invalid JSON). Consider using a proper Dart literal parser or explicitly handling the expected input format.
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new formatTasksAsTxt function lacks test coverage. Given the complexity of the formatting logic (handling multiple data types, date parsing, nested annotations, fallback scenarios), comprehensive tests should be added to verify correct behavior for: valid JSON input with all field types, edge cases (null values, empty arrays, malformed dates), Dart-style map input with single quotes, and various annotation structures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function lacks documentation explaining its purpose, parameters, return value, and behavior. Consider adding a dartdoc comment describing: the expected input format (date string or DateTime object), the output format (YYYY-MM-DD HH:MM:SS in local timezone or '-' for null), and that it returns the original value as a string if parsing fails.