import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import '../../../state/app/infrastructure/loadable_state/loadable_state.dart'; import '../../../state/app/infrastructure/loadable_state/view/loadable_state_consumer.dart'; import '../../../state/app/infrastructure/utility_widgets/bloc_module.dart'; import '../../../state/app/modules/files/bloc/files_bloc.dart'; import '../../../state/app/modules/files/bloc/files_state.dart'; import '../../../state/app/modules/settings/bloc/settings_cubit.dart'; import '../../../utils/cache_invalidation_bus.dart'; import '../../../widget/placeholder_view.dart'; import 'data/sort_options.dart'; import 'files_upload_dialog.dart'; import 'widgets/add_file_menu.dart'; import 'widgets/clipboard_banner.dart'; import 'widgets/file_element.dart'; import 'widgets/files_sort_actions.dart'; class Files extends StatelessWidget { final List path; Files({List? path, super.key}) : path = path ?? []; @override Widget build(BuildContext context) => BlocModule>( create: (_) => FilesBloc(initialPath: path), child: (context, _, _) => _FilesView(path: path), ); } class _FilesView extends StatefulWidget { final List path; const _FilesView({required this.path}); @override State<_FilesView> createState() => _FilesViewState(); } class _FilesViewState extends State<_FilesView> { late final SettingsCubit settings; late SortOption currentSort; late bool currentSortDirection; late final StreamSubscription _invalidationSub; // Cache key in FilesBloc's pathString format: '/' for root, otherwise // segments joined without leading/trailing slash. String get _myPathString => widget.path.isEmpty ? '/' : widget.path.join('/'); // Relative folder path matching the WebDAV format used by `CacheableFile.path` // (no leading slash; trailing slash for non-root). Empty string means root. String get _currentFolderPath => widget.path.isEmpty ? '' : '${widget.path.join('/')}/'; @override void initState() { super.initState(); settings = context.read(); currentSort = settings.val().fileSettings.sortBy; currentSortDirection = settings.val().fileSettings.ascending; _invalidationSub = CacheInvalidationBus.listFilesStream.listen(_onInvalidation); } void _onInvalidation(String invalidatedPath) { if (!mounted) return; if (invalidatedPath != _myPathString) return; context.read().refresh(); } @override void dispose() { _invalidationSub.cancel(); super.dispose(); } Future _mediaUpload(List? paths) async { if (paths == null) return; final bloc = context.read(); unawaited(pushScreen( context, withNavBar: false, screen: FilesUploadDialog( filePaths: paths, remotePath: widget.path.join('/'), onUploadFinished: (_) => bloc.refresh(), ), )); } @override Widget build(BuildContext context) { final bloc = context.read(); return Scaffold( appBar: AppBar( title: Text(widget.path.isNotEmpty ? widget.path.last : 'Dateien'), actions: [ FilesSortActions( currentSort: currentSort, ascending: currentSortDirection, onDirectionChanged: (e) { setState(() { currentSortDirection = e; settings.val(write: true).fileSettings.ascending = e; }); }, onSortChanged: (e) { setState(() { currentSort = e; settings.val(write: true).fileSettings.sortBy = e; }); }, ), ], ), floatingActionButton: FloatingActionButton( heroTag: 'uploadFile', backgroundColor: Theme.of(context).primaryColor, onPressed: () => showAddFileSheet(context, bloc: bloc, onPickedFiles: _mediaUpload), child: const Icon(Icons.add), ), body: Column( children: [ ClipboardBanner(currentFolder: _currentFolderPath, onPasteDone: bloc.refresh), Expanded( child: LoadableStateConsumer( isReady: (state) => state.listing != null, child: (state, _) { final listing = state.listing!; if (listing.files.isEmpty) { return const PlaceholderView(icon: Icons.folder_off_rounded, text: 'Der Ordner ist leer'); } final files = listing.sortBy( sortOption: currentSort, foldersToTop: context.watch().val().fileSettings.sortFoldersToTop, reversed: currentSortDirection, ); return ListView.builder( padding: EdgeInsets.zero, itemCount: files.length, itemBuilder: (context, index) => FileElement(files[index], widget.path, bloc.refresh), ); }, ), ), ], ), ); } }