move files per drag and drop
This commit is contained in:
@@ -14,6 +14,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
int _pageSize = 20;
|
int _pageSize = 20;
|
||||||
String _sortBy = 'name';
|
String _sortBy = 'name';
|
||||||
bool _isAscending = true;
|
bool _isAscending = true;
|
||||||
|
String _currentFilter = '';
|
||||||
|
|
||||||
FileBrowserBloc(this._fileService) : super(DirectoryInitial()) {
|
FileBrowserBloc(this._fileService) : super(DirectoryInitial()) {
|
||||||
on<LoadDirectory>(_onLoadDirectory);
|
on<LoadDirectory>(_onLoadDirectory);
|
||||||
@@ -22,6 +23,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
on<ApplySort>(_onApplySort);
|
on<ApplySort>(_onApplySort);
|
||||||
on<ApplyFilter>(_onApplyFilter);
|
on<ApplyFilter>(_onApplyFilter);
|
||||||
on<CreateFolder>(_onCreateFolder);
|
on<CreateFolder>(_onCreateFolder);
|
||||||
|
on<MoveFile>(_onMoveFile);
|
||||||
on<ResetFileBrowser>(_onResetFileBrowser);
|
on<ResetFileBrowser>(_onResetFileBrowser);
|
||||||
on<LoadPage>(_onLoadPage);
|
on<LoadPage>(_onLoadPage);
|
||||||
on<ChangePageSize>(_onChangePageSize);
|
on<ChangePageSize>(_onChangePageSize);
|
||||||
@@ -39,6 +41,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
final files = await _fileService.getFiles(event.orgId, event.path);
|
final files = await _fileService.getFiles(event.orgId, event.path);
|
||||||
_currentFiles = _sortFiles(files, _sortBy, _isAscending);
|
_currentFiles = _sortFiles(files, _sortBy, _isAscending);
|
||||||
_filteredFiles = _currentFiles;
|
_filteredFiles = _currentFiles;
|
||||||
|
_currentFilter = '';
|
||||||
_currentPage = 1;
|
_currentPage = 1;
|
||||||
if (files.isEmpty) {
|
if (files.isEmpty) {
|
||||||
emit(DirectoryEmpty(_currentPath));
|
emit(DirectoryEmpty(_currentPath));
|
||||||
@@ -70,15 +73,16 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
_isAscending = event.isAscending;
|
_isAscending = event.isAscending;
|
||||||
_currentFiles = _sortFiles(_currentFiles, _sortBy, _isAscending);
|
_currentFiles = _sortFiles(_currentFiles, _sortBy, _isAscending);
|
||||||
_filteredFiles = _currentFiles
|
_filteredFiles = _currentFiles
|
||||||
.where((f) => f.name.toLowerCase().contains(''))
|
.where((f) => f.name.toLowerCase().contains(_currentFilter))
|
||||||
.toList(); // Re-apply filter if any, but since filter is separate, perhaps need to track filter
|
.toList();
|
||||||
_currentPage = 1;
|
_currentPage = 1;
|
||||||
_emitLoadedState(emit);
|
_emitLoadedState(emit);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onApplyFilter(ApplyFilter event, Emitter<FileBrowserState> emit) {
|
void _onApplyFilter(ApplyFilter event, Emitter<FileBrowserState> emit) {
|
||||||
|
_currentFilter = event.filter.toLowerCase();
|
||||||
_filteredFiles = _currentFiles
|
_filteredFiles = _currentFiles
|
||||||
.where((f) => f.name.toLowerCase().contains(event.filter.toLowerCase()))
|
.where((f) => f.name.toLowerCase().contains(_currentFilter))
|
||||||
.toList();
|
.toList();
|
||||||
_currentPage = 1;
|
_currentPage = 1;
|
||||||
_emitLoadedState(emit);
|
_emitLoadedState(emit);
|
||||||
@@ -94,12 +98,49 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
event.parentPath,
|
event.parentPath,
|
||||||
event.folderName,
|
event.folderName,
|
||||||
);
|
);
|
||||||
// Refresh the directory to show the new folder
|
// Add the new folder to local state if in current directory
|
||||||
add(LoadDirectory(orgId: event.orgId, path: event.parentPath));
|
if (event.parentPath == _currentPath) {
|
||||||
|
final newFolder = FileItem(
|
||||||
|
name: event.folderName,
|
||||||
|
path: '${event.parentPath}/${event.folderName}',
|
||||||
|
type: FileType.folder,
|
||||||
|
size: 0,
|
||||||
|
lastModified: DateTime.now(),
|
||||||
|
);
|
||||||
|
_currentFiles.add(newFolder);
|
||||||
|
_currentFiles = _sortFiles(_currentFiles, _sortBy, _isAscending);
|
||||||
|
_filteredFiles = _currentFiles
|
||||||
|
.where((f) => f.name.toLowerCase().contains(_currentFilter))
|
||||||
|
.toList();
|
||||||
|
_emitLoadedState(emit);
|
||||||
|
} else {
|
||||||
|
add(LoadDirectory(orgId: event.orgId, path: event.parentPath));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// For now, emit error state or handle appropriately
|
emit(DirectoryError(e.toString()));
|
||||||
// Since states don't have error for create, perhaps refresh anyway
|
}
|
||||||
add(LoadDirectory(orgId: event.orgId, path: event.parentPath));
|
}
|
||||||
|
|
||||||
|
void _onMoveFile(MoveFile event, Emitter<FileBrowserState> emit) async {
|
||||||
|
try {
|
||||||
|
await _fileService.moveFile(
|
||||||
|
event.orgId,
|
||||||
|
event.sourcePath,
|
||||||
|
event.targetPath,
|
||||||
|
);
|
||||||
|
// If moving into current directory, reload to get the new file details
|
||||||
|
if (event.targetPath == _currentPath) {
|
||||||
|
add(LoadDirectory(orgId: event.orgId, path: _currentPath));
|
||||||
|
} else {
|
||||||
|
// Remove the moved file if it was in current directory
|
||||||
|
_currentFiles.removeWhere((f) => f.path == event.sourcePath);
|
||||||
|
_filteredFiles = _currentFiles
|
||||||
|
.where((f) => f.name.toLowerCase().contains(_currentFilter))
|
||||||
|
.toList();
|
||||||
|
_emitLoadedState(emit);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(DirectoryError(e.toString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +151,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
emit(DirectoryInitial());
|
emit(DirectoryInitial());
|
||||||
_currentOrgId = '';
|
_currentOrgId = '';
|
||||||
_currentPath = '/';
|
_currentPath = '/';
|
||||||
|
_currentFilter = '';
|
||||||
_currentPage = 1;
|
_currentPage = 1;
|
||||||
_pageSize = 20;
|
_pageSize = 20;
|
||||||
_sortBy = 'name';
|
_sortBy = 'name';
|
||||||
@@ -133,6 +175,7 @@ class FileBrowserBloc extends Bloc<FileBrowserEvent, FileBrowserState> {
|
|||||||
final files = await _fileService.searchFiles(event.orgId, event.query);
|
final files = await _fileService.searchFiles(event.orgId, event.query);
|
||||||
_currentFiles = files;
|
_currentFiles = files;
|
||||||
_filteredFiles = files;
|
_filteredFiles = files;
|
||||||
|
_currentFilter = '';
|
||||||
_currentPage = 1;
|
_currentPage = 1;
|
||||||
if (files.isEmpty) {
|
if (files.isEmpty) {
|
||||||
emit(DirectoryEmpty(_currentPath));
|
emit(DirectoryEmpty(_currentPath));
|
||||||
|
|||||||
@@ -91,3 +91,18 @@ class SearchFiles extends FileBrowserEvent {
|
|||||||
@override
|
@override
|
||||||
List<Object> get props => [orgId, query];
|
List<Object> get props => [orgId, query];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MoveFile extends FileBrowserEvent {
|
||||||
|
final String orgId;
|
||||||
|
final String sourcePath;
|
||||||
|
final String targetPath;
|
||||||
|
|
||||||
|
const MoveFile({
|
||||||
|
required this.orgId,
|
||||||
|
required this.sourcePath,
|
||||||
|
required this.targetPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [orgId, sourcePath, targetPath];
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,11 +93,9 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
color: AppTheme.secondaryText.withValues(alpha: 0.5),
|
color: AppTheme.secondaryText.withValues(alpha: 0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: const OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
borderSide: const BorderSide(
|
borderSide: BorderSide(color: AppTheme.accentColor),
|
||||||
color: AppTheme.accentColor,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -144,21 +142,18 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _editFile(FileItem file) {
|
void _editFile(FileItem file) {
|
||||||
// Placeholder for edit action
|
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text('Edit ${file.name}')));
|
).showSnackBar(SnackBar(content: Text('Edit ${file.name}')));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _downloadFile(FileItem file) {
|
void _downloadFile(FileItem file) {
|
||||||
// Placeholder for download action
|
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text('Download ${file.name}')));
|
).showSnackBar(SnackBar(content: Text('Download ${file.name}')));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _sendFile(FileItem file) {
|
void _sendFile(FileItem file) {
|
||||||
// Placeholder for send action
|
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text('Send ${file.name}')));
|
).showSnackBar(SnackBar(content: Text('Send ${file.name}')));
|
||||||
@@ -322,9 +317,9 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
color: AppTheme.secondaryText.withValues(alpha: 0.5),
|
color: AppTheme.secondaryText.withValues(alpha: 0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: const OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||||
borderSide: const BorderSide(color: AppTheme.accentColor),
|
borderSide: BorderSide(color: AppTheme.accentColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -341,6 +336,97 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildFileItem(
|
||||||
|
FileItem file,
|
||||||
|
bool isSelected,
|
||||||
|
bool isHovered,
|
||||||
|
bool isDraggedOver,
|
||||||
|
) {
|
||||||
|
return MouseRegion(
|
||||||
|
onEnter: (_) => setState(() => _hovered[file.path] = true),
|
||||||
|
onExit: (_) => setState(() => _hovered[file.path] = false),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selectedFilePath = file.path;
|
||||||
|
});
|
||||||
|
if (file.type == FileType.folder) {
|
||||||
|
context.read<FileBrowserBloc>().add(NavigateToFolder(file.path));
|
||||||
|
} else {
|
||||||
|
context.go('/viewer/${file.name}');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: isSelected
|
||||||
|
? Border.all(
|
||||||
|
color: AppTheme.accentColor.withValues(alpha: 0.4),
|
||||||
|
width: 2,
|
||||||
|
)
|
||||||
|
: isDraggedOver
|
||||||
|
? Border.all(color: Colors.blue, width: 2)
|
||||||
|
: null,
|
||||||
|
color: isSelected
|
||||||
|
? AppTheme.accentColor.withValues(alpha: 0.08)
|
||||||
|
: isHovered
|
||||||
|
? Colors.white.withValues(alpha: 0.05)
|
||||||
|
: isDraggedOver
|
||||||
|
? Colors.blue.withValues(alpha: 0.1)
|
||||||
|
: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
file.type == FileType.folder
|
||||||
|
? Icons.folder
|
||||||
|
: Icons.insert_drive_file,
|
||||||
|
color: AppTheme.primaryText,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
file.type == FileType.folder
|
||||||
|
? (file.name.startsWith('/') ? file.name : '/${file.name}')
|
||||||
|
: file.name,
|
||||||
|
style: const TextStyle(color: AppTheme.primaryText),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
file.type == FileType.folder
|
||||||
|
? 'Folder'
|
||||||
|
: 'File - ${file.size} bytes',
|
||||||
|
style: const TextStyle(color: AppTheme.secondaryText),
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit, color: AppTheme.secondaryText),
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
onPressed: () => _editFile(file),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.download,
|
||||||
|
color: AppTheme.secondaryText,
|
||||||
|
),
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
onPressed: () => _downloadFile(file),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.send, color: AppTheme.secondaryText),
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
onPressed: () => _sendFile(file),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FileBrowserBloc, FileBrowserState>(
|
return BlocBuilder<FileBrowserBloc, FileBrowserState>(
|
||||||
@@ -350,6 +436,7 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
child: CircularProgressIndicator(color: AppTheme.accentColor),
|
child: CircularProgressIndicator(color: AppTheme.accentColor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is DirectoryError) {
|
if (state is DirectoryError) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -358,6 +445,7 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is DirectoryEmpty) {
|
if (state is DirectoryEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@@ -425,7 +513,7 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
context.read<FileBrowserBloc>().add(
|
context.read<FileBrowserBloc>().add(
|
||||||
CreateFolder(
|
CreateFolder(
|
||||||
orgId: 'org1',
|
orgId: 'org1',
|
||||||
parentPath: state.currentPath,
|
parentPath: '/',
|
||||||
folderName: folderName,
|
folderName: folderName,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -456,21 +544,9 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
splashColor: Colors.transparent,
|
splashColor: Colors.transparent,
|
||||||
highlightColor: Colors.transparent,
|
highlightColor: Colors.transparent,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final parentPath = state.currentPath == '/'
|
final currentPath = '/';
|
||||||
? '/'
|
|
||||||
: state.currentPath
|
|
||||||
.substring(
|
|
||||||
0,
|
|
||||||
state.currentPath.lastIndexOf('/'),
|
|
||||||
)
|
|
||||||
.isEmpty
|
|
||||||
? '/'
|
|
||||||
: state.currentPath.substring(
|
|
||||||
0,
|
|
||||||
state.currentPath.lastIndexOf('/'),
|
|
||||||
);
|
|
||||||
context.read<FileBrowserBloc>().add(
|
context.read<FileBrowserBloc>().add(
|
||||||
LoadDirectory(orgId: 'org1', path: parentPath),
|
LoadDirectory(orgId: 'org1', path: currentPath),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -484,6 +560,7 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is DirectoryLoaded) {
|
if (state is DirectoryLoaded) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@@ -502,7 +579,6 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
child: _buildTitle(),
|
child: _buildTitle(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
// Breadcrumbs and back button
|
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: state.breadcrumbs.isNotEmpty,
|
visible: state.breadcrumbs.isNotEmpty,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -625,111 +701,61 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
final file = state.paginatedFiles[index];
|
final file = state.paginatedFiles[index];
|
||||||
final isSelected = _selectedFilePath == file.path;
|
final isSelected = _selectedFilePath == file.path;
|
||||||
final isHovered = _hovered[file.path] ?? false;
|
final isHovered = _hovered[file.path] ?? false;
|
||||||
return MouseRegion(
|
|
||||||
onEnter: (_) =>
|
if (file.type == FileType.folder) {
|
||||||
setState(() => _hovered[file.path] = true),
|
return DragTarget<FileItem>(
|
||||||
onExit: (_) =>
|
builder: (context, candidate, rejected) {
|
||||||
setState(() => _hovered[file.path] = false),
|
final isDraggedOver = candidate.isNotEmpty;
|
||||||
child: GestureDetector(
|
return Draggable<FileItem>(
|
||||||
onTap: () {
|
data: file,
|
||||||
setState(() {
|
feedback: Opacity(
|
||||||
_selectedFilePath = file.path;
|
opacity: 0.8,
|
||||||
});
|
child: Icon(
|
||||||
if (file.type == FileType.folder) {
|
Icons.folder,
|
||||||
context.read<FileBrowserBloc>().add(
|
|
||||||
NavigateToFolder(file.path),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Open viewer
|
|
||||||
context.go('/viewer/${file.name}');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
vertical: 4,
|
|
||||||
horizontal: 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: isSelected
|
|
||||||
? Border.all(
|
|
||||||
color: AppTheme.accentColor.withValues(
|
|
||||||
alpha: 0.4,
|
|
||||||
),
|
|
||||||
width: 2,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
color: isSelected
|
|
||||||
? AppTheme.accentColor.withValues(alpha: 0.08)
|
|
||||||
: isHovered
|
|
||||||
? Colors.white.withValues(alpha: 0.05)
|
|
||||||
: Colors.transparent,
|
|
||||||
),
|
|
||||||
child: ListTile(
|
|
||||||
leading: Icon(
|
|
||||||
file.type == FileType.folder
|
|
||||||
? Icons.folder
|
|
||||||
: Icons.insert_drive_file,
|
|
||||||
color: AppTheme.primaryText,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
file.type == FileType.folder
|
|
||||||
? (file.name.startsWith('/')
|
|
||||||
? file.name
|
|
||||||
: '/${file.name}')
|
|
||||||
: file.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: AppTheme.primaryText,
|
color: AppTheme.primaryText,
|
||||||
|
size: 48,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
child: _buildFileItem(
|
||||||
file.type == FileType.folder
|
file,
|
||||||
? 'Folder'
|
isSelected,
|
||||||
: 'File - ${file.size} bytes',
|
isHovered,
|
||||||
style: const TextStyle(
|
isDraggedOver,
|
||||||
color: AppTheme.secondaryText,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
trailing: Row(
|
);
|
||||||
mainAxisSize: MainAxisSize.min,
|
},
|
||||||
children: [
|
onAccept: (draggedFile) {
|
||||||
IconButton(
|
context.read<FileBrowserBloc>().add(
|
||||||
icon: const Icon(
|
MoveFile(
|
||||||
Icons.edit,
|
orgId: 'org1',
|
||||||
color: AppTheme.secondaryText,
|
sourcePath: draggedFile.path,
|
||||||
),
|
targetPath: file.path,
|
||||||
splashColor: Colors.transparent,
|
|
||||||
highlightColor: Colors.transparent,
|
|
||||||
onPressed: () => _editFile(file),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.download,
|
|
||||||
color: AppTheme.secondaryText,
|
|
||||||
),
|
|
||||||
splashColor: Colors.transparent,
|
|
||||||
highlightColor: Colors.transparent,
|
|
||||||
onPressed: () => _downloadFile(file),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.send,
|
|
||||||
color: AppTheme.secondaryText,
|
|
||||||
),
|
|
||||||
splashColor: Colors.transparent,
|
|
||||||
highlightColor: Colors.transparent,
|
|
||||||
onPressed: () => _sendFile(file),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Pagination controls
|
|
||||||
if (state.totalPages > 1) ...[
|
if (state.totalPages > 1) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Row(
|
Row(
|
||||||
@@ -779,6 +805,7 @@ class _FileExplorerState extends State<FileExplorer> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ abstract class FileRepository {
|
|||||||
Future<void> uploadFile(String orgId, FileItem file);
|
Future<void> uploadFile(String orgId, FileItem file);
|
||||||
Future<void> deleteFile(String orgId, String path);
|
Future<void> deleteFile(String orgId, String path);
|
||||||
Future<void> createFolder(String orgId, String parentPath, String folderName);
|
Future<void> createFolder(String orgId, String parentPath, String folderName);
|
||||||
|
Future<void> moveFile(String orgId, String sourcePath, String targetPath);
|
||||||
Future<List<FileItem>> searchFiles(String orgId, String query);
|
Future<List<FileItem>> searchFiles(String orgId, String query);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,6 +222,28 @@ class MockFileRepository implements FileRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> moveFile(
|
||||||
|
String orgId,
|
||||||
|
String sourcePath,
|
||||||
|
String targetPath,
|
||||||
|
) async {
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
final fileIndex = _files.indexWhere((f) => f.path == sourcePath);
|
||||||
|
if (fileIndex != -1) {
|
||||||
|
final file = _files[fileIndex];
|
||||||
|
final newName = file.path.split('/').last;
|
||||||
|
final newPath = targetPath == '/' ? '/$newName' : '$targetPath/$newName';
|
||||||
|
_files[fileIndex] = FileItem(
|
||||||
|
name: file.name,
|
||||||
|
path: newPath,
|
||||||
|
type: file.type,
|
||||||
|
size: file.size,
|
||||||
|
lastModified: DateTime.now(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<FileItem>> searchFiles(String orgId, String query) async {
|
Future<List<FileItem>> searchFiles(String orgId, String query) async {
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
|||||||
@@ -45,6 +45,17 @@ class FileService {
|
|||||||
await _fileRepository.createFolder(orgId, parentPath, folderName);
|
await _fileRepository.createFolder(orgId, parentPath, folderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> moveFile(
|
||||||
|
String orgId,
|
||||||
|
String sourcePath,
|
||||||
|
String targetPath,
|
||||||
|
) async {
|
||||||
|
if (sourcePath.isEmpty || targetPath.isEmpty) {
|
||||||
|
throw Exception('Paths cannot be empty');
|
||||||
|
}
|
||||||
|
await _fileRepository.moveFile(orgId, sourcePath, targetPath);
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<FileItem>> searchFiles(String orgId, String query) async {
|
Future<List<FileItem>> searchFiles(String orgId, String query) async {
|
||||||
if (query.isEmpty) {
|
if (query.isEmpty) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
Reference in New Issue
Block a user