refactored broad range of the application, split files, modularized calendar and file views, centralized bottom sheets and clipboard handling, and implemented unit test coverage

This commit is contained in:
2026-05-08 19:05:16 +02:00
parent 3b1b0d0c19
commit c62a14645a
68 changed files with 4633 additions and 3141 deletions
@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import '../../../../state/app/modules/files/bloc/files_bloc.dart';
import '../../../../widget/async_action_button.dart';
import '../../../../widget/details_bottom_sheet.dart';
import '../../../../widget/file_pick.dart';
/// Opens the "Element hinzufügen" sheet (create folder, upload, take photo, …).
/// [onPickedFiles] receives selected/captured file paths (gallery, file picker
/// or camera) and is responsible for kicking off the upload flow.
void showAddFileSheet(
BuildContext context, {
required FilesBloc bloc,
required Future<void> Function(List<String>? paths) onPickedFiles,
}) {
showDetailsBottomSheet(
context,
children: (sheetCtx) => [
ListTile(
leading: const Icon(Icons.create_new_folder_outlined),
title: const Text('Ordner erstellen'),
onTap: () {
Navigator.of(sheetCtx).pop();
_showCreateFolderDialog(context, bloc);
},
),
ListTile(
leading: const Icon(Icons.upload_file),
title: const Text('Aus Dateien hochladen'),
onTap: () {
FilePick.documentPick().then(onPickedFiles);
Navigator.of(sheetCtx).pop();
},
),
ListTile(
leading: const Icon(Icons.add_a_photo_outlined),
title: const Text('Aus Galerie hochladen'),
onTap: () {
FilePick.multipleGalleryPick().then((value) {
if (value != null) onPickedFiles(value.map((e) => e.path).toList());
});
Navigator.of(sheetCtx).pop();
},
),
ListTile(
leading: const Icon(Icons.camera_alt_outlined),
title: const Text('Foto aufnehmen'),
onTap: () {
FilePick.cameraPick().then((image) {
if (image != null) onPickedFiles([image.path]);
});
Navigator.of(sheetCtx).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'),
autofocus: true,
),
actions: [
AsyncDialogAction(
confirmLabel: 'Ordner erstellen',
onConfirm: () async {
if (inputController.text.trim().isEmpty) {
throw Exception('Bitte einen Namen eingeben.');
}
await bloc.createFolder(inputController.text.trim());
},
),
],
),
);
}