Add error handling for organization loading in HomePage
This commit is contained in:
@@ -12,6 +12,7 @@ import '../blocs/permission/permission_event.dart';
|
|||||||
import '../blocs/permission/permission_state.dart';
|
import '../blocs/permission/permission_state.dart';
|
||||||
import '../blocs/upload/upload_bloc.dart';
|
import '../blocs/upload/upload_bloc.dart';
|
||||||
import '../blocs/upload/upload_event.dart';
|
import '../blocs/upload/upload_event.dart';
|
||||||
|
import '../blocs/upload/upload_state.dart';
|
||||||
import '../models/file_item.dart';
|
import '../models/file_item.dart';
|
||||||
import '../theme/app_theme.dart';
|
import '../theme/app_theme.dart';
|
||||||
import '../theme/modern_glass_button.dart';
|
import '../theme/modern_glass_button.dart';
|
||||||
@@ -661,393 +662,416 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FileBrowserBloc, FileBrowserState>(
|
return BlocListener<UploadBloc, UploadState>(
|
||||||
builder: (context, state) {
|
listener: (context, uploadState) {
|
||||||
if (state is DirectoryLoading) {
|
if (uploadState is UploadInProgress) {
|
||||||
return Center(
|
final hasCompleted = uploadState.uploads.any((u) => u.isCompleted);
|
||||||
child: CircularProgressIndicator(color: AppTheme.accentColor),
|
if (hasCompleted) {
|
||||||
);
|
final fbState = context.read<FileBrowserBloc>().state;
|
||||||
|
String currentPath = '/';
|
||||||
|
if (fbState is DirectoryLoaded) currentPath = fbState.currentPath;
|
||||||
|
if (fbState is DirectoryEmpty) currentPath = fbState.currentPath;
|
||||||
|
context.read<FileBrowserBloc>().add(
|
||||||
|
LoadDirectory(orgId: widget.orgId, path: currentPath),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
child: BlocBuilder<FileBrowserBloc, FileBrowserState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is DirectoryLoading) {
|
||||||
|
return Center(
|
||||||
|
child: CircularProgressIndicator(color: AppTheme.accentColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (state is DirectoryError) {
|
if (state is DirectoryError) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Error: ${state.error}',
|
'Error: ${state.error}',
|
||||||
style: const TextStyle(color: AppTheme.primaryText),
|
style: const TextStyle(color: AppTheme.primaryText),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is DirectoryEmpty) {
|
if (state is DirectoryEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: AppTheme.primaryText.withValues(alpha: 0.5),
|
color: AppTheme.primaryText.withValues(alpha: 0.5),
|
||||||
width: 1,
|
width: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: _buildTitle(),
|
||||||
),
|
),
|
||||||
child: _buildTitle(),
|
const SizedBox(height: 16),
|
||||||
),
|
BlocBuilder<PermissionBloc, PermissionState>(
|
||||||
const SizedBox(height: 16),
|
builder: (context, permState) {
|
||||||
BlocBuilder<PermissionBloc, PermissionState>(
|
if (permState is PermissionLoaded &&
|
||||||
builder: (context, permState) {
|
permState.capabilities.canWrite) {
|
||||||
if (permState is PermissionLoaded &&
|
return Row(
|
||||||
permState.capabilities.canWrite) {
|
children: [
|
||||||
return Row(
|
ModernGlassButton(
|
||||||
children: [
|
onPressed: () async {
|
||||||
ModernGlassButton(
|
final result = await FilePicker.platform
|
||||||
onPressed: () async {
|
.pickFiles(withData: true);
|
||||||
final result = await FilePicker.platform
|
if (result != null && result.files.isNotEmpty) {
|
||||||
.pickFiles();
|
final files = result.files
|
||||||
if (result != null && result.files.isNotEmpty) {
|
.map(
|
||||||
final files = result.files
|
(file) => FileItem(
|
||||||
.map(
|
name: file.name,
|
||||||
(file) => FileItem(
|
// Parent path only; server uses filename from multipart
|
||||||
name: file.name,
|
path: state.currentPath,
|
||||||
path: '/${file.name}',
|
type: FileType.file,
|
||||||
type: FileType.file,
|
size: file.size,
|
||||||
size: file.size,
|
lastModified: DateTime.now(),
|
||||||
lastModified: DateTime.now(),
|
localPath: file.path,
|
||||||
localPath: file.path,
|
bytes: file.bytes,
|
||||||
bytes: file.bytes,
|
),
|
||||||
),
|
)
|
||||||
)
|
.toList();
|
||||||
.toList();
|
context.read<UploadBloc>().add(
|
||||||
context.read<UploadBloc>().add(
|
StartUpload(
|
||||||
StartUpload(
|
files: files,
|
||||||
files: files,
|
targetPath: state.currentPath,
|
||||||
targetPath: '/',
|
orgId: widget.orgId,
|
||||||
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<FileBrowserBloc>().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<FileBrowserBloc>().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<FileBrowserBloc>().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<FileBrowserBloc>().add(
|
|
||||||
NavigateToFolder(breadcrumb.path),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'/${breadcrumb.name}',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppTheme.secondaryText,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}
|
||||||
|
},
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.upload),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Upload File'),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 16),
|
||||||
],
|
ModernGlassButton(
|
||||||
),
|
onPressed: () async {
|
||||||
const SizedBox(height: 16),
|
final folderName =
|
||||||
],
|
await _showCreateFolderDialog(context);
|
||||||
),
|
if (folderName != null &&
|
||||||
),
|
folderName.isNotEmpty) {
|
||||||
BlocBuilder<PermissionBloc, PermissionState>(
|
context.read<FileBrowserBloc>().add(
|
||||||
builder: (context, permState) {
|
CreateFolder(
|
||||||
if (permState is PermissionLoaded &&
|
orgId: widget.orgId,
|
||||||
permState.capabilities.canWrite) {
|
parentPath: '/',
|
||||||
return Row(
|
folderName: folderName,
|
||||||
children: [
|
),
|
||||||
ModernGlassButton(
|
);
|
||||||
onPressed: () async {
|
}
|
||||||
final result = await FilePicker.platform
|
},
|
||||||
.pickFiles();
|
child: const Row(
|
||||||
if (result != null && result.files.isNotEmpty) {
|
children: [
|
||||||
final files = result.files
|
Icon(Icons.create_new_folder),
|
||||||
.map(
|
SizedBox(width: 8),
|
||||||
(file) => FileItem(
|
Text('New Folder'),
|
||||||
name: file.name,
|
],
|
||||||
path: '/${file.name}',
|
|
||||||
type: FileType.file,
|
|
||||||
size: file.size,
|
|
||||||
lastModified: DateTime.now(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
context.read<UploadBloc>().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<FileBrowserBloc>().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<FileItem>(
|
|
||||||
builder: (context, candidate, rejected) {
|
|
||||||
final isDraggedOver = candidate.isNotEmpty;
|
|
||||||
return Draggable<FileItem>(
|
|
||||||
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<FileBrowserBloc>().add(
|
|
||||||
MoveFile(
|
|
||||||
orgId: widget.orgId,
|
|
||||||
sourcePath: draggedFile.data.path,
|
|
||||||
targetPath: file.path,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Draggable<FileItem>(
|
|
||||||
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),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.chevron_left,
|
Icons.arrow_back,
|
||||||
color: AppTheme.primaryText,
|
color: AppTheme.primaryText,
|
||||||
),
|
),
|
||||||
splashColor: Colors.transparent,
|
splashColor: Colors.transparent,
|
||||||
highlightColor: Colors.transparent,
|
highlightColor: Colors.transparent,
|
||||||
onPressed: state.currentPage > 1
|
onPressed: () {
|
||||||
? () {
|
final parentPath = _getParentPath(state.currentPath);
|
||||||
context.read<FileBrowserBloc>().add(
|
context.read<FileBrowserBloc>().add(
|
||||||
LoadPage(state.currentPage - 1),
|
LoadDirectory(
|
||||||
);
|
orgId: widget.orgId,
|
||||||
}
|
path: parentPath,
|
||||||
: null,
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Text(
|
const Text(
|
||||||
'${state.currentPage} / ${state.totalPages}',
|
'Empty Folder',
|
||||||
style: const TextStyle(
|
style: TextStyle(color: AppTheme.primaryText),
|
||||||
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<FileBrowserBloc>().add(
|
|
||||||
LoadPage(state.currentPage + 1),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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<FileBrowserBloc>().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<FileBrowserBloc>().add(
|
||||||
|
NavigateToFolder(breadcrumb.path),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'/${breadcrumb.name}',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: AppTheme.secondaryText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocBuilder<PermissionBloc, PermissionState>(
|
||||||
|
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<UploadBloc>().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<FileBrowserBloc>().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<FileItem>(
|
||||||
|
builder: (context, candidate, rejected) {
|
||||||
|
final isDraggedOver = candidate.isNotEmpty;
|
||||||
|
return Draggable<FileItem>(
|
||||||
|
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<FileBrowserBloc>().add(
|
||||||
|
MoveFile(
|
||||||
|
orgId: widget.orgId,
|
||||||
|
sourcePath: draggedFile.data.path,
|
||||||
|
targetPath: file.path,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Draggable<FileItem>(
|
||||||
|
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<FileBrowserBloc>().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<FileBrowserBloc>().add(
|
||||||
|
LoadPage(state.currentPage + 1),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -345,6 +345,17 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
>(
|
>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state is OrganizationLoaded) {
|
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 =
|
final orgId =
|
||||||
state.selectedOrg?.id ?? '';
|
state.selectedOrg?.id ?? '';
|
||||||
// Reload file browser when org changes (or when falling back to personal workspace)
|
// Reload file browser when org changes (or when falling back to personal workspace)
|
||||||
|
|||||||
Reference in New Issue
Block a user