Skip to content

Commit febe218

Browse files
committed
feat: Enhance TXT export readability with label:value format
1 parent d802b00 commit febe218

File tree

2 files changed

+135
-1
lines changed

2 files changed

+135
-1
lines changed

lib/app/models/storage/savefile.dart

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:io';
22
import 'dart:typed_data';
3+
import 'dart:convert';
34
import 'package:file_selector/file_selector.dart';
45
import '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+
25157
Future<void> exportTasks({
26158
required String contents,
27159
required String suggestedName,

lib/app/modules/profile/views/profile_view.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,10 @@ class ProfileView extends GetView<ProfileController> {
179179
onPressed: () {
180180
// Navigator.of(context).pop();
181181
Get.back();
182+
// Convert exported data to human-readable TXT
183+
var txt = formatTasksAsTxt(tasks);
182184
exportTasks(
183-
contents: tasks,
185+
contents: txt,
184186
suggestedName: 'tasks-$now.txt',
185187
);
186188
},

0 commit comments

Comments
 (0)