From b0a6da9a0d421f9b956f5ab62f4040d01572d45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Tue, 16 Dec 2025 21:19:26 +0100 Subject: [PATCH] third commit --- .../blocs/file_browser/file_browser_bloc.dart | 115 +++- .../file_browser/file_browser_event.dart | 28 + .../file_browser/file_browser_state.dart | 19 +- b0esche_cloud/lib/main.dart | 10 +- b0esche_cloud/lib/pages/file_explorer.dart | 531 +++++++++++++++--- .../lib/repositories/file_repository.dart | 1 + .../repositories/mock_file_repository.dart | 8 + b0esche_cloud/lib/services/file_service.dart | 7 + 8 files changed, 614 insertions(+), 105 deletions(-) diff --git a/b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart b/b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart index d826283..26bc74f 100644 --- a/b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart +++ b/b0esche_cloud/lib/blocs/file_browser/file_browser_bloc.dart @@ -9,6 +9,10 @@ class FileBrowserBloc extends Bloc { String _currentOrgId = ''; String _currentPath = '/'; List _currentFiles = []; + List _filteredFiles = []; + int _currentPage = 1; + int _pageSize = 20; + String _sortBy = 'name'; FileBrowserBloc(this._fileService) : super(DirectoryInitial()) { on(_onLoadDirectory); @@ -18,6 +22,9 @@ class FileBrowserBloc extends Bloc { on(_onApplyFilter); on(_onCreateFolder); on(_onResetFileBrowser); + on(_onLoadPage); + on(_onChangePageSize); + on(_onSearchFiles); } void _onLoadDirectory( @@ -29,19 +36,13 @@ class FileBrowserBloc extends Bloc { _currentPath = event.path; try { final files = await _fileService.getFiles(event.orgId, event.path); - final breadcrumbs = _generateBreadcrumbs(event.path); - _currentFiles = files; + _currentFiles = _sortFiles(files, _sortBy); + _filteredFiles = _currentFiles; + _currentPage = 1; if (files.isEmpty) { emit(DirectoryEmpty()); } else { - emit( - DirectoryLoaded( - files: files, - filteredFiles: files, - breadcrumbs: breadcrumbs, - currentPath: event.path, - ), - ); + _emitLoadedState(emit); } } catch (e) { emit(DirectoryError(e.toString())); @@ -64,24 +65,21 @@ class FileBrowserBloc extends Bloc { } void _onApplySort(ApplySort event, Emitter emit) { - // Implement sorting - // For now, just refresh - add(RefreshDirectory()); + _sortBy = event.sortBy; + _currentFiles = _sortFiles(_currentFiles, _sortBy); + _filteredFiles = _currentFiles + .where((f) => f.name.toLowerCase().contains('')) + .toList(); // Re-apply filter if any, but since filter is separate, perhaps need to track filter + _currentPage = 1; + _emitLoadedState(emit); } void _onApplyFilter(ApplyFilter event, Emitter emit) { - // Implement filtering - final filtered = _currentFiles + _filteredFiles = _currentFiles .where((f) => f.name.toLowerCase().contains(event.filter.toLowerCase())) .toList(); - emit( - DirectoryLoaded( - files: _currentFiles, - filteredFiles: filtered, - breadcrumbs: _generateBreadcrumbs(_currentPath), - currentPath: _currentPath, - ), - ); + _currentPage = 1; + _emitLoadedState(emit); } void _onCreateFolder( @@ -110,6 +108,77 @@ class FileBrowserBloc extends Bloc { emit(DirectoryInitial()); _currentOrgId = ''; _currentPath = '/'; + _currentPage = 1; + _pageSize = 20; + _sortBy = 'name'; + } + + void _onLoadPage(LoadPage event, Emitter emit) { + _currentPage = event.page; + _emitLoadedState(emit); + } + + void _onChangePageSize(ChangePageSize event, Emitter emit) { + _pageSize = event.pageSize; + _currentPage = 1; // Reset to first page + _emitLoadedState(emit); + } + + void _onSearchFiles(SearchFiles event, Emitter emit) async { + emit(DirectoryLoading()); + try { + final files = await _fileService.searchFiles(event.orgId, event.query); + _currentFiles = files; + _filteredFiles = files; + _currentPage = 1; + if (files.isEmpty) { + emit(DirectoryEmpty()); + } else { + _emitLoadedState(emit); + } + } catch (e) { + emit(DirectoryError(e.toString())); + } + } + + void _emitLoadedState(Emitter emit) { + final paginatedFiles = _getPaginatedFiles(); + final totalPages = (_filteredFiles.length / _pageSize).ceil(); + emit( + DirectoryLoaded( + files: _currentFiles, + filteredFiles: _filteredFiles, + paginatedFiles: paginatedFiles, + breadcrumbs: _generateBreadcrumbs(_currentPath), + currentPath: _currentPath, + currentPage: _currentPage, + pageSize: _pageSize, + totalPages: totalPages, + ), + ); + } + + List _getPaginatedFiles() { + final start = (_currentPage - 1) * _pageSize; + final end = start + _pageSize; + return _filteredFiles.sublist(start, end.clamp(0, _filteredFiles.length)); + } + + List _sortFiles(List files, String sortBy) { + final sorted = List.from(files); + sorted.sort((a, b) { + switch (sortBy) { + case 'name': + return a.name.compareTo(b.name); + case 'date': + return b.lastModified.compareTo(a.lastModified); + case 'size': + return b.size.compareTo(a.size); + default: + return a.name.compareTo(b.name); + } + }); + return sorted; } List _generateBreadcrumbs(String path) { diff --git a/b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart b/b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart index 2ff6cdb..e37a41b 100644 --- a/b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart +++ b/b0esche_cloud/lib/blocs/file_browser/file_browser_event.dart @@ -62,3 +62,31 @@ class CreateFolder extends FileBrowserEvent { } class ResetFileBrowser extends FileBrowserEvent {} + +class LoadPage extends FileBrowserEvent { + final int page; + + const LoadPage(this.page); + + @override + List get props => [page]; +} + +class ChangePageSize extends FileBrowserEvent { + final int pageSize; + + const ChangePageSize(this.pageSize); + + @override + List get props => [pageSize]; +} + +class SearchFiles extends FileBrowserEvent { + final String orgId; + final String query; + + const SearchFiles({required this.orgId, required this.query}); + + @override + List get props => [orgId, query]; +} diff --git a/b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart b/b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart index a977d6d..9dc8c5f 100644 --- a/b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart +++ b/b0esche_cloud/lib/blocs/file_browser/file_browser_state.dart @@ -25,18 +25,35 @@ class DirectoryLoading extends FileBrowserState {} class DirectoryLoaded extends FileBrowserState { final List files; final List filteredFiles; + final List paginatedFiles; final List breadcrumbs; final String currentPath; + final int currentPage; + final int pageSize; + final int totalPages; const DirectoryLoaded({ required this.files, required this.filteredFiles, + required this.paginatedFiles, required this.breadcrumbs, required this.currentPath, + required this.currentPage, + required this.pageSize, + required this.totalPages, }); @override - List get props => [files, filteredFiles, breadcrumbs, currentPath]; + List get props => [ + files, + filteredFiles, + paginatedFiles, + breadcrumbs, + currentPath, + currentPage, + pageSize, + totalPages, + ]; } class DirectoryEmpty extends FileBrowserState {} diff --git a/b0esche_cloud/lib/main.dart b/b0esche_cloud/lib/main.dart index cbcf1da..b042910 100644 --- a/b0esche_cloud/lib/main.dart +++ b/b0esche_cloud/lib/main.dart @@ -7,22 +7,28 @@ import 'blocs/organization/organization_bloc.dart'; import 'blocs/permission/permission_bloc.dart'; import 'blocs/file_browser/file_browser_bloc.dart'; import 'blocs/upload/upload_bloc.dart'; -import 'services/file_service.dart'; import 'repositories/mock_file_repository.dart'; -import 'theme/app_theme.dart'; +import 'services/file_service.dart'; import 'pages/home_page.dart'; import 'pages/login_form.dart'; +import 'pages/file_explorer.dart'; import 'pages/document_viewer.dart'; +import 'theme/app_theme.dart'; final GoRouter _router = GoRouter( routes: [ GoRoute(path: '/', builder: (context, state) => const HomePage()), GoRoute(path: '/login', builder: (context, state) => const LoginForm()), + GoRoute( path: '/viewer/:fileId', builder: (context, state) => DocumentViewer(fileId: state.pathParameters['fileId']!), ), + GoRoute( + path: '/org/:orgId/drive', + builder: (context, state) => const FileExplorer(), + ), ], ); diff --git a/b0esche_cloud/lib/pages/file_explorer.dart b/b0esche_cloud/lib/pages/file_explorer.dart index c77ddcd..8ef8dec 100644 --- a/b0esche_cloud/lib/pages/file_explorer.dart +++ b/b0esche_cloud/lib/pages/file_explorer.dart @@ -6,10 +6,13 @@ import '../blocs/file_browser/file_browser_bloc.dart'; import '../blocs/file_browser/file_browser_event.dart'; 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/upload/upload_bloc.dart'; import '../blocs/upload/upload_event.dart'; import '../models/file_item.dart'; +import '../theme/app_theme.dart'; +import '../theme/modern_glass_button.dart'; class FileExplorer extends StatefulWidget { const FileExplorer({super.key}); @@ -19,6 +22,18 @@ class FileExplorer extends StatefulWidget { } class _FileExplorerState extends State { + String? _selectedFilePath; + bool _isSearching = false; + bool _showField = false; + final TextEditingController _searchController = TextEditingController(); + String _searchQuery = ''; + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + @override void initState() { super.initState(); @@ -26,6 +41,217 @@ class _FileExplorerState extends State { context.read().add( LoadDirectory(orgId: 'org1', path: '/'), ); + context.read().add(LoadPermissions('org1')); + } + + Future _showCreateFolderDialog(BuildContext context) async { + final TextEditingController controller = TextEditingController(); + return showDialog( + context: context, + builder: (BuildContext context) { + return Dialog( + backgroundColor: Colors.transparent, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: Container( + decoration: AppTheme.glassDecoration, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'New Folder', + style: const TextStyle( + color: AppTheme.primaryText, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + TextField( + controller: controller, + autofocus: true, + style: const TextStyle(color: AppTheme.primaryText), + decoration: InputDecoration( + hintText: 'Enter folder name', + hintStyle: const TextStyle(color: AppTheme.secondaryText), + contentPadding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 12, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + color: AppTheme.accentColor.withValues(alpha: 0.5), + ), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide( + color: AppTheme.secondaryText.withValues(alpha: 0.5), + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide(color: AppTheme.accentColor), + ), + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text( + 'Cancel', + style: TextStyle(color: AppTheme.primaryText), + ), + ), + TextButton( + onPressed: () { + final folderName = controller.text.trim(); + if (folderName.isNotEmpty) { + final nameWithSlash = folderName.startsWith('/') + ? folderName + : '/$folderName'; + Navigator.of(context).pop(nameWithSlash); + } + }, + child: const Text( + 'Create', + style: TextStyle(color: AppTheme.accentColor), + ), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ); + } + + void _editFile(FileItem file) { + // Placeholder for edit action + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Edit ${file.name}'))); + } + + void _downloadFile(FileItem file) { + // Placeholder for download action + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Download ${file.name}'))); + } + + void _sendFile(FileItem file) { + // Placeholder for send action + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Send ${file.name}'))); + } + + Widget _buildTitle() { + const double titleWidth = 72.0; + return SizedBox( + width: double.infinity, + height: 50, + child: Stack( + alignment: Alignment.centerLeft, + children: [ + Positioned( + left: 0, + child: const Text( + '/Drive', + style: TextStyle( + fontSize: 24, + color: AppTheme.primaryText, + fontWeight: FontWeight.bold, + ), + ), + ), + AnimatedPositioned( + left: _isSearching ? titleWidth + 250.0 : titleWidth, + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + child: Padding( + padding: const EdgeInsets.only(top: 2), + child: IconButton( + icon: Icon( + _isSearching ? Icons.close : Icons.search, + color: AppTheme.accentColor, + ), + onPressed: () { + if (_isSearching) { + setState(() { + _showField = false; + _isSearching = false; + _searchController.clear(); + _searchQuery = ''; + context.read().add(ApplyFilter('')); + }); + } else { + setState(() { + _isSearching = true; + }); + Future.delayed(const Duration(milliseconds: 150), () { + setState(() { + _showField = true; + }); + }); + } + }, + ), + ), + ), + if (_showField) + AnimatedPositioned( + left: titleWidth, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: SizedBox( + width: 250, + child: TextField( + controller: _searchController, + autofocus: true, + style: const TextStyle(color: AppTheme.primaryText), + decoration: InputDecoration( + hintText: 'Search Files...', + hintStyle: const TextStyle(color: AppTheme.secondaryText), + contentPadding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 12, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide( + color: AppTheme.secondaryText.withValues(alpha: 0.5), + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(20), + borderSide: BorderSide(color: AppTheme.accentColor), + ), + ), + onChanged: (value) { + _searchQuery = value; + context.read().add( + ApplyFilter(_searchQuery), + ); + }, + ), + ), + ), + ], + ), + ); } @override @@ -39,7 +265,7 @@ class _FileExplorerState extends State { return Center( child: Text( 'Error: ${state.error}', - style: const TextStyle(color: Colors.white), + style: const TextStyle(color: AppTheme.primaryText), ), ); } @@ -49,16 +275,25 @@ class _FileExplorerState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Drive', - style: TextStyle(fontSize: 24, color: Colors.white), + Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppTheme.primaryText.withValues(alpha: 0.5), + width: 1, + ), + ), + ), + child: _buildTitle(), ), const SizedBox(height: 16), - // Back button Row( children: [ IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: const Icon( + Icons.arrow_back, + color: AppTheme.primaryText, + ), onPressed: () { context.read().add( LoadDirectory(orgId: 'org1', path: '/'), @@ -67,7 +302,7 @@ class _FileExplorerState extends State { ), const Text( 'Empty Folder', - style: TextStyle(color: Colors.white), + style: TextStyle(color: AppTheme.primaryText), ), ], ), @@ -76,31 +311,66 @@ class _FileExplorerState extends State { builder: (context, permState) { if (permState is PermissionLoaded && permState.capabilities.canWrite) { - return ElevatedButton( - 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(), + 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: 'org1', ), - ) - .toList(); - context.read().add( - StartUpload( - files: files, - targetPath: '/', - orgId: 'org1', - ), - ); - } - }, - child: const Text('Upload File'), + ); + } + }, + 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: 'org1', + parentPath: '/', + folderName: folderName, + ), + ); + } + }, + child: const Row( + children: [ + Icon(Icons.create_new_folder), + SizedBox(width: 8), + Text('New Folder'), + ], + ), + ), + ], ); } return const SizedBox.shrink(); @@ -116,9 +386,16 @@ class _FileExplorerState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Drive', - style: TextStyle(fontSize: 24, color: Colors.white), + Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppTheme.primaryText.withValues(alpha: 0.5), + width: 1, + ), + ), + ), + child: _buildTitle(), ), const SizedBox(height: 16), // Breadcrumbs and back button @@ -131,7 +408,7 @@ class _FileExplorerState extends State { IconButton( icon: const Icon( Icons.arrow_back, - color: Colors.white, + color: AppTheme.primaryText, ), onPressed: () { context.read().add( @@ -153,7 +430,7 @@ class _FileExplorerState extends State { child: Text( '${breadcrumb.name}/', style: const TextStyle( - color: Colors.white70, + color: AppTheme.secondaryText, ), ), ); @@ -171,31 +448,67 @@ class _FileExplorerState extends State { builder: (context, permState) { if (permState is PermissionLoaded && permState.capabilities.canWrite) { - return ElevatedButton( - 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(), + 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: 'org1', ), - ) - .toList(); - context.read().add( - StartUpload( - files: files, - targetPath: '/', - orgId: 'org1', - ), - ); - } - }, - child: const Text('Upload File'), + ); + } + }, + 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: 'org1', + parentPath: state.currentPath, + folderName: folderName, + ), + ); + } + }, + child: const Row( + children: [ + Icon(Icons.create_new_folder), + SizedBox(width: 8), + Text('New Folder'), + ], + ), + ), + ], ); } return const SizedBox.shrink(); @@ -204,27 +517,15 @@ class _FileExplorerState extends State { const SizedBox(height: 16), Expanded( child: ListView.builder( - itemCount: state.files.length, + itemCount: state.filteredFiles.length, itemBuilder: (context, index) { - final file = state.files[index]; - return ListTile( - leading: Icon( - file.type == FileType.folder - ? Icons.folder - : Icons.insert_drive_file, - color: Colors.white, - ), - title: Text( - file.name, - style: const TextStyle(color: Colors.white), - ), - subtitle: Text( - file.type == FileType.folder - ? 'Folder' - : 'File - ${file.size} bytes', - style: const TextStyle(color: Colors.white70), - ), + final file = state.filteredFiles[index]; + final isSelected = _selectedFilePath == file.path; + return GestureDetector( onTap: () { + setState(() { + _selectedFilePath = file.path; + }); if (file.type == FileType.folder) { context.read().add( NavigateToFolder(file.path), @@ -234,6 +535,78 @@ class _FileExplorerState extends State { 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) + : 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, + ), + onPressed: () => _editFile(file), + ), + IconButton( + icon: const Icon( + Icons.download, + color: AppTheme.secondaryText, + ), + onPressed: () => _downloadFile(file), + ), + IconButton( + icon: const Icon( + Icons.send, + color: AppTheme.secondaryText, + ), + onPressed: () => _sendFile(file), + ), + ], + ), + ), + ), ); }, ), diff --git a/b0esche_cloud/lib/repositories/file_repository.dart b/b0esche_cloud/lib/repositories/file_repository.dart index 8a63a0d..5a88ef6 100644 --- a/b0esche_cloud/lib/repositories/file_repository.dart +++ b/b0esche_cloud/lib/repositories/file_repository.dart @@ -6,4 +6,5 @@ abstract class FileRepository { Future uploadFile(String orgId, FileItem file); Future deleteFile(String orgId, String path); Future createFolder(String orgId, String parentPath, String folderName); + Future> searchFiles(String orgId, String query); } diff --git a/b0esche_cloud/lib/repositories/mock_file_repository.dart b/b0esche_cloud/lib/repositories/mock_file_repository.dart index c787feb..b0495e9 100644 --- a/b0esche_cloud/lib/repositories/mock_file_repository.dart +++ b/b0esche_cloud/lib/repositories/mock_file_repository.dart @@ -78,4 +78,12 @@ class MockFileRepository implements FileRepository { ), ); } + + @override + Future> searchFiles(String orgId, String query) async { + await Future.delayed(const Duration(seconds: 1)); + return _files + .where((f) => f.name.toLowerCase().contains(query.toLowerCase())) + .toList(); + } } diff --git a/b0esche_cloud/lib/services/file_service.dart b/b0esche_cloud/lib/services/file_service.dart index e309ec2..55cb2e3 100644 --- a/b0esche_cloud/lib/services/file_service.dart +++ b/b0esche_cloud/lib/services/file_service.dart @@ -44,4 +44,11 @@ class FileService { } await _fileRepository.createFolder(orgId, parentPath, folderName); } + + Future> searchFiles(String orgId, String query) async { + if (query.isEmpty) { + return []; + } + return await _fileRepository.searchFiles(orgId, query); + } }