import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:loader_overlay/loader_overlay.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import '../../../api/marianumcloud/webdav/queries/listFiles/cacheableFile.dart'; import '../../../state/app/infrastructure/loadableState/loadable_state.dart'; import '../../../state/app/infrastructure/loadableState/view/loadable_state_consumer.dart'; import '../../../state/app/infrastructure/utilityWidgets/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 '../../../widget/filePick.dart'; import '../../../widget/placeholderView.dart'; import 'fileElement.dart'; import 'filesUploadDialog.dart'; class BetterSortOption { String displayName; int Function(CacheableFile, CacheableFile) compare; IconData icon; BetterSortOption({required this.displayName, required this.icon, required this.compare}); } enum SortOption { name, date, size } class SortOptions { static Map options = { SortOption.name: BetterSortOption( displayName: 'Name', icon: Icons.sort_by_alpha_outlined, compare: (a, b) => a.name.compareTo(b.name), ), SortOption.date: BetterSortOption( displayName: 'Datum', icon: Icons.history_outlined, compare: (a, b) => a.modifiedAt!.compareTo(b.modifiedAt!), ), SortOption.size: BetterSortOption( displayName: 'Größe', icon: Icons.sd_card_outlined, compare: (a, b) { if (a.isDirectory || b.isDirectory) return a.isDirectory ? 1 : 0; if (a.size == null) return 0; if (b.size == null) return 1; return a.size!.compareTo(b.size!); }, ), }; static BetterSortOption getOption(SortOption option) => options[option]!; } 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; @override void initState() { super.initState(); settings = context.read(); currentSort = settings.val().fileSettings.sortBy; currentSortDirection = settings.val().fileSettings.ascending; } Future mediaUpload(List? paths) async { if (paths == null) return; final bloc = context.read(); 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: [ PopupMenuButton( icon: Icon(currentSortDirection ? Icons.text_rotate_up : Icons.text_rotation_down), itemBuilder: (context) => [true, false] .map((e) => PopupMenuItem( value: e, enabled: e != currentSortDirection, child: Row( children: [ Icon( e ? Icons.text_rotate_up : Icons.text_rotation_down, color: Theme.of(context).colorScheme.onSurface, ), const SizedBox(width: 15), Text(e ? 'Aufsteigend' : 'Absteigend'), ], ), )) .toList(), onSelected: (e) { setState(() { currentSortDirection = e; settings.val(write: true).fileSettings.ascending = e; }); }, ), PopupMenuButton( icon: const Icon(Icons.sort), itemBuilder: (context) => SortOptions.options.keys .map((key) => PopupMenuItem( value: key, enabled: key != currentSort, child: Row( children: [ Icon(SortOptions.getOption(key).icon, color: Theme.of(context).colorScheme.onSurface), const SizedBox(width: 15), Text(SortOptions.getOption(key).displayName), ], ), )) .toList(), onSelected: (e) { setState(() { currentSort = e; settings.val(write: true).fileSettings.sortBy = e; }); }, ), ], ), floatingActionButton: FloatingActionButton( heroTag: 'uploadFile', backgroundColor: Theme.of(context).primaryColor, onPressed: () => _showAddDialog(context, bloc), child: const Icon(Icons.add), ), body: LoadableStateConsumer( child: (state, _) { final listing = state.listing; if (listing == null) return const SizedBox.shrink(); 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 LoaderOverlay( child: ListView.builder( padding: EdgeInsets.zero, itemCount: files.length, itemBuilder: (context, index) => FileElement(files[index], widget.path, bloc.refresh), ), ); }, ), ); } void _showAddDialog(BuildContext context, FilesBloc bloc) { showDialog( context: context, builder: (dialogCtx) => SimpleDialog(children: [ ListTile( leading: const Icon(Icons.create_new_folder_outlined), title: const Text('Ordner erstellen'), onTap: () { Navigator.of(dialogCtx).pop(); _showCreateFolderDialog(context, bloc); }, ), ListTile( leading: const Icon(Icons.upload_file), title: const Text('Aus Dateien hochladen'), onTap: () { FilePick.documentPick().then(mediaUpload); Navigator.of(dialogCtx).pop(); }, ), Visibility( visible: !Platform.isIOS, child: ListTile( leading: const Icon(Icons.add_a_photo_outlined), title: const Text('Aus Gallerie hochladen'), onTap: () { FilePick.multipleGalleryPick().then((value) { if (value != null) mediaUpload(value.map((e) => e.path).toList()); }); Navigator.of(dialogCtx).pop(); }, ), ), ]), ); } void _showCreateFolderDialog(BuildContext context, FilesBloc bloc) { final inputController = TextEditingController(); showDialog( context: context, builder: (dialogCtx) => AlertDialog( title: const Text('Neuer Ordner'), content: TextField( controller: inputController, decoration: const InputDecoration(labelText: 'Name'), ), actions: [ TextButton(onPressed: () => Navigator.of(dialogCtx).pop(), child: const Text('Abbrechen')), TextButton( onPressed: () { bloc.createFolder(inputController.text); Navigator.of(dialogCtx).pop(); }, child: const Text('Ordner erstellen'), ), ], ), ); } }