From 708d4ca7909283fe1eea2f6773e6dd75ee41c39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Fri, 9 Jan 2026 23:14:45 +0100 Subject: [PATCH] Add error handling for organization loading in HomePage --- b0esche_cloud/lib/pages/file_explorer.dart | 736 +++++++++++---------- b0esche_cloud/lib/pages/home_page.dart | 11 + 2 files changed, 391 insertions(+), 356 deletions(-) diff --git a/b0esche_cloud/lib/pages/file_explorer.dart b/b0esche_cloud/lib/pages/file_explorer.dart index 8c010d5..48609af 100644 --- a/b0esche_cloud/lib/pages/file_explorer.dart +++ b/b0esche_cloud/lib/pages/file_explorer.dart @@ -12,6 +12,7 @@ import '../blocs/permission/permission_event.dart'; import '../blocs/permission/permission_state.dart'; import '../blocs/upload/upload_bloc.dart'; import '../blocs/upload/upload_event.dart'; +import '../blocs/upload/upload_state.dart'; import '../models/file_item.dart'; import '../theme/app_theme.dart'; import '../theme/modern_glass_button.dart'; @@ -661,393 +662,416 @@ class _FileExplorerState extends State { @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is DirectoryLoading) { - return Center( - child: CircularProgressIndicator(color: AppTheme.accentColor), - ); + return BlocListener( + listener: (context, uploadState) { + if (uploadState is UploadInProgress) { + final hasCompleted = uploadState.uploads.any((u) => u.isCompleted); + if (hasCompleted) { + final fbState = context.read().state; + String currentPath = '/'; + if (fbState is DirectoryLoaded) currentPath = fbState.currentPath; + if (fbState is DirectoryEmpty) currentPath = fbState.currentPath; + context.read().add( + LoadDirectory(orgId: widget.orgId, path: currentPath), + ); + } } + }, + child: BlocBuilder( + builder: (context, state) { + if (state is DirectoryLoading) { + return Center( + child: CircularProgressIndicator(color: AppTheme.accentColor), + ); + } - if (state is DirectoryError) { - return Center( - child: Text( - 'Error: ${state.error}', - style: const TextStyle(color: AppTheme.primaryText), - ), - ); - } + if (state is DirectoryError) { + return Center( + child: Text( + 'Error: ${state.error}', + style: const TextStyle(color: AppTheme.primaryText), + ), + ); + } - if (state is DirectoryEmpty) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: AppTheme.primaryText.withValues(alpha: 0.5), - width: 1, + if (state is DirectoryEmpty) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppTheme.primaryText.withValues(alpha: 0.5), + width: 1, + ), ), ), + child: _buildTitle(), ), - child: _buildTitle(), - ), - const SizedBox(height: 16), - BlocBuilder( - builder: (context, permState) { - if (permState is PermissionLoaded && - permState.capabilities.canWrite) { - return Row( - children: [ - ModernGlassButton( - onPressed: () async { - final result = await FilePicker.platform - .pickFiles(); - if (result != null && result.files.isNotEmpty) { - final files = result.files - .map( - (file) => FileItem( - name: file.name, - path: '/${file.name}', - type: FileType.file, - size: file.size, - lastModified: DateTime.now(), - localPath: file.path, - bytes: file.bytes, - ), - ) - .toList(); - context.read().add( - StartUpload( - files: files, - targetPath: '/', - orgId: widget.orgId, - ), - ); - } - }, - child: const Row( - children: [ - Icon(Icons.upload), - SizedBox(width: 8), - Text('Upload File'), - ], - ), - ), - const SizedBox(width: 16), - ModernGlassButton( - onPressed: () async { - final folderName = await _showCreateFolderDialog( - context, - ); - if (folderName != null && folderName.isNotEmpty) { - context.read().add( - CreateFolder( - orgId: widget.orgId, - parentPath: '/', - folderName: folderName, - ), - ); - } - }, - child: const Row( - children: [ - Icon(Icons.create_new_folder), - SizedBox(width: 8), - Text('New Folder'), - ], - ), - ), - ], - ); - } - return const SizedBox.shrink(); - }, - ), - const SizedBox(height: 16), - Row( - children: [ - IconButton( - icon: const Icon( - Icons.arrow_back, - color: AppTheme.primaryText, - ), - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - onPressed: () { - final parentPath = _getParentPath(state.currentPath); - context.read().add( - LoadDirectory(orgId: widget.orgId, path: parentPath), - ); - }, - ), - const Text( - 'Empty Folder', - style: TextStyle(color: AppTheme.primaryText), - ), - ], - ), - ], - ), - ); - } - - if (state is DirectoryLoaded) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: AppTheme.primaryText.withValues(alpha: 0.5), - width: 1, - ), - ), - ), - child: _buildTitle(), - ), - const SizedBox(height: 16), - Visibility( - visible: state.breadcrumbs.isNotEmpty, - child: Column( - children: [ - Row( - children: [ - IconButton( - icon: const Icon( - Icons.arrow_back, - color: AppTheme.primaryText, - ), - onPressed: () { - final parentPath = _getParentPath( - state.currentPath, - ); - context.read().add( - LoadDirectory( - orgId: widget.orgId, - path: parentPath, - ), - ); - }, - ), - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: state.breadcrumbs.map((breadcrumb) { - return TextButton( - onPressed: () { - context.read().add( - NavigateToFolder(breadcrumb.path), - ); - }, - child: Text( - '/${breadcrumb.name}', - style: const TextStyle( - color: AppTheme.secondaryText, - ), + const SizedBox(height: 16), + BlocBuilder( + builder: (context, permState) { + if (permState is PermissionLoaded && + permState.capabilities.canWrite) { + return Row( + children: [ + ModernGlassButton( + onPressed: () async { + final result = await FilePicker.platform + .pickFiles(withData: true); + if (result != null && result.files.isNotEmpty) { + final files = result.files + .map( + (file) => FileItem( + name: file.name, + // Parent path only; server uses filename from multipart + path: state.currentPath, + type: FileType.file, + size: file.size, + lastModified: DateTime.now(), + localPath: file.path, + bytes: file.bytes, + ), + ) + .toList(); + context.read().add( + StartUpload( + files: files, + targetPath: state.currentPath, + orgId: widget.orgId, ), ); - }).toList(), + } + }, + child: const Row( + children: [ + Icon(Icons.upload), + SizedBox(width: 8), + Text('Upload File'), + ], ), ), - ), - ], - ), - const SizedBox(height: 16), - ], - ), - ), - BlocBuilder( - builder: (context, permState) { - if (permState is PermissionLoaded && - permState.capabilities.canWrite) { - return Row( - children: [ - ModernGlassButton( - onPressed: () async { - final result = await FilePicker.platform - .pickFiles(); - if (result != null && result.files.isNotEmpty) { - final files = result.files - .map( - (file) => FileItem( - name: file.name, - path: '/${file.name}', - type: FileType.file, - size: file.size, - lastModified: DateTime.now(), - ), - ) - .toList(); - context.read().add( - StartUpload( - files: files, - targetPath: '/', - orgId: widget.orgId, - ), - ); - } - }, - child: const Row( - children: [ - Icon(Icons.upload), - SizedBox(width: 8), - Text('Upload File'), - ], - ), - ), - const SizedBox(width: 16), - ModernGlassButton( - onPressed: () async { - final folderName = await _showCreateFolderDialog( - context, - ); - if (folderName != null && folderName.isNotEmpty) { - context.read().add( - CreateFolder( - orgId: widget.orgId, - parentPath: state.currentPath, - folderName: folderName, - ), - ); - } - }, - child: const Row( - children: [ - Icon(Icons.create_new_folder), - SizedBox(width: 8), - Text('New Folder'), - ], - ), - ), - ], - ); - } - return const SizedBox.shrink(); - }, - ), - const SizedBox(height: 16), - Expanded( - child: ListView.builder( - itemCount: state.paginatedFiles.length, - itemBuilder: (context, index) { - final file = state.paginatedFiles[index]; - final isSelected = _selectedFilePath == file.path; - final isHovered = _hovered[file.path] ?? false; - - if (file.type == FileType.folder) { - return DragTarget( - builder: (context, candidate, rejected) { - final isDraggedOver = candidate.isNotEmpty; - return Draggable( - data: file, - feedback: Opacity( - opacity: 0.8, - child: Icon( - Icons.folder, - color: AppTheme.primaryText, - size: 48, - ), + const SizedBox(width: 16), + ModernGlassButton( + onPressed: () async { + final folderName = + await _showCreateFolderDialog(context); + if (folderName != null && + folderName.isNotEmpty) { + context.read().add( + CreateFolder( + orgId: widget.orgId, + parentPath: '/', + folderName: folderName, + ), + ); + } + }, + child: const Row( + children: [ + Icon(Icons.create_new_folder), + SizedBox(width: 8), + Text('New Folder'), + ], ), - child: _buildFileItem( - file, - isSelected, - isHovered, - isDraggedOver, - ), - ); - }, - onAcceptWithDetails: (draggedFile) { - context.read().add( - MoveFile( - orgId: widget.orgId, - sourcePath: draggedFile.data.path, - targetPath: file.path, - ), - ); - }, - ); - } else { - return Draggable( - data: file, - feedback: Opacity( - opacity: 0.8, - child: Icon( - Icons.insert_drive_file, - color: AppTheme.primaryText, - size: 48, ), - ), - child: _buildFileItem( - file, - isSelected, - isHovered, - false, - ), + ], ); } + return const SizedBox.shrink(); }, ), - ), - if (state.totalPages > 1) ...[ const SizedBox(height: 16), Row( - mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( icon: const Icon( - Icons.chevron_left, + Icons.arrow_back, color: AppTheme.primaryText, ), splashColor: Colors.transparent, highlightColor: Colors.transparent, - onPressed: state.currentPage > 1 - ? () { - context.read().add( - LoadPage(state.currentPage - 1), - ); - } - : null, + onPressed: () { + final parentPath = _getParentPath(state.currentPath); + context.read().add( + LoadDirectory( + orgId: widget.orgId, + path: parentPath, + ), + ); + }, ), - Text( - '${state.currentPage} / ${state.totalPages}', - style: const TextStyle( - color: AppTheme.primaryText, - fontSize: 16, - ), - ), - IconButton( - icon: const Icon( - Icons.chevron_right, - color: AppTheme.primaryText, - ), - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - onPressed: state.currentPage < state.totalPages - ? () { - context.read().add( - LoadPage(state.currentPage + 1), - ); - } - : null, + const Text( + 'Empty Folder', + style: TextStyle(color: AppTheme.primaryText), ), ], ), ], - ], - ), - ); - } + ), + ); + } - return const SizedBox.shrink(); - }, + if (state is DirectoryLoaded) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppTheme.primaryText.withValues(alpha: 0.5), + width: 1, + ), + ), + ), + child: _buildTitle(), + ), + const SizedBox(height: 16), + Visibility( + visible: state.breadcrumbs.isNotEmpty, + child: Column( + children: [ + Row( + children: [ + IconButton( + icon: const Icon( + Icons.arrow_back, + color: AppTheme.primaryText, + ), + onPressed: () { + final parentPath = _getParentPath( + state.currentPath, + ); + context.read().add( + LoadDirectory( + orgId: widget.orgId, + path: parentPath, + ), + ); + }, + ), + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: state.breadcrumbs.map((breadcrumb) { + return TextButton( + onPressed: () { + context.read().add( + NavigateToFolder(breadcrumb.path), + ); + }, + child: Text( + '/${breadcrumb.name}', + style: const TextStyle( + color: AppTheme.secondaryText, + ), + ), + ); + }).toList(), + ), + ), + ), + ], + ), + const SizedBox(height: 16), + ], + ), + ), + BlocBuilder( + builder: (context, permState) { + if (permState is PermissionLoaded && + permState.capabilities.canWrite) { + return Row( + children: [ + ModernGlassButton( + onPressed: () async { + final result = await FilePicker.platform + .pickFiles(withData: true); + if (result != null && result.files.isNotEmpty) { + final files = result.files + .map( + (file) => FileItem( + name: file.name, + // Parent path only; server uses filename from multipart + path: state.currentPath, + type: FileType.file, + size: file.size, + lastModified: DateTime.now(), + localPath: file.path, + bytes: file.bytes, + ), + ) + .toList(); + context.read().add( + StartUpload( + files: files, + targetPath: state.currentPath, + orgId: widget.orgId, + ), + ); + } + }, + child: const Row( + children: [ + Icon(Icons.upload), + SizedBox(width: 8), + Text('Upload File'), + ], + ), + ), + const SizedBox(width: 16), + ModernGlassButton( + onPressed: () async { + final folderName = + await _showCreateFolderDialog(context); + if (folderName != null && + folderName.isNotEmpty) { + context.read().add( + CreateFolder( + orgId: widget.orgId, + parentPath: state.currentPath, + folderName: folderName, + ), + ); + } + }, + child: const Row( + children: [ + Icon(Icons.create_new_folder), + SizedBox(width: 8), + Text('New Folder'), + ], + ), + ), + ], + ); + } + return const SizedBox.shrink(); + }, + ), + const SizedBox(height: 16), + Expanded( + child: ListView.builder( + itemCount: state.paginatedFiles.length, + itemBuilder: (context, index) { + final file = state.paginatedFiles[index]; + final isSelected = _selectedFilePath == file.path; + final isHovered = _hovered[file.path] ?? false; + + if (file.type == FileType.folder) { + return DragTarget( + builder: (context, candidate, rejected) { + final isDraggedOver = candidate.isNotEmpty; + return Draggable( + data: file, + feedback: Opacity( + opacity: 0.8, + child: Icon( + Icons.folder, + color: AppTheme.primaryText, + size: 48, + ), + ), + child: _buildFileItem( + file, + isSelected, + isHovered, + isDraggedOver, + ), + ); + }, + onAcceptWithDetails: (draggedFile) { + context.read().add( + MoveFile( + orgId: widget.orgId, + sourcePath: draggedFile.data.path, + targetPath: file.path, + ), + ); + }, + ); + } else { + return Draggable( + data: file, + feedback: Opacity( + opacity: 0.8, + child: Icon( + Icons.insert_drive_file, + color: AppTheme.primaryText, + size: 48, + ), + ), + child: _buildFileItem( + file, + isSelected, + isHovered, + false, + ), + ); + } + }, + ), + ), + if (state.totalPages > 1) ...[ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: const Icon( + Icons.chevron_left, + color: AppTheme.primaryText, + ), + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onPressed: state.currentPage > 1 + ? () { + context.read().add( + LoadPage(state.currentPage - 1), + ); + } + : null, + ), + Text( + '${state.currentPage} / ${state.totalPages}', + style: const TextStyle( + color: AppTheme.primaryText, + fontSize: 16, + ), + ), + IconButton( + icon: const Icon( + Icons.chevron_right, + color: AppTheme.primaryText, + ), + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onPressed: state.currentPage < state.totalPages + ? () { + context.read().add( + LoadPage(state.currentPage + 1), + ); + } + : null, + ), + ], + ), + ], + ], + ), + ); + } + + return const SizedBox.shrink(); + }, + ), ); } diff --git a/b0esche_cloud/lib/pages/home_page.dart b/b0esche_cloud/lib/pages/home_page.dart index 1876407..0b984c1 100644 --- a/b0esche_cloud/lib/pages/home_page.dart +++ b/b0esche_cloud/lib/pages/home_page.dart @@ -345,6 +345,17 @@ class _HomePageState extends State with TickerProviderStateMixin { >( listener: (context, state) { if (state is OrganizationLoaded) { + // Show errors if present + if (state.error != null && + state.error!.isNotEmpty) { + ScaffoldMessenger.of( + context, + ).showSnackBar( + SnackBar( + content: Text(state.error!), + ), + ); + } final orgId = state.selectedOrg?.id ?? ''; // Reload file browser when org changes (or when falling back to personal workspace)