@@ -23,7 +23,7 @@ class GroupExportService {
2323 // 1. Grades matrix: students × sessions → grade value
2424 // ---------------------------------------------------------------------------
2525
26- Future <File > exportGradesCsv ({
26+ Future <({ File file, String filename}) > exportGradesCsv ({
2727 required int groupId,
2828 required String groupName,
2929 }) async {
@@ -116,7 +116,7 @@ class GroupExportService {
116116 buf.write ('\n ' );
117117 }
118118
119- return _saveAndReturn (
119+ return _buildFile (
120120 content: buf.toString (),
121121 filename: '${_sanitize (groupName )}_grades.csv' ,
122122 );
@@ -126,7 +126,7 @@ class GroupExportService {
126126 // 2. Attendance sheet: students × session dates
127127 // ---------------------------------------------------------------------------
128128
129- Future <File > exportAttendanceCsv ({
129+ Future <({ File file, String filename}) > exportAttendanceCsv ({
130130 required int groupId,
131131 required String groupName,
132132 }) async {
@@ -206,7 +206,7 @@ class GroupExportService {
206206 buf.write (';$absentCount ;$excusedCount ;$rate \n ' );
207207 }
208208
209- return _saveAndReturn (
209+ return _buildFile (
210210 content: buf.toString (),
211211 filename: '${_sanitize (groupName )}_attendance.csv' ,
212212 );
@@ -216,7 +216,7 @@ class GroupExportService {
216216 // 3. Homework & material compliance
217217 // ---------------------------------------------------------------------------
218218
219- Future <File > exportHomeworkMaterialCsv ({
219+ Future <({ File file, String filename}) > exportHomeworkMaterialCsv ({
220220 required int groupId,
221221 required String groupName,
222222 }) async {
@@ -265,7 +265,7 @@ class GroupExportService {
265265 dateFormat: dateFormat,
266266 );
267267
268- return _saveAndReturn (
268+ return _buildFile (
269269 content: buf.toString (),
270270 filename: '${_sanitize (groupName )}_homework_material.csv' ,
271271 );
@@ -313,7 +313,7 @@ class GroupExportService {
313313 // 4. Full student summary (flat)
314314 // ---------------------------------------------------------------------------
315315
316- Future <File > exportSummaryCsv ({
316+ Future <({ File file, String filename}) > exportSummaryCsv ({
317317 required int groupId,
318318 required String groupName,
319319 }) async {
@@ -426,7 +426,7 @@ class GroupExportService {
426426 buf.write ('\n ' );
427427 }
428428
429- return _saveAndReturn (
429+ return _buildFile (
430430 content: buf.toString (),
431431 filename: '${_sanitize (groupName )}_summary.csv' ,
432432 );
@@ -436,16 +436,16 @@ class GroupExportService {
436436 // Helpers
437437 // ---------------------------------------------------------------------------
438438
439- Future <File > _saveAndReturn ({
439+ Future <({ File file, String filename})> _buildFile ({
440440 required String content,
441441 required String filename,
442442 }) async {
443- final dir = await getApplicationDocumentsDirectory ();
443+ final dir = await getTemporaryDirectory ();
444444 final file = File ('${dir .path }/$filename ' );
445445 // UTF-8 BOM for Excel compatibility.
446446 final bom = [0xEF , 0xBB , 0xBF ];
447447 await file.writeAsBytes ([...bom, ...utf8.encode (content)]);
448- return file;
448+ return ( file: file, filename : filename) ;
449449 }
450450
451451 List <DateTime > _uniqueDates (Iterable <DateTime > raw) {
@@ -500,12 +500,20 @@ class _SessionKey {
500500 String get id => '${date .toIso8601String ()}|$categoryId |$label ' ;
501501}
502502
503- /// Shares [file] on platforms that support a share sheet, otherwise just
504- /// keeps the saved file.
505- Future <void > shareOrSaveFile ({
503+ /// Delivers [file] to the user.
504+ ///
505+ /// On Android/iOS shows a share sheet so the user can pick the destination.
506+ /// On desktop, [savePathResolver] is called to obtain the target path
507+ /// (e.g. via a FilePicker dialog). If it returns `null` the export is
508+ /// cancelled and this function returns `false` .
509+ Future <bool > shareOrSaveFile ({
506510 required File file,
511+ required String filename,
507512 required String mimeType,
508513 String ? subject,
514+ /// Called on desktop to resolve where the user wants to save the file.
515+ /// Should open a save-file dialog and return the chosen path, or null.
516+ Future <String ?> Function ()? savePathResolver,
509517}) async {
510518 if (Platform .isAndroid || Platform .isIOS) {
511519 await SharePlus .instance.share (
@@ -514,6 +522,16 @@ Future<void> shareOrSaveFile({
514522 subject: subject,
515523 ),
516524 );
525+ return true ;
517526 }
518- // On desktop the file is already saved; nothing more to do.
527+
528+ // Desktop: ask the caller for a save path.
529+ final savePath = savePathResolver != null
530+ ? await savePathResolver ()
531+ : null ;
532+ if (savePath == null ) return false ; // user cancelled or no resolver
533+
534+ final bytes = await file.readAsBytes ();
535+ await File (savePath).writeAsBytes (bytes);
536+ return true ;
519537}
0 commit comments