Files
stadtbote-app/lib/popups/files_popup.dart
T
2026-02-14 08:52:06 +01:00

263 lines
7.7 KiB
Dart

import 'package:flutter/material.dart';
import 'package:stadtbote/content_views/pdf_viewer_page.dart';
import 'package:stadtbote/model/tour_file.dart';
import 'package:stadtbote/tools/constants.dart';
import 'package:stadtbote/tools/util.dart' show showAlertDialog;
import 'package:stadtbote/tools/util.dart' as util;
void showFilesPopup(BuildContext context, List<TourFile> files) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Dateien'),
content: SizedBox(
width: double.maxFinite,
height: 400,
child: ListView.builder(
shrinkWrap: true,
itemCount: files.length,
itemBuilder: (context, index) {
final file = files[index];
return _FileListItem(file: file);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Schließen'),
),
],
);
},
);
}
class _FileListItem extends StatefulWidget {
final TourFile file;
const _FileListItem({required this.file});
@override
State<_FileListItem> createState() => _FileListItemState();
}
class _FileListItemState extends State<_FileListItem> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
IconData iconData;
if (widget.file.isPdf) {
iconData = Icons.picture_as_pdf;
} else if (widget.file.isImage) {
iconData = Icons.image;
} else {
iconData = Icons.insert_drive_file;
}
return Card(
margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
leading: Icon(iconData, color: Constants.colorPrimary),
title: Text(widget.file.name),
subtitle: widget.file.remark != null && widget.file.remark!.isNotEmpty
? Text(widget.file.remark!)
: null,
trailing: _isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.open_in_new),
onTap: () => _openFile(context),
),
);
}
Future<void> _openFile(BuildContext context) async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
try {
if (widget.file.isPdf) {
Navigator.of(context).pop(); // Close the files dialog
// Navigate to PDF viewer
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PdfViewerPage(
pdfUrl: widget.file.url,
title: widget.file.name,
),
),
);
} else if (widget.file.isImage) {
Navigator.of(context).pop(); // Close the files dialog
// Show image in a dialog
await showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
insetPadding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AppBar(
title: Text(widget.file.name),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
automaticallyImplyLeading: false,
),
Flexible(
child: Image.network(
widget.file.url,
fit: BoxFit.contain,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: Colors.red,
),
const SizedBox(height: 16),
Text(
'Fehler beim Laden des Bildes:\n$error',
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red),
),
],
),
);
},
),
),
],
),
);
},
);
} else {
// For other file types, try to open with external app
showAlertDialog(
'Information',
'Dateityp wird nicht direkt unterstützt. Die Datei wird im Browser geöffnet.',
);
}
} catch (e) {
util.Util.logException('Error opening file: $e');
if (context.mounted) {
showAlertDialog(
'Fehler',
'Fehler beim Öffnen der Datei: $e',
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
}
/// Widget to display a file icon button if files are available
class FilesIconButton extends StatelessWidget {
final List<TourFile>? files;
final double iconSize;
const FilesIconButton({
super.key,
required this.files,
this.iconSize = 24,
});
@override
Widget build(BuildContext context) {
if (files == null || files!.isEmpty) {
return const SizedBox.shrink();
}
return IconButton(
icon: const Icon(Icons.attach_file),
iconSize: iconSize,
color: Constants.colorPrimary,
tooltip: '${files!.length} Datei(en)',
onPressed: () => showFilesPopup(context, files!),
);
}
}
/// Badge widget showing file count with icon
class FilesBadge extends StatelessWidget {
final List<TourFile>? files;
final VoidCallback? onTap;
const FilesBadge({
super.key,
required this.files,
this.onTap,
});
@override
Widget build(BuildContext context) {
if (files == null || files!.isEmpty) {
return const SizedBox.shrink();
}
return GestureDetector(
onTap: onTap ?? () => showFilesPopup(context, files!),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Constants.colorPrimary.withAlpha(30),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Constants.colorPrimary, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.attach_file,
size: 16,
color: Constants.colorPrimary,
),
const SizedBox(width: 4),
Text(
'${files!.length}',
style: TextStyle(
color: Constants.colorPrimary,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
),
),
);
}
}