diff --git a/b0esche_cloud/lib/pages/file_explorer.dart b/b0esche_cloud/lib/pages/file_explorer.dart index 5e417c5..b0181c8 100644 --- a/b0esche_cloud/lib/pages/file_explorer.dart +++ b/b0esche_cloud/lib/pages/file_explorer.dart @@ -10,6 +10,8 @@ import '../blocs/file_browser/file_browser_state.dart'; import '../blocs/permission/permission_bloc.dart'; import '../blocs/permission/permission_event.dart'; import '../blocs/permission/permission_state.dart'; +import '../blocs/session/session_bloc.dart'; +import '../blocs/session/session_state.dart'; import '../blocs/upload/upload_bloc.dart'; import '../blocs/upload/upload_event.dart'; import '../blocs/upload/upload_state.dart'; @@ -282,15 +284,59 @@ class _FileExplorerState extends State { } void _downloadFile(FileItem file) async { + // Show download starting snackbar + ScaffoldFeatureController? snackController; + if (context.mounted) { + snackController = ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppTheme.accentColor, + backgroundColor: AppTheme.accentColor.withValues(alpha: 0.3), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Preparing download: ${file.name}', + overflow: TextOverflow.ellipsis, + style: const TextStyle(color: AppTheme.primaryText), + ), + ), + ], + ), + duration: const Duration(seconds: 3), + backgroundColor: AppTheme.primaryBackground, + ), + ); + } + try { final fileService = getIt(); + + // Get token from SessionBloc for authentication + final sessionState = context.read().state; + String? token; + if (sessionState is SessionActive) { + token = sessionState.token; + } + + if (token == null) { + throw Exception('Not authenticated'); + } + final downloadUrl = await fileService.getDownloadUrl( widget.orgId, file.path, ); - // For web, use the download URL with the ApiClient base URL (from DI) - final fullUrl = '${fileService.baseUrl}$downloadUrl'; + // Build full URL with token for authentication (anchor elements can't send headers) + final fullUrl = '${fileService.baseUrl}$downloadUrl&token=${Uri.encodeComponent(token)}'; // Trigger download via anchor element final anchor = web.HTMLAnchorElement() @@ -298,16 +344,50 @@ class _FileExplorerState extends State { ..download = file.name; anchor.click(); + // Dismiss preparing snackbar and show success + snackController?.close(); if (context.mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Downloading ${file.name}'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.download_done, color: AppTheme.accentColor, size: 20), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Downloaded ${file.name}', + overflow: TextOverflow.ellipsis, + style: const TextStyle(color: AppTheme.primaryText), + ), + ), + ], + ), + duration: const Duration(seconds: 2), + backgroundColor: AppTheme.primaryBackground, + ), + ); } } catch (e) { + snackController?.close(); if (context.mounted) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Download failed: $e'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Row( + children: [ + const Icon(Icons.error_outline, color: Colors.red, size: 20), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Download failed: $e', + overflow: TextOverflow.ellipsis, + style: const TextStyle(color: AppTheme.primaryText), + ), + ), + ], + ), + backgroundColor: AppTheme.primaryBackground, + ), + ); } } }