reworked operation (#12)

Reviewed-on: #12
Co-authored-by: Mark M2 Macbook <marco@catelli.it>
Co-committed-by: Mark M2 Macbook <marco@catelli.it>
This commit is contained in:
2026-05-04 15:36:42 +02:00
committed by brontomark
parent 9f57207a39
commit 94ad524bae
110 changed files with 5831 additions and 5306 deletions

View File

@@ -0,0 +1,220 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flux/core/utils/functions.dart';
import 'package:flux/features/attachments/models/attachment_model.dart';
import 'package:pdfx/pdfx.dart';
import 'package:internet_file/internet_file.dart';
class AttachmentViewerScreen extends StatefulWidget {
final AttachmentModel attachment;
final Function(String newName)? onRename;
final VoidCallback? onDelete;
const AttachmentViewerScreen({
super.key,
required this.attachment,
this.onRename,
this.onDelete,
});
@override
State<AttachmentViewerScreen> createState() => _AttachmentViewerScreenState();
}
class _AttachmentViewerScreenState extends State<AttachmentViewerScreen> {
PdfControllerPinch? _pdfController;
bool _isLoading = true;
String? _errorMessage;
Uint8List? _fileBytes;
late String _fileName;
bool get isPdf => widget.attachment.extension.toLowerCase() == 'pdf';
@override
void initState() {
super.initState();
_fileName = widget.attachment.name;
_loadFile();
}
Future<void> _loadFile() async {
try {
// 1. Capiamo da dove prendere i dati
if (widget.attachment.localBytes != null) {
_fileBytes = widget.attachment.localBytes;
} else if (widget.attachment.storagePath != null &&
widget.attachment.storagePath!.isNotEmpty) {
final signedUrl = await getSignedUrl(widget.attachment.storagePath!);
_fileBytes = await InternetFile.get(signedUrl);
} else {
throw Exception("Nessun documento trovato o byte mancanti.");
}
// 2. Se è PDF, inizializziamo il controller
if (isPdf && _fileBytes != null) {
_pdfController = PdfControllerPinch(
document: PdfDocument.openData(_fileBytes!),
);
}
if (mounted) setState(() => _isLoading = false);
} catch (e) {
if (mounted) {
setState(() {
_isLoading = false;
_errorMessage = e.toString();
});
}
}
}
@override
void dispose() {
_pdfController?.dispose();
super.dispose();
}
void _showRenameDialog() {
final ctrl = TextEditingController(text: _fileName);
ctrl.selection = TextSelection(
baseOffset: 0,
extentOffset: ctrl.text.length,
);
final focusNode = FocusNode();
showDialog(
context: context,
builder: (context) {
WidgetsBinding.instance.addPostFrameCallback(
(_) => focusNode.requestFocus(),
);
return AlertDialog(
title: const Text('Rinomina File'),
content: TextField(
controller: ctrl,
focusNode: focusNode,
decoration: InputDecoration(
labelText: 'Nuovo nome',
suffixText: '.${widget.attachment.extension}',
),
onSubmitted: (val) {
Navigator.pop(context);
if (val.trim().isNotEmpty && widget.onRename != null) {
setState(() {
_fileName = val.trim();
});
widget.onRename!(val.trim());
}
},
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Annulla'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
if (ctrl.text.trim().isNotEmpty && widget.onRename != null) {
setState(() {
_fileName = ctrl.text.trim();
});
widget.onRename!(ctrl.text.trim());
}
},
child: const Text('Salva'),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black87, // Sfondo scuro per i viewer è il top
appBar: AppBar(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
title: Text(_fileName, style: const TextStyle(fontSize: 16)),
actions: [
if (widget.onRename != null)
IconButton(
icon: const Icon(Icons.edit),
tooltip: 'Rinomina',
onPressed: _showRenameDialog,
),
if (widget.onDelete != null)
IconButton(
icon: const Icon(Icons.delete, color: Colors.redAccent),
tooltip: 'Elimina',
onPressed: () {
// Chiediamo conferma
showDialog(
context: context,
builder: (c) => AlertDialog(
title: const Text('Eliminare file?'),
content: const Text(
'Sei sicuro di voler eliminare questo allegato?',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(c),
child: const Text('Annulla'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
),
onPressed: () {
Navigator.pop(c); // Chiude dialog
widget.onDelete!(); // Lancia eliminazione
Navigator.pop(context); // Chiude il viewer
},
child: const Text('Elimina'),
),
],
),
);
},
),
],
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_isLoading) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
}
if (_errorMessage != null) {
return Center(
child: Text(
'Errore: $_errorMessage',
style: const TextStyle(color: Colors.redAccent),
),
);
}
if (_fileBytes == null) {
return const Center(
child: Text(
'File non disponibile',
style: TextStyle(color: Colors.white),
),
);
}
if (isPdf && _pdfController != null) {
return PdfViewPinch(controller: _pdfController!);
} else {
return InteractiveViewer(
maxScale: 5.0,
child: Center(child: Image.memory(_fileBytes!)),
);
}
}
}