feat-insert-service (#5)
Reviewed-on: http://catelliub.zapto.org:3000/brontomark/flux/pulls/5 Co-authored-by: mark-cachy <marco@catelli.it> Co-committed-by: mark-cachy <marco@catelli.it>
This commit is contained in:
@@ -8,7 +8,7 @@ import 'package:flux/features/home/ui/home_screen.dart';
|
||||
import 'package:flux/features/master_data/products/ui/products_screen.dart';
|
||||
import 'package:flux/features/master_data/store/ui/create_store_screen.dart';
|
||||
import 'package:flux/features/services/models/service_model.dart';
|
||||
import 'package:flux/features/services/ui/service_form_screen.dart';
|
||||
import 'package:flux/features/services/ui/service_form_screen/service_form_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'dart:async';
|
||||
|
||||
@@ -80,9 +80,15 @@ class AppRouter {
|
||||
path: '/service-form',
|
||||
name: 'service-form',
|
||||
builder: (context, state) {
|
||||
// Recuperiamo il ServiceModel se passato come extra
|
||||
final service = state.extra as ServiceModel?;
|
||||
return ServiceFormScreen(initialService: service);
|
||||
// Recuperiamo l'oggetto se passato tramite 'extra'
|
||||
final existingService = state.extra as ServiceModel?;
|
||||
// Recuperiamo l'ID se presente nell'URL
|
||||
final serviceId = state.uri.queryParameters['serviceId'];
|
||||
|
||||
return ServiceFormScreen(
|
||||
serviceId: serviceId ?? existingService?.id,
|
||||
existingService: existingService,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -19,4 +19,24 @@ extension MyStringExtensions on String? {
|
||||
})
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
String fileExtension() {
|
||||
if (this == null || this!.trim().isEmpty) return '';
|
||||
|
||||
final parts = this!.split('.');
|
||||
if (parts.length < 2) return ''; // Nessuna estensione trovata
|
||||
|
||||
return parts.last.toLowerCase();
|
||||
}
|
||||
|
||||
String fileNameWithoutExtension() {
|
||||
if (this == null || this!.trim().isEmpty) return '';
|
||||
this!.replaceAll(RegExp(r'[^a-zA-Z0-9\.\-]'), '_');
|
||||
final parts = this!.split('.');
|
||||
if (parts.length < 2) return this!; // Nessuna estensione trovata
|
||||
|
||||
return parts
|
||||
.sublist(0, parts.length - 1)
|
||||
.join('.'); // Ritorna tutto tranne l'ultima parte
|
||||
}
|
||||
}
|
||||
|
||||
61
lib/core/widgets/image_viewer_widget.dart
Normal file
61
lib/core/widgets/image_viewer_widget.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart'; // <--- AGGIUNGI QUESTO
|
||||
|
||||
class ImageViewerWidget extends StatelessWidget {
|
||||
final String? storagePath; // ATTENZIONE: Ora contiene lo storagePath!
|
||||
final Uint8List? bytes;
|
||||
|
||||
const ImageViewerWidget({super.key, this.storagePath, this.bytes})
|
||||
: assert(
|
||||
(storagePath != null && storagePath != '') || bytes != null,
|
||||
'Errore: Devi fornire un Path valido o i bytes del file!',
|
||||
);
|
||||
|
||||
// Funzione che chiede le chiavi a Supabase
|
||||
Future<String> _getSignedUrl() async {
|
||||
return await Supabase.instance.client.storage
|
||||
.from('documents')
|
||||
.createSignedUrl(storagePath!, 60); // Link che si autodistrugge in 60s
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.black),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
body: InteractiveViewer(
|
||||
maxScale: 5.0,
|
||||
child: Center(
|
||||
// Se abbiamo i byte, mostriamo subito. Altrimenti usiamo il FutureBuilder!
|
||||
child: bytes != null
|
||||
? Image.memory(bytes!)
|
||||
: FutureBuilder<String>(
|
||||
future: _getSignedUrl(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
return const Text(
|
||||
"Errore caricamento immagine (Permessi negati?)",
|
||||
style: TextStyle(color: Colors.red),
|
||||
);
|
||||
}
|
||||
if (snapshot.hasData) {
|
||||
return Image.network(snapshot.data!);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
97
lib/core/widgets/pdf_viewer_widget.dart
Normal file
97
lib/core/widgets/pdf_viewer_widget.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:pdfx/pdfx.dart';
|
||||
import 'package:internet_file/internet_file.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class PdfViewerWidget extends StatefulWidget {
|
||||
final String? storagePath;
|
||||
final Uint8List? bytes;
|
||||
|
||||
const PdfViewerWidget({super.key, this.storagePath, this.bytes})
|
||||
: assert(
|
||||
(storagePath != null && storagePath != '') || bytes != null,
|
||||
'Errore: Devi fornire un URL valido o i bytes del file!',
|
||||
);
|
||||
|
||||
@override
|
||||
State<PdfViewerWidget> createState() => _PdfViewerWidgetState();
|
||||
}
|
||||
|
||||
class _PdfViewerWidgetState extends State<PdfViewerWidget> {
|
||||
late PdfControllerPinch _pdfController;
|
||||
bool _isLoading = true;
|
||||
String? _errorMessage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initPdf();
|
||||
}
|
||||
|
||||
Future<void> _initPdf() async {
|
||||
try {
|
||||
Uint8List pdfData;
|
||||
|
||||
if (widget.bytes != null) {
|
||||
// SCENARIO 1: Pratica in bozza, file appena scelto (Locale)
|
||||
pdfData = widget.bytes!;
|
||||
} else if (widget.storagePath != null && widget.storagePath!.isNotEmpty) {
|
||||
// SCENARIO 2: Pratica salvata, scarichiamo da Supabase (Remoto)
|
||||
final signedUrl = await GetIt.I
|
||||
.get<SupabaseClient>()
|
||||
.storage
|
||||
.from('documents')
|
||||
.createSignedUrl(widget.storagePath!, 60);
|
||||
pdfData = await InternetFile.get(signedUrl);
|
||||
} else {
|
||||
throw Exception("Nessun documento trovato");
|
||||
}
|
||||
|
||||
_pdfController = PdfControllerPinch(
|
||||
document: PdfDocument.openData(pdfData),
|
||||
);
|
||||
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_errorMessage = e.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pdfController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isLoading) {
|
||||
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
|
||||
if (_errorMessage != null) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(leading: const CloseButton()),
|
||||
body: Center(child: Text("Errore: $_errorMessage")),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Anteprima PDF"),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
body: PdfViewPinch(controller: _pdfController),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user