11import 'dart:io' ;
22import 'dart:typed_data' ;
3+ import 'dart:convert' ;
34import 'package:file_selector/file_selector.dart' ;
45import 'package:file_picker_writable/file_picker_writable.dart' ;
56
@@ -22,6 +23,137 @@ Future<void> saveServerCert(String contents) async {
2223 }
2324}
2425
26+ String formatDateValue (dynamic val) {
27+ if (val == null ) return '-' ;
28+
29+ try {
30+ final dt = DateTime .parse (val.toString ()).toLocal ();
31+ return '${dt .year .toString ().padLeft (4 , '0' )}-'
32+ '${dt .month .toString ().padLeft (2 , '0' )}-'
33+ '${dt .day .toString ().padLeft (2 , '0' )} '
34+ '${dt .hour .toString ().padLeft (2 , '0' )}:'
35+ '${dt .minute .toString ().padLeft (2 , '0' )}:'
36+ '${dt .second .toString ().padLeft (2 , '0' )}' ;
37+ } catch (_) {
38+ return val.toString (); // fallback
39+ }
40+ }
41+
42+ String formatTasksAsTxt (String contents) {
43+ Map <String , String > labelMap = {
44+ 'description' : 'Description' ,
45+ 'status' : 'Status' ,
46+ 'due' : 'Due' ,
47+ 'project' : 'Project' ,
48+ 'priority' : 'Priority' ,
49+ 'uuid' : 'UUID' ,
50+ 'tags' : 'Tags' ,
51+ 'depends' : 'Depends' ,
52+ 'annotations' : 'Annotations' ,
53+ 'entry' : 'Entry' ,
54+ 'modified' : 'Modified' ,
55+ 'start' : 'Start' ,
56+ 'wait' : 'Wait' ,
57+ 'recur' : 'Recur' ,
58+ 'rtype' : 'RType' ,
59+ 'urgency' : 'Urgency' ,
60+ 'end' : 'End' ,
61+ 'id' : 'ID'
62+ };
63+
64+ String formatTaskMap (Map m) {
65+ final entryVal = m['entry' ];
66+ final startVal = m['start' ];
67+
68+ List <String > order = [
69+ 'id' ,
70+ 'description' ,
71+ 'status' ,
72+ 'project' ,
73+ 'due' ,
74+ 'priority' ,
75+ 'uuid' ,
76+ 'entry' ,
77+ 'modified' ,
78+ 'start' ,
79+ 'wait' ,
80+ 'recur' ,
81+ 'rtype' ,
82+ 'urgency' ,
83+ 'end' ,
84+ 'tags' ,
85+ 'depends' ,
86+ 'annotations'
87+ ];
88+ List <String > lines = [];
89+ for (var key in order) {
90+ if (! m.containsKey (key) || m[key] == null ) continue ;
91+ if (key == 'start' &&
92+ startVal != null &&
93+ entryVal != null &&
94+ startVal.toString () == entryVal.toString ()) {
95+ continue ;
96+ }
97+
98+ var val = m[key];
99+ if (key == 'tags' || key == 'depends' ) {
100+ if (val is List ) {
101+ lines.add ('${labelMap [key ]}: ${val .join (', ' )}' );
102+ } else {
103+ lines.add ('${labelMap [key ]}: $val ' );
104+ }
105+ } else if (key == 'annotations' ) {
106+ if (val is List && val.isNotEmpty) {
107+ lines.add ('${labelMap [key ]}:' );
108+ for (var a in val) {
109+ if (a is Map ) {
110+ var entry = a['entry' ] ?? '' ;
111+ var desc = a['description' ] ?? '' ;
112+ lines.add (' - ${labelMap ['entry' ]}: $entry ' );
113+ lines.add (' Description: $desc ' );
114+ } else {
115+ lines.add (' - $a ' );
116+ }
117+ }
118+ }
119+ } else {
120+ final isDateField =
121+ ['due' , 'entry' , 'modified' , 'start' , 'wait' , 'end' ].contains (key);
122+
123+ lines.add (
124+ '${labelMap [key ] ?? key }: '
125+ '${isDateField ? formatDateValue (val ) : val .toString ()}' ,
126+ );
127+ }
128+ }
129+ return lines.join ('\n ' );
130+ }
131+
132+ dynamic parsed;
133+ try {
134+ parsed = json.decode (contents);
135+ } catch (_) {
136+ try {
137+ // Attempt to convert Dart-style maps (single quotes) to JSON
138+ var fixed = contents.replaceAll ("'" , '"' );
139+ parsed = json.decode (fixed);
140+ } catch (e) {
141+ return contents; // fallback to original if parsing fails
142+ }
143+ }
144+
145+ if (parsed is List ) {
146+ return parsed.map ((e) {
147+ if (e is Map ) return formatTaskMap (Map .from (e));
148+ return e.toString ();
149+ }).join ('\n\n ' );
150+ } else if (parsed is Map ) {
151+ return formatTaskMap (Map .from (parsed));
152+ } else {
153+ return parsed.toString ();
154+ }
155+ }
156+
25157Future <void > exportTasks ({
26158 required String contents,
27159 required String suggestedName,
0 commit comments