feat-insert-service #5

Merged
brontomark merged 11 commits from feat-insert-service into main 2026-04-20 16:52:20 +02:00
15 changed files with 397 additions and 72 deletions
Showing only changes of commit 8dc1c661ed - Show all commits

View File

@@ -31,7 +31,7 @@ extension MyStringExtensions on String? {
String fileNameWithoutExtension() { String fileNameWithoutExtension() {
if (this == null || this!.trim().isEmpty) return ''; if (this == null || this!.trim().isEmpty) return '';
this!.replaceAll(RegExp(r'[^a-zA-Z0-9\.\-]'), '_');
final parts = this!.split('.'); final parts = this!.split('.');
if (parts.length < 2) return this!; // Nessuna estensione trovata if (parts.length < 2) return this!; // Nessuna estensione trovata

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
class ImageViewerWidget extends StatelessWidget {
final String url;
const ImageViewerWidget({super.key, required this.url});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
),
body: InteractiveViewer(
// InteractiveViewer dà lo zoom gratis alle immagini!
child: Center(child: Image.network(url)),
),
);
}
}

View File

@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:pdfx/pdfx.dart';
import 'package:internet_file/internet_file.dart'; // flutter pub add internet_file
class PdfViewerWidget extends StatefulWidget {
final String url;
const PdfViewerWidget({super.key, required this.url});
@override
State<PdfViewerWidget> createState() => _PdfViewerWidgetState();
}
class _PdfViewerWidgetState extends State<PdfViewerWidget> {
late PdfControllerPinch _pdfController;
bool _isLoading = true;
@override
void initState() {
super.initState();
_initPdf();
}
Future<void> _initPdf() async {
// Scarica il file in memoria in modo fluido
final pdfData = await InternetFile.get(widget.url);
_pdfController = PdfControllerPinch(
document: PdfDocument.openData(pdfData),
);
if (mounted) setState(() => _isLoading = false);
}
@override
void dispose() {
_pdfController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
return Scaffold(
// Usiamo Scaffold dentro il Dialog per avere l'AppBar e poter chiudere
appBar: AppBar(
title: const Text("Visualizzatore PDF"),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
),
body: PdfViewPinch(
controller: _pdfController,
// pdfx gestisce nativamente il pinch to zoom!
),
);
}
}

View File

@@ -90,7 +90,7 @@ class CustomerRepository {
} }
/// Salva il riferimento del file nel DB /// Salva il riferimento del file nel DB
Future<void> saveFileReference(CustomerFileModel file) async { Future<void> saveCustomerFile(CustomerFileModel file) async {
await _supabase.from('customer_file').insert(file.toMap()); await _supabase.from('customer_file').insert(file.toMap());
} }
@@ -149,6 +149,10 @@ class CustomerRepository {
} }
} }
Future<void> saveFileReference(CustomerFileModel file) async {
await _supabase.from('customer_file').upsert(file.toMap());
}
/// Aggiorna la lista degli URL nel database /// Aggiorna la lista degli URL nel database
Future<void> updateCustomerDocuments(int id, List<String> urls) async { Future<void> updateCustomerDocuments(int id, List<String> urls) async {
await _supabase await _supabase

View File

@@ -3,10 +3,12 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_bloc.dart'; import 'package:flux/core/blocs/session/session_bloc.dart';
import 'package:flux/core/utils/string_extensions.dart';
import 'package:flux/features/services/data/services_repository.dart'; import 'package:flux/features/services/data/services_repository.dart';
import 'package:flux/features/services/models/energy_service_model.dart'; import 'package:flux/features/services/models/energy_service_model.dart';
import 'package:flux/features/services/models/entertainment_service_model.dart'; import 'package:flux/features/services/models/entertainment_service_model.dart';
import 'package:flux/features/services/models/fin_service_model.dart'; import 'package:flux/features/services/models/fin_service_model.dart';
import 'package:flux/features/services/models/service_file_model.dart';
import 'package:flux/features/services/models/service_model.dart'; import 'package:flux/features/services/models/service_model.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@@ -130,7 +132,7 @@ class ServicesCubit extends Cubit<ServicesState> {
companyId: _sessionBloc.state.company!.id, companyId: _sessionBloc.state.company!.id,
), ),
status: ServicesStatus.ready, status: ServicesStatus.ready,
localAttachments: [], files: [],
), ),
); );
} }
@@ -210,7 +212,7 @@ class ServicesCubit extends Cubit<ServicesState> {
final serviceToSave = state.currentService!.copyWith(isBozza: isBozza); final serviceToSave = state.currentService!.copyWith(isBozza: isBozza);
// 2. Salvataggio corazzato // 2. Salvataggio corazzato
await _repository.saveFullService(serviceToSave, state.localAttachments); await _repository.saveFullService(serviceToSave, state.files);
// 3. Reset e ricaricamento // 3. Reset e ricaricamento
emit(state.copyWith(status: ServicesStatus.saved, currentService: null)); emit(state.copyWith(status: ServicesStatus.saved, currentService: null));
@@ -228,14 +230,90 @@ class ServicesCubit extends Cubit<ServicesState> {
// --- GESTIONE ALLEGATI LOCALI --- // --- GESTIONE ALLEGATI LOCALI ---
void addAttachments(List<PlatformFile> files) { void addAttachments(List<PlatformFile> files) {
// Aggiungiamo i nuovi file a quelli già presenti in memoria // Trasformiamo i PlatformFile in ServiceFileModel "temporanei"
final updatedList = [...state.localAttachments, ...files]; final newAttachments = files.map((file) {
emit(state.copyWith(localAttachments: updatedList)); return ServiceFileModel(
id: '', // ID vuoto perché non ancora su DB
serviceId: state.currentService?.id ?? '',
name: file.name.fileNameWithoutExtension(),
extension: file.name.fileExtension(),
url: '', // URL vuoto perché non ancora caricato
fileSize: file.size,
localBytes: file.bytes, // Fondamentale per l'upload!
createdAt: DateTime.now(),
);
}).toList();
// Uniamo i file esistenti (remoti + locali già aggiunti) con i nuovi
final updatedList = [
...(state.currentService?.files ?? []),
...newAttachments,
];
emit(
state.copyWith(
currentService: state.currentService?.copyWith(files: updatedList),
),
);
} }
void removeLocalAttachment(int index) { void removeAttachment(int index) {
final updatedList = List<PlatformFile>.from(state.localAttachments); if (state.currentService == null) return;
final updatedList = List<ServiceFileModel>.from(
state.currentService!.files,
);
updatedList.removeAt(index); updatedList.removeAt(index);
emit(state.copyWith(localAttachments: updatedList));
emit(
state.copyWith(
currentService: state.currentService?.copyWith(files: updatedList),
),
);
}
void saveAndCopyFileToCustomer(ServiceFileModel file) async {
final currentService = state.currentService;
if (currentService == null || currentService.customerId == null) {
// Magari mostra un errore: non posso copiare al cliente se non c'è un cliente!
return;
}
emit(state.copyWith(status: ServicesStatus.loading));
try {
// 1. Salviamo la pratica (Bozza o definitiva che sia)
// Questo assicura che il file sia stato caricato su Storage e censito su DB
await saveCurrentService(isBozza: currentService.isBozza);
// 2. Recuperiamo il file "aggiornato"
// Dopo il saveCurrentService, il file che prima era "locale" ora ha un URL.
// Lo cerchiamo nella lista aggiornata per nome o estensione.
final savedFile = state.currentService!.files.firstWhere(
(f) => f.name == file.name && f.extension == file.extension,
orElse: () => file,
);
if (savedFile.url.isEmpty) {
throw Exception("Errore: URL del file non trovato dopo il salvataggio.");
}
// 3. Chiamiamo il repository per la copia fisica nel database del cliente
// Passiamo l'URL del file e l'ID del cliente
await _repository.copyFileToCustomer(
file: savedFile,
customerId: currentService.customerId!,
);
// 4. Feedback all'utente
// Potresti emettere un successo o mostrare un toast
emit(state.copyWith(status: ServicesStatus.success));
} catch (e) {
emit(state.copyWith(
status: ServicesStatus.failure,
errorMessage: "Errore durante la copia del file: $e",
));
}
} }
} }

View File

@@ -10,7 +10,7 @@ class ServicesState extends Equatable {
final String query; final String query;
final DateTimeRange? dateRange; final DateTimeRange? dateRange;
final bool hasReachedMax; final bool hasReachedMax;
final List<PlatformFile> localAttachments; final List<ServiceFileModel> files;
const ServicesState({ const ServicesState({
required this.status, required this.status,
@@ -20,7 +20,7 @@ class ServicesState extends Equatable {
this.query = '', this.query = '',
this.dateRange, this.dateRange,
this.hasReachedMax = false, this.hasReachedMax = false,
this.localAttachments = const [], this.files = const [],
}); });
ServicesState copyWith({ ServicesState copyWith({
@@ -31,7 +31,7 @@ class ServicesState extends Equatable {
String? query, String? query,
DateTimeRange? dateRange, DateTimeRange? dateRange,
bool? hasReachedMax, bool? hasReachedMax,
List<PlatformFile>? localAttachments, List<ServiceFileModel>? files,
}) { }) {
return ServicesState( return ServicesState(
status: status ?? this.status, status: status ?? this.status,
@@ -41,7 +41,7 @@ class ServicesState extends Equatable {
query: query ?? this.query, query: query ?? this.query,
dateRange: dateRange ?? this.dateRange, dateRange: dateRange ?? this.dateRange,
hasReachedMax: hasReachedMax ?? this.hasReachedMax, hasReachedMax: hasReachedMax ?? this.hasReachedMax,
localAttachments: localAttachments ?? this.localAttachments, files: files ?? this.files,
); );
} }
@@ -54,6 +54,6 @@ class ServicesState extends Equatable {
query, query,
dateRange, dateRange,
hasReachedMax, hasReachedMax,
localAttachments, files,
]; ];
} }

View File

@@ -1,7 +1,7 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flux/core/blocs/session/session_bloc.dart'; import 'package:flux/core/blocs/session/session_bloc.dart';
import 'package:flux/core/utils/string_extensions.dart'; import 'package:flux/features/customers/data/customer_repository.dart';
import 'package:flux/features/customers/models/customer_file_model.dart';
import 'package:flux/features/services/models/service_file_model.dart'; import 'package:flux/features/services/models/service_file_model.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
@@ -10,6 +10,7 @@ import '../models/service_model.dart';
class ServicesRepository { class ServicesRepository {
final _supabase = Supabase.instance.client; final _supabase = Supabase.instance.client;
final companyId = GetIt.I.get<SessionBloc>().state.company!.id; final companyId = GetIt.I.get<SessionBloc>().state.company!.id;
final CustomerRepository _customerRepository = GetIt.I<CustomerRepository>();
// --- RECUPERO SINGOLO SERVIZIO CON JOIN COMPLETO --- // --- RECUPERO SINGOLO SERVIZIO CON JOIN COMPLETO ---
Future<ServiceModel> fetchServiceById(String id) async { Future<ServiceModel> fetchServiceById(String id) async {
@@ -84,7 +85,7 @@ class ServicesRepository {
// --- SALVATAGGIO COMPLETO (PRIMA PADRE, POI FIGLI) --- // --- SALVATAGGIO COMPLETO (PRIMA PADRE, POI FIGLI) ---
Future<void> saveFullService( Future<void> saveFullService(
ServiceModel service, ServiceModel service,
List<PlatformFile> localFiles, List<ServiceFileModel> files,
) async { ) async {
try { try {
// 1. Upsert del record principale // 1. Upsert del record principale
@@ -152,40 +153,27 @@ class ServicesRepository {
if (insertTasks.isNotEmpty) { if (insertTasks.isNotEmpty) {
await Future.wait(insertTasks); await Future.wait(insertTasks);
} }
if (localFiles.isNotEmpty) { if (files.isNotEmpty) {
final List<Future> uploadTasks = []; final List<Future> uploadTasks = [];
for (var file in localFiles) { for (var file in files) {
// Puliamo il nome del file per evitare problemi con spazi o caratteri strani
final cleanFileName = file.name.replaceAll(
RegExp(r'[^a-zA-Z0-9\.\-]'),
'_',
);
final storagePath = final storagePath =
'$companyId/services/$newId/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName'; '$companyId/services/$newId/${DateTime.now().millisecondsSinceEpoch}_${file.name}.${file.extension}';
final String mimeType = file.extension.toLowerCase() == 'pdf'
final int fileSize = file.size; ? 'application/pdf'
: 'image/${file.extension}';
final fileToSave = ServiceFileModel( final fileToSave = file.copyWith(serviceId: newId);
name: cleanFileName.fileNameWithoutExtension(),
extension: cleanFileName.fileExtension(),
url: '',
serviceId: newId,
fileSize: fileSize,
);
// Creiamo una funzione asincrona per caricare file e scrivere nel DB // Creiamo una funzione asincrona per caricare file e scrivere nel DB
Future<void> uploadAndLink() async { Future<void> uploadAndLink() async {
// Determiniamo il MIME type corretto in base all'estensione // Determiniamo il MIME type corretto in base all'estensione
final String mimeType = fileToSave.extension.toLowerCase() == 'pdf'
? 'application/pdf'
: 'image/${fileToSave.extension}';
// A. Upload nel Bucket Storage (usiamo i bytes così funziona anche su Web!) // A. Upload nel Bucket Storage (usiamo i bytes così funziona anche su Web!)
await _supabase.storage await _supabase.storage
.from('documents') .from('documents')
.uploadBinary( .uploadBinary(
storagePath, storagePath,
file.bytes!, fileToSave.localBytes!,
fileOptions: FileOptions( fileOptions: FileOptions(
contentType: contentType:
mimeType, // Diciamo a Supabase esattamente cos'è! mimeType, // Diciamo a Supabase esattamente cos'è!
@@ -255,4 +243,18 @@ class ServicesRepository {
]; // Fallback se non c'è ancora storia ]; // Fallback se non c'è ancora storia
} }
} }
Future<void> copyFileToCustomer({
required ServiceFileModel file,
required String customerId,
}) async {
CustomerFileModel fileToCopy = CustomerFileModel(
customerId: customerId,
name: file.name,
url: file.url,
extension: file.extension,
fileSize: file.fileSize,
);
await _customerRepository.saveCustomerFile(fileToCopy);
}
} }

View File

@@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class ServiceFileModel extends Equatable { class ServiceFileModel extends Equatable {
@@ -7,7 +9,8 @@ class ServiceFileModel extends Equatable {
final String extension; final String extension;
final String url; final String url;
final String serviceId; final String serviceId;
final int fileSize; // <--- Aggiunto final int fileSize;
final Uint8List? localBytes;
const ServiceFileModel({ const ServiceFileModel({
this.id, this.id,
@@ -17,6 +20,7 @@ class ServiceFileModel extends Equatable {
required this.url, required this.url,
required this.serviceId, required this.serviceId,
required this.fileSize, required this.fileSize,
this.localBytes,
}); });
// Trasforma i byte in qualcosa di leggibile (KB, MB, GB) // Trasforma i byte in qualcosa di leggibile (KB, MB, GB)
@@ -39,6 +43,7 @@ class ServiceFileModel extends Equatable {
String? url, String? url,
String? serviceId, String? serviceId,
int? fileSize, int? fileSize,
Uint8List? localBytes,
}) { }) {
return ServiceFileModel( return ServiceFileModel(
id: id ?? this.id, id: id ?? this.id,
@@ -48,6 +53,7 @@ class ServiceFileModel extends Equatable {
url: url ?? this.url, url: url ?? this.url,
serviceId: serviceId ?? this.serviceId, serviceId: serviceId ?? this.serviceId,
fileSize: fileSize ?? this.fileSize, fileSize: fileSize ?? this.fileSize,
localBytes: localBytes ?? this.localBytes,
); );
} }

View File

@@ -3,6 +3,7 @@ import 'package:flux/core/utils/string_extensions.dart';
import 'package:flux/features/services/models/energy_service_model.dart'; import 'package:flux/features/services/models/energy_service_model.dart';
import 'package:flux/features/services/models/entertainment_service_model.dart'; import 'package:flux/features/services/models/entertainment_service_model.dart';
import 'package:flux/features/services/models/fin_service_model.dart'; import 'package:flux/features/services/models/fin_service_model.dart';
import 'package:flux/features/services/models/service_file_model.dart'; // <-- Aggiunto Import
class ServiceModel extends Equatable { class ServiceModel extends Equatable {
final String? id; final String? id;
@@ -29,6 +30,9 @@ class ServiceModel extends Equatable {
final List<FinServiceModel> finServices; final List<FinServiceModel> finServices;
final List<EntertainmentServiceModel> entertainmentServices; final List<EntertainmentServiceModel> entertainmentServices;
// ALLEGATI (Aggiunto)
final List<ServiceFileModel> files;
const ServiceModel({ const ServiceModel({
this.id, this.id,
this.createdAt, this.createdAt,
@@ -47,6 +51,7 @@ class ServiceModel extends Equatable {
this.energyServices = const [], this.energyServices = const [],
this.finServices = const [], this.finServices = const [],
this.entertainmentServices = const [], this.entertainmentServices = const [],
this.files = const [], // <-- Aggiunto default vuoto
this.customerDisplayName, this.customerDisplayName,
required this.companyId, required this.companyId,
}); });
@@ -69,6 +74,7 @@ class ServiceModel extends Equatable {
List<EnergyServiceModel>? energyServices, List<EnergyServiceModel>? energyServices,
List<FinServiceModel>? finServices, List<FinServiceModel>? finServices,
List<EntertainmentServiceModel>? entertainmentServices, List<EntertainmentServiceModel>? entertainmentServices,
List<ServiceFileModel>? files, // <-- Aggiunto
String? customerDisplayName, String? customerDisplayName,
String? companyId, String? companyId,
}) { }) {
@@ -91,6 +97,7 @@ class ServiceModel extends Equatable {
finServices: finServices ?? this.finServices, finServices: finServices ?? this.finServices,
entertainmentServices: entertainmentServices:
entertainmentServices ?? this.entertainmentServices, entertainmentServices ?? this.entertainmentServices,
files: files ?? this.files, // <-- Aggiunto
customerDisplayName: customerDisplayName ?? this.customerDisplayName, customerDisplayName: customerDisplayName ?? this.customerDisplayName,
companyId: companyId ?? this.companyId, companyId: companyId ?? this.companyId,
); );
@@ -115,6 +122,7 @@ class ServiceModel extends Equatable {
energyServices, energyServices,
finServices, finServices,
entertainmentServices, entertainmentServices,
files, // <-- Aggiunto
customerDisplayName, customerDisplayName,
companyId, companyId,
]; ];
@@ -155,6 +163,13 @@ class ServiceModel extends Equatable {
.toList() ?? .toList() ??
const [], const [],
// I FILE! (Assicurati che la foreign key su Supabase usi esattamente questo nome)
files:
(map['service_file'] as List?)
?.map((x) => ServiceFileModel.fromMap(x))
.toList() ??
const [],
// Display name del cliente con fallback // Display name del cliente con fallback
customerDisplayName: map['customer'] != null customerDisplayName: map['customer'] != null
? "${map['customer']['nome'] ?? ''}".myFormat() ? "${map['customer']['nome'] ?? ''}".myFormat()

View File

@@ -1,7 +1,10 @@
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/widgets/image_viewer_widget.dart';
import 'package:flux/core/widgets/pdf_viewer_widget.dart';
import 'package:flux/features/services/blocs/services_cubit.dart'; import 'package:flux/features/services/blocs/services_cubit.dart';
import 'package:flux/features/services/models/service_file_model.dart';
class AttachmentsSection extends StatelessWidget { class AttachmentsSection extends StatelessWidget {
const AttachmentsSection({super.key}); const AttachmentsSection({super.key});
@@ -24,7 +27,7 @@ class AttachmentsSection extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<ServicesCubit, ServicesState>( return BlocBuilder<ServicesCubit, ServicesState>(
builder: (context, state) { builder: (context, state) {
final localFiles = state.localAttachments; final files = state.files;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -49,7 +52,7 @@ class AttachmentsSection extends StatelessWidget {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
if (localFiles.isEmpty) if (files.isEmpty)
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
@@ -71,16 +74,20 @@ class AttachmentsSection extends StatelessWidget {
ListView.builder( ListView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: localFiles.length, itemCount: files.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final file = localFiles[index]; final file = files[index];
// Calcoliamo la dimensione in MB // Calcoliamo la dimensione in MB
final sizeMb = (file.size / (1024 * 1024)).toStringAsFixed(2); final sizeMb = (file.fileSize / (1024 * 1024))
.toStringAsFixed(2);
// Scegliamo un'icona in base al tipo di file // Scegliamo un'icona in base al tipo di file
final isPdf = file.extension?.toLowerCase() == 'pdf'; final isPdf = file.extension.toLowerCase() == 'pdf';
return Card( return GestureDetector(
onTap: () => _handleSingleClick(context, file),
onDoubleTap: () => _handleDoubleClick(context, file),
child: Card(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
elevation: 0, elevation: 0,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -106,7 +113,8 @@ class AttachmentsSection extends StatelessWidget {
), ),
onPressed: () => context onPressed: () => context
.read<ServicesCubit>() .read<ServicesCubit>()
.removeLocalAttachment(index), .removeAttachment(index),
),
), ),
), ),
); );
@@ -117,4 +125,54 @@ class AttachmentsSection extends StatelessWidget {
}, },
); );
} }
// --- LOGICA DI COPIA AL CLIENTE ---
void _handleSingleClick(BuildContext context, ServiceFileModel file) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text("Copia nei documenti Cliente"),
content: const Text(
"Vuoi copiare questo file nell'anagrafica del cliente? \n\n"
"Attenzione: per procedere, la pratica attuale verrà prima salvata in stato BOZZA.",
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text("Annulla"),
),
ElevatedButton(
onPressed: () {
Navigator.pop(ctx);
// 1. Diciamo al Cubit di salvare in Bozza e fare la copia
context.read<ServicesCubit>().saveAndCopyFileToCustomer(file);
},
child: const Text("Salva e Copia"),
),
],
),
);
}
// --- LOGICA DI VISUALIZZAZIONE OVERLAY ---
void _handleDoubleClick(BuildContext context, ServiceFileModel file) {
showDialog(
context: context,
barrierDismissible: true,
builder: (ctx) => Dialog(
insetPadding: const EdgeInsets.all(16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: SizedBox(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.8,
child: file.isPdf
? PdfViewerWidget(url: file.url)
: ImageViewerWidget(url: file.url),
),
),
),
);
}
} }

View File

@@ -7,12 +7,14 @@ import Foundation
import app_links import app_links
import file_picker import file_picker
import pdfx
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
PdfxPlugin.register(with: registry.registrar(forPlugin: "PdfxPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
} }

View File

@@ -145,6 +145,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.8" version: "2.0.8"
extension:
dependency: transitive
description:
name: extension
sha256: be3a6b7f8adad2f6e2e8c63c895d19811fcf203e23466c6296267941d0ff4f24
url: "https://pub.dev"
source: hosted
version: "0.6.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -177,6 +185,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "11.0.2" version: "11.0.2"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -312,6 +328,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
internet_file:
dependency: "direct main"
description:
name: internet_file
sha256: c303ebf02caa853f072c49150557e76957622adacb18420008531c97a5ef5026
url: "https://pub.dev"
source: hosted
version: "1.3.0"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -488,6 +512,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
pdfx:
dependency: "direct main"
description:
name: pdfx
sha256: "29db9b71d46bf2335e001f91693f2c3fbbf0760e4c2eb596bf4bafab211471c1"
url: "https://pub.dev"
source: hosted
version: "2.9.2"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@@ -496,6 +528,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.2" version: "7.0.2"
photo_view:
dependency: transitive
description:
name: photo_view
sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e"
url: "https://pub.dev"
source: hosted
version: "0.15.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@@ -685,6 +725,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.12.2" version: "2.12.2"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
url: "https://pub.dev"
source: hosted
version: "3.4.0+1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@@ -709,6 +757,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
universal_file:
dependency: transitive
description:
name: universal_file
sha256: d1a957fccaad2a32023b62fe435b273ee47aaf2eb804709795e4bf4afff50960
url: "https://pub.dev"
source: hosted
version: "1.0.0"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
url_launcher: url_launcher:
dependency: transitive dependency: transitive
description: description:
@@ -773,6 +837,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.5" version: "3.1.5"
uuid:
dependency: transitive
description:
name: uuid
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
url: "https://pub.dev"
source: hosted
version: "4.5.3"
vector_graphics: vector_graphics:
dependency: transitive dependency: transitive
description: description:

View File

@@ -18,7 +18,9 @@ dependencies:
get_it: ^9.2.1 get_it: ^9.2.1
go_router: ^17.2.0 go_router: ^17.2.0
google_fonts: ^8.0.2 google_fonts: ^8.0.2
internet_file: ^1.3.0
intl: ^0.20.2 intl: ^0.20.2
pdfx: ^2.9.2
shared_preferences: ^2.5.5 shared_preferences: ^2.5.5
supabase_flutter: ^2.12.2 supabase_flutter: ^2.12.2

View File

@@ -7,11 +7,14 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h> #include <app_links/app_links_plugin_c_api.h>
#include <pdfx/pdfx_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar( AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi")); registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
PdfxPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PdfxPlugin"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows")); registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links app_links
pdfx
url_launcher_windows url_launcher_windows
) )