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 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 _openFile(BuildContext context) async { if (_isLoading) return; setState(() { _isLoading = true; }); try { if (widget.file.isPdf) { // Show PDF in a dialog on top of the files dialog await showDialog( context: context, builder: (BuildContext context) { return Dialog( insetPadding: const EdgeInsets.all(8), child: Column( children: [ AppBar( title: Text(widget.file.name), leading: IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), automaticallyImplyLeading: false, ), Expanded( child: PdfViewerPage( pdfUrl: widget.file.url, title: widget.file.name, ), ), ], ), ); }, ); } else if (widget.file.isImage) { // Bildanhänge wie PDFs vergrößern und im gezoomten Zustand verschieben. await showDialog( context: context, builder: (BuildContext context) { return Dialog( insetPadding: const EdgeInsets.all(16), child: Column( children: [ AppBar( title: Text(widget.file.name), leading: IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), automaticallyImplyLeading: false, ), Expanded( child: InteractiveViewer( minScale: 1, maxScale: 5, child: SizedBox.expand( 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? 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? 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, ), ), ], ), ), ); } }