From 1a41a99e2087ed5648c480e82557ae5f28e41dad Mon Sep 17 00:00:00 2001 From: solocla Date: Sat, 17 Jan 2026 17:36:07 +0100 Subject: [PATCH] added certificate page --- lib/config/api_config.dart | 33 +- lib/screens/home_page.dart | 15 + lib/screens/medical_certificates_page.dart | 613 ++++++++++++++++++ lib/services/medical_certificates_api.dart | 113 ++++ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 136 ++++ pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 11 files changed, 919 insertions(+), 7 deletions(-) create mode 100644 lib/screens/medical_certificates_page.dart create mode 100644 lib/services/medical_certificates_api.dart diff --git a/lib/config/api_config.dart b/lib/config/api_config.dart index 3b857de..555753e 100644 --- a/lib/config/api_config.dart +++ b/lib/config/api_config.dart @@ -1,21 +1,40 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; class ApiConfig { - // Host handling: + /// Metti true quando vuoi puntare al server live + static const bool useLive = true; + + /// Dominio produzione + static const String liveHost = 'app.yogiboook.com'; + + // Host handling DEV: // - Web: localhost // - Android emulator: 10.0.2.2 (maps to host machine) - static String get host { + static String get _devHost { if (kIsWeb) return 'localhost'; if (defaultTargetPlatform == TargetPlatform.android) return '10.0.2.2'; return 'localhost'; } - // Laravel/Vanguard API (login, password remind, social) - static String get laravelApiBase => 'http://$host/yogiboook/public/api'; + static String get host => useLive ? liveHost : _devHost; - // Your custom PHP APIs under /public/userarea/api/ - static String get phpApiBase => 'http://$host/yogiboook/public/userarea/api'; + static String get scheme => useLive ? 'https' : 'http'; + + /// Laravel/Vanguard API (login, password remind, ecc) + /// DEV: http://10.0.2.2/yogiboook/public/api + /// LIVE: https://app.yogiboook.com/public/api + static String get laravelApiBase { + if (useLive) return '$scheme://$host/public/api'; + return '$scheme://$host/yogiboook/public/api'; + } + + /// PHP custom APIs + /// DEV: http://10.0.2.2/yogiboook/public/userarea/api + /// LIVE: https://app.yogiboook.com/public/userarea/api + static String get phpApiBase { + if (useLive) return '$scheme://$host/public/userarea/api'; + return '$scheme://$host/yogiboook/public/userarea/api'; + } static const String deviceName = 'FlutterApp'; } diff --git a/lib/screens/home_page.dart b/lib/screens/home_page.dart index c853548..0cfcef6 100644 --- a/lib/screens/home_page.dart +++ b/lib/screens/home_page.dart @@ -8,6 +8,7 @@ import 'select_school_page.dart'; import 'meditation_page.dart'; import '../services/vanguard_api.dart'; import 'login_page.dart'; +import 'medical_certificates_page.dart'; class HomePage extends StatefulWidget { final String token; @@ -278,6 +279,20 @@ class _HomePageState extends State { ); }, ), + ListTile( + leading: const Icon(Icons.medical_information), + title: const Text('Certificati medici'), + onTap: () { + Navigator.of(context).pop(); // chiude drawer + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + MedicalCertificatesPage(token: widget.token), + ), + ); + }, + ), const Divider(height: 1), ListTile( leading: const Icon(Icons.logout), diff --git a/lib/screens/medical_certificates_page.dart b/lib/screens/medical_certificates_page.dart new file mode 100644 index 0000000..defd71e --- /dev/null +++ b/lib/screens/medical_certificates_page.dart @@ -0,0 +1,613 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../services/medical_certificates_api.dart'; +import '../config/api_config.dart'; + +class MedicalCertificatesPage extends StatefulWidget { + final String token; + + const MedicalCertificatesPage({super.key, required this.token}); + + @override + State createState() => + _MedicalCertificatesPageState(); +} + +class _MedicalCertificatesPageState extends State { + bool loading = true; + String error = ''; + List> certs = []; + + // upload flow + File? pickedFile; + String pickedLabel = ''; + final docNameCtrl = TextEditingController(text: 'certificato'); + final notesCtrl = TextEditingController(); + DateTime? expiryDate; + bool uploading = false; + + @override + void initState() { + super.initState(); + _reload(); + } + + @override + void dispose() { + docNameCtrl.dispose(); + notesCtrl.dispose(); + super.dispose(); + } + + Future _reload() async { + setState(() { + loading = true; + error = ''; + }); + try { + final list = await MedicalCertificatesApi.list(token: widget.token); + setState(() => certs = list); + } catch (e) { + setState(() => error = e.toString()); + } finally { + setState(() => loading = false); + } + } + + Future _pickFromChooser() async { + // 1 solo bottone -> bottom sheet scelta + final choice = await showModalBottomSheet( + context: context, + showDragHandle: true, + builder: (_) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.photo_camera), + title: const Text('Fotocamera'), + onTap: () => Navigator.pop(context, 'camera'), + ), + ListTile( + leading: const Icon(Icons.photo_library), + title: const Text('Galleria'), + onTap: () => Navigator.pop(context, 'gallery'), + ), + ListTile( + leading: const Icon(Icons.picture_as_pdf), + title: const Text('PDF'), + onTap: () => Navigator.pop(context, 'pdf'), + ), + const SizedBox(height: 10), + ], + ), + ), + ); + + if (choice == null) return; + + File? f; + String label = ''; + + if (choice == 'camera' || choice == 'gallery') { + final picker = ImagePicker(); + final x = await picker.pickImage( + source: choice == 'camera' ? ImageSource.camera : ImageSource.gallery, + imageQuality: 85, + ); + if (x == null) return; + f = File(x.path); + label = x.name; + } + + if (choice == 'pdf') { + final res = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: const ['pdf'], + withData: false, + ); + if (res == null || res.files.isEmpty) return; + final path = res.files.single.path; + if (path == null) return; + f = File(path); + label = res.files.single.name; + } + + if (!mounted) return; + + setState(() { + pickedFile = f; + pickedLabel = label; + // reset campi per nuovo upload + docNameCtrl.text = 'certificato'; + notesCtrl.clear(); + expiryDate = null; + }); + } + + Future _pickExpiry() async { + final now = DateTime.now(); + final d = await showDatePicker( + context: context, + firstDate: DateTime(now.year, now.month, now.day), + lastDate: DateTime(now.year + 10), + initialDate: expiryDate ?? now, + ); + if (d == null) return; + setState(() => expiryDate = d); + } + + String _fmtYYYYMMDD(DateTime d) { + final mm = d.month.toString().padLeft(2, '0'); + final dd = d.day.toString().padLeft(2, '0'); + return '${d.year}-$mm-$dd'; + } + + String _fmtDDMMYYYY(DateTime d) { + final dd = d.day.toString().padLeft(2, '0'); + final mm = d.month.toString().padLeft(2, '0'); + return '$dd/$mm/${d.year}'; + } + + Future _doUpload() async { + if (pickedFile == null) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Seleziona prima un file.'))); + return; + } + if (expiryDate == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Seleziona la data di scadenza.')), + ); + return; + } + + setState(() { + uploading = true; + error = ''; + }); + + try { + await MedicalCertificatesApi.upload( + token: widget.token, + file: pickedFile!, + documentName: docNameCtrl.text, + expiryDateYYYYMMDD: _fmtYYYYMMDD(expiryDate!), + notes: notesCtrl.text, + ); + + // reset form + setState(() { + pickedFile = null; + pickedLabel = ''; + docNameCtrl.text = 'certificato'; + notesCtrl.clear(); + expiryDate = null; + }); + + await _reload(); + + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Certificato caricato correttamente.')), + ); + } catch (e) { + setState(() => error = e.toString()); + } finally { + setState(() => uploading = false); + } + } + + Future _openFileUrl(String url) async { + // url arriva tipo "/userarea/certificate/xxx" + // per aprirlo serve url assoluto + final abs = '${ApiConfig.scheme}://${ApiConfig.host}$url'; + final uri = Uri.parse(abs); + final ok = await launchUrl(uri, mode: LaunchMode.externalApplication); + if (!ok && mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Impossibile aprire il file.')), + ); + } + } + + Future _deleteCert(int id) async { + final ok = await showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text('Elimina certificato'), + content: const Text('Vuoi davvero eliminare questo certificato?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('Annulla'), + ), + ElevatedButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('Elimina'), + ), + ], + ), + ); + if (ok != true) return; + + try { + await MedicalCertificatesApi.delete(token: widget.token, certId: id); + await _reload(); + } catch (e) { + setState(() => error = e.toString()); + } + } + + bool _isImagePath(String p) { + final x = p.toLowerCase(); + return x.endsWith('.jpg') || + x.endsWith('.jpeg') || + x.endsWith('.png') || + x.endsWith('.heic') || + x.endsWith('.heif'); + } + + bool _isPdfPath(String p) => p.toLowerCase().endsWith('.pdf'); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + 'Certificati medici', + style: TextStyle(fontWeight: FontWeight.w900), + ), + actions: [ + IconButton( + onPressed: loading ? null : _reload, + icon: const Icon(Icons.refresh), + ), + ], + ), + body: RefreshIndicator( + onRefresh: _reload, + child: ListView( + padding: const EdgeInsets.fromLTRB(16, 14, 16, 22), + children: [ + // 1 SOLO bottone in alto + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: uploading ? null : _pickFromChooser, + icon: const Icon(Icons.cloud_upload), + label: const Text('Carica certificato'), + ), + ), + + const SizedBox(height: 12), + + // Preview + form (solo se file selezionato) + if (pickedFile != null) ...[ + _UploadCard( + filename: pickedLabel.isNotEmpty + ? pickedLabel + : pickedFile!.path.split(Platform.pathSeparator).last, + isImage: _isImagePath(pickedFile!.path), + isPdf: _isPdfPath(pickedFile!.path), + file: pickedFile!, + documentNameController: docNameCtrl, + notesController: notesCtrl, + expiryDate: expiryDate, + onPickExpiry: _pickExpiry, + onUpload: uploading ? null : _doUpload, + uploading: uploading, + expiryLabel: expiryDate == null + ? 'Seleziona data' + : _fmtDDMMYYYY(expiryDate!), + ), + const SizedBox(height: 14), + ], + + if (error.isNotEmpty) ...[ + Text(error, style: const TextStyle(color: Colors.red)), + const SizedBox(height: 10), + ], + + Text( + 'Certificati caricati (${certs.length})', + style: const TextStyle(fontWeight: FontWeight.w900, fontSize: 16), + ), + const SizedBox(height: 10), + + if (loading) + const Padding( + padding: EdgeInsets.only(top: 30), + child: Center(child: CircularProgressIndicator()), + ) + else if (certs.isEmpty) + const Padding( + padding: EdgeInsets.only(top: 26), + child: Center(child: Text('Nessun certificato caricato')), + ) + else + ...certs.map((c) { + final expired = c['is_expired'] == true; + final docName = (c['document_name'] ?? '').toString(); + final filename = (c['filename'] ?? '').toString(); + final uploadedAt = (c['uploaded_at'] ?? '').toString(); + final expiry = (c['expiry_date'] ?? '').toString(); + final notes = (c['notes'] ?? '').toString(); + final fileUrl = (c['file_url'] ?? '').toString(); + + return Card( + elevation: 0, + color: expired ? const Color(0xFFFFEBEE) : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + docName.isNotEmpty ? docName : 'certificato', + style: const TextStyle( + fontWeight: FontWeight.w900, + fontSize: 15, + ), + ), + ), + if (expired) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 4, + ), + decoration: BoxDecoration( + color: const Color(0xFFDC3545), + borderRadius: BorderRadius.circular(999), + ), + child: const Text( + 'SCADUTO', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w900, + fontSize: 11, + ), + ), + ), + ], + ), + const SizedBox(height: 6), + Text( + filename, + style: const TextStyle( + color: Colors.black54, + fontWeight: FontWeight.w600, + fontSize: 12, + ), + ), + const SizedBox(height: 10), + + Row( + children: [ + const Icon( + Icons.event, + size: 16, + color: Colors.black54, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + 'Caricato: $uploadedAt', + style: const TextStyle( + color: Colors.black54, + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox(height: 6), + Row( + children: [ + const Icon( + Icons.timer, + size: 16, + color: Colors.black54, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + 'Scadenza: ${expiry.isEmpty ? '—' : expiry}', + style: TextStyle( + color: expired + ? const Color(0xFFDC3545) + : Colors.black54, + fontSize: 12, + ), + ), + ), + ], + ), + + if (notes.trim().isNotEmpty) ...[ + const SizedBox(height: 10), + Text(notes, style: const TextStyle(fontSize: 12)), + ], + + const SizedBox(height: 10), + Row( + children: [ + TextButton.icon( + onPressed: () => _openFileUrl(fileUrl), + icon: const Icon(Icons.open_in_new), + label: const Text('Apri'), + ), + const Spacer(), + TextButton.icon( + onPressed: () => + _deleteCert((c['id'] as num).toInt()), + icon: const Icon( + Icons.delete_outline, + color: Colors.red, + ), + label: const Text( + 'Elimina', + style: TextStyle(color: Colors.red), + ), + ), + ], + ), + ], + ), + ), + ); + }), + ], + ), + ), + ); + } +} + +class _UploadCard extends StatelessWidget { + final String filename; + final bool isImage; + final bool isPdf; + final File file; + final TextEditingController documentNameController; + final TextEditingController notesController; + final DateTime? expiryDate; + final String expiryLabel; + final VoidCallback onPickExpiry; + final VoidCallback? onUpload; + final bool uploading; + + const _UploadCard({ + required this.filename, + required this.isImage, + required this.isPdf, + required this.file, + required this.documentNameController, + required this.notesController, + required this.expiryDate, + required this.expiryLabel, + required this.onPickExpiry, + required this.onUpload, + required this.uploading, + }); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 0, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), + child: Padding( + padding: const EdgeInsets.fromLTRB(14, 14, 14, 14), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Anteprima', + style: TextStyle(fontWeight: FontWeight.w900, fontSize: 14), + ), + const SizedBox(height: 10), + + if (isImage) + ClipRRect( + borderRadius: BorderRadius.circular(14), + child: Image.file( + file, + height: 160, + width: double.infinity, + fit: BoxFit.cover, + ), + ) + else + Container( + width: double.infinity, + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: const Color(0xFFF6F6FB), + borderRadius: BorderRadius.circular(14), + ), + child: Row( + children: [ + Icon( + isPdf ? Icons.picture_as_pdf : Icons.insert_drive_file, + size: 30, + ), + const SizedBox(width: 10), + Expanded( + child: Text( + filename, + style: const TextStyle(fontWeight: FontWeight.w700), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + + const SizedBox(height: 14), + + TextField( + controller: documentNameController, + decoration: const InputDecoration( + labelText: 'Nome documento', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 10), + + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: onPickExpiry, + icon: const Icon(Icons.date_range), + label: Text(expiryLabel), + ), + ), + ], + ), + const SizedBox(height: 10), + + TextField( + controller: notesController, + minLines: 2, + maxLines: 4, + decoration: const InputDecoration( + labelText: 'Note (opzionale)', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 12), + + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: onUpload, + icon: uploading + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.check), + label: Text(uploading ? 'Caricamento...' : 'Conferma upload'), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/services/medical_certificates_api.dart b/lib/services/medical_certificates_api.dart new file mode 100644 index 0000000..8d9397b --- /dev/null +++ b/lib/services/medical_certificates_api.dart @@ -0,0 +1,113 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:mime/mime.dart'; + +import '../config/api_config.dart'; + +class MedicalCertificatesApi { + static Uri _u(String path, [Map? q]) => + Uri.parse('${ApiConfig.phpApiBase}/$path').replace(queryParameters: q); + + static Future>> list({ + required String token, + }) async { + final res = await http.get( + _u('api_medical_certificates_list.php'), + headers: {'Accept': 'application/json', 'Authorization': 'Bearer $token'}, + ); + + if (res.statusCode != 200) { + throw Exception('List failed (${res.statusCode}): ${res.body}'); + } + + final data = jsonDecode(res.body) as Map; + if (data['success'] != true) { + throw Exception(data['message'] ?? 'List error'); + } + + final certs = (data['certificates'] as List).cast>(); + return certs; + } + + static Future> upload({ + required String token, + required File file, + required String documentName, + required String expiryDateYYYYMMDD, + String? notes, + }) async { + final req = http.MultipartRequest( + 'POST', + _u('api_medical_certificates_upload.php'), + ); + + req.headers.addAll({ + 'Accept': 'application/json', + 'Authorization': 'Bearer $token', + }); + + req.fields['document_name'] = documentName.trim().isEmpty + ? 'certificato' + : documentName.trim(); + req.fields['expiry_date'] = expiryDateYYYYMMDD; + if (notes != null && notes.trim().isNotEmpty) + req.fields['notes'] = notes.trim(); + + final mimeType = lookupMimeType(file.path) ?? 'application/octet-stream'; + + req.files.add( + await http.MultipartFile.fromPath( + 'certificate', + file.path, + contentType: _parseMediaType(mimeType), + ), + ); + + final streamed = await req.send(); + final body = await streamed.stream.bytesToString(); + + if (streamed.statusCode != 200) { + throw Exception('Upload failed (${streamed.statusCode}): $body'); + } + + final data = jsonDecode(body) as Map; + if (data['success'] != true) { + throw Exception(data['message'] ?? 'Upload error'); + } + return (data['certificate'] as Map).cast(); + } + + static Future delete({ + required String token, + required int certId, + }) async { + // usiamo POST fallback (più compatibile) + final res = await http.post( + _u('api_medical_certificates_delete.php'), + headers: { + 'Accept': 'application/json', + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: 'cert_id=$certId', + ); + + if (res.statusCode != 200) { + throw Exception('Delete failed (${res.statusCode}): ${res.body}'); + } + + final data = jsonDecode(res.body) as Map; + if (data['success'] != true) { + throw Exception(data['message'] ?? 'Delete error'); + } + } +} + +// helper per MediaType senza dipendenze extra +http.MediaType? _parseMediaType(String mime) { + final parts = mime.split('/'); + if (parts.length != 2) return null; + return http.MediaType(parts[0], parts[1]); +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index cc10c4d..d8a4062 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,12 +7,16 @@ #include "generated_plugin_registrant.h" #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 8e2a190..04f81f4 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_linux + file_selector_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c7adea5..3d123a5 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,12 +6,16 @@ import FlutterMacOS import Foundation import audioplayers_darwin +import file_picker +import file_selector_macos import google_sign_in_ios import path_provider_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) + FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index b2c0819..cd5b2f5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + url: "https://pub.dev" + source: hosted + version: "0.3.5+1" crypto: dependency: transitive description: @@ -137,6 +145,46 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 + url: "https://pub.dev" + source: hosted + version: "8.3.7" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + url: "https://pub.dev" + source: hosted + version: "0.9.5" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" fixnum: dependency: transitive description: @@ -163,6 +211,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + url: "https://pub.dev" + source: hosted + version: "2.0.33" flutter_test: dependency: "direct dev" description: flutter @@ -237,6 +293,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677" + url: "https://pub.dev" + source: hosted + version: "0.8.13+10" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad" + url: "https://pub.dev" + source: hosted + version: "0.8.13+3" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" + url: "https://pub.dev" + source: hosted + version: "0.2.2+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" intl: dependency: "direct main" description: @@ -301,6 +421,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + mime: + dependency: "direct main" + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -538,6 +666,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c507365..3ad283e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,9 @@ dependencies: google_sign_in: ^7.0.0 url_launcher: ^6.3.2 audioplayers: ^6.5.1 + file_picker: ^8.0.7 + image_picker: ^1.1.2 + mime: ^2.0.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 43d432f..6e77110 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,11 +7,14 @@ #include "generated_plugin_registrant.h" #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { AudioplayersWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 1772613..804cebd 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows + file_selector_windows url_launcher_windows )