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() {
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

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
Future<void> saveFileReference(CustomerFileModel file) async {
Future<void> saveCustomerFile(CustomerFileModel file) async {
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
Future<void> updateCustomerDocuments(int id, List<String> urls) async {
await _supabase

View File

@@ -3,10 +3,12 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_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/models/energy_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/service_file_model.dart';
import 'package:flux/features/services/models/service_model.dart';
import 'package:get_it/get_it.dart';
import 'package:collection/collection.dart';
@@ -130,7 +132,7 @@ class ServicesCubit extends Cubit<ServicesState> {
companyId: _sessionBloc.state.company!.id,
),
status: ServicesStatus.ready,
localAttachments: [],
files: [],
),
);
}
@@ -210,7 +212,7 @@ class ServicesCubit extends Cubit<ServicesState> {
final serviceToSave = state.currentService!.copyWith(isBozza: isBozza);
// 2. Salvataggio corazzato
await _repository.saveFullService(serviceToSave, state.localAttachments);
await _repository.saveFullService(serviceToSave, state.files);
// 3. Reset e ricaricamento
emit(state.copyWith(status: ServicesStatus.saved, currentService: null));
@@ -228,14 +230,90 @@ class ServicesCubit extends Cubit<ServicesState> {
// --- GESTIONE ALLEGATI LOCALI ---
void addAttachments(List<PlatformFile> files) {
// Aggiungiamo i nuovi file a quelli già presenti in memoria
final updatedList = [...state.localAttachments, ...files];
emit(state.copyWith(localAttachments: updatedList));
// Trasformiamo i PlatformFile in ServiceFileModel "temporanei"
final newAttachments = files.map((file) {
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) {
final updatedList = List<PlatformFile>.from(state.localAttachments);
void removeAttachment(int index) {
if (state.currentService == null) return;
final updatedList = List<ServiceFileModel>.from(
state.currentService!.files,
);
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 DateTimeRange? dateRange;
final bool hasReachedMax;
final List<PlatformFile> localAttachments;
final List<ServiceFileModel> files;
const ServicesState({
required this.status,
@@ -20,7 +20,7 @@ class ServicesState extends Equatable {
this.query = '',
this.dateRange,
this.hasReachedMax = false,
this.localAttachments = const [],
this.files = const [],
});
ServicesState copyWith({
@@ -31,7 +31,7 @@ class ServicesState extends Equatable {
String? query,
DateTimeRange? dateRange,
bool? hasReachedMax,
List<PlatformFile>? localAttachments,
List<ServiceFileModel>? files,
}) {
return ServicesState(
status: status ?? this.status,
@@ -41,7 +41,7 @@ class ServicesState extends Equatable {
query: query ?? this.query,
dateRange: dateRange ?? this.dateRange,
hasReachedMax: hasReachedMax ?? this.hasReachedMax,
localAttachments: localAttachments ?? this.localAttachments,
files: files ?? this.files,
);
}
@@ -54,6 +54,6 @@ class ServicesState extends Equatable {
query,
dateRange,
hasReachedMax,
localAttachments,
files,
];
}

View File

@@ -1,7 +1,7 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.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:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
@@ -10,6 +10,7 @@ import '../models/service_model.dart';
class ServicesRepository {
final _supabase = Supabase.instance.client;
final companyId = GetIt.I.get<SessionBloc>().state.company!.id;
final CustomerRepository _customerRepository = GetIt.I<CustomerRepository>();
// --- RECUPERO SINGOLO SERVIZIO CON JOIN COMPLETO ---
Future<ServiceModel> fetchServiceById(String id) async {
@@ -84,7 +85,7 @@ class ServicesRepository {
// --- SALVATAGGIO COMPLETO (PRIMA PADRE, POI FIGLI) ---
Future<void> saveFullService(
ServiceModel service,
List<PlatformFile> localFiles,
List<ServiceFileModel> files,
) async {
try {
// 1. Upsert del record principale
@@ -152,40 +153,27 @@ class ServicesRepository {
if (insertTasks.isNotEmpty) {
await Future.wait(insertTasks);
}
if (localFiles.isNotEmpty) {
if (files.isNotEmpty) {
final List<Future> uploadTasks = [];
for (var file in localFiles) {
// Puliamo il nome del file per evitare problemi con spazi o caratteri strani
final cleanFileName = file.name.replaceAll(
RegExp(r'[^a-zA-Z0-9\.\-]'),
'_',
);
for (var file in files) {
final storagePath =
'$companyId/services/$newId/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
final int fileSize = file.size;
final fileToSave = ServiceFileModel(
name: cleanFileName.fileNameWithoutExtension(),
extension: cleanFileName.fileExtension(),
url: '',
serviceId: newId,
fileSize: fileSize,
);
'$companyId/services/$newId/${DateTime.now().millisecondsSinceEpoch}_${file.name}.${file.extension}';
final String mimeType = file.extension.toLowerCase() == 'pdf'
? 'application/pdf'
: 'image/${file.extension}';
final fileToSave = file.copyWith(serviceId: newId);
// Creiamo una funzione asincrona per caricare file e scrivere nel DB
Future<void> uploadAndLink() async {
// 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!)
await _supabase.storage
.from('documents')
.uploadBinary(
storagePath,
file.bytes!,
fileToSave.localBytes!,
fileOptions: FileOptions(
contentType:
mimeType, // Diciamo a Supabase esattamente cos'è!
@@ -255,4 +243,18 @@ class ServicesRepository {
]; // 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';
class ServiceFileModel extends Equatable {
@@ -7,7 +9,8 @@ class ServiceFileModel extends Equatable {
final String extension;
final String url;
final String serviceId;
final int fileSize; // <--- Aggiunto
final int fileSize;
final Uint8List? localBytes;
const ServiceFileModel({
this.id,
@@ -17,6 +20,7 @@ class ServiceFileModel extends Equatable {
required this.url,
required this.serviceId,
required this.fileSize,
this.localBytes,
});
// Trasforma i byte in qualcosa di leggibile (KB, MB, GB)
@@ -39,6 +43,7 @@ class ServiceFileModel extends Equatable {
String? url,
String? serviceId,
int? fileSize,
Uint8List? localBytes,
}) {
return ServiceFileModel(
id: id ?? this.id,
@@ -48,6 +53,7 @@ class ServiceFileModel extends Equatable {
url: url ?? this.url,
serviceId: serviceId ?? this.serviceId,
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/entertainment_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 {
final String? id;
@@ -29,6 +30,9 @@ class ServiceModel extends Equatable {
final List<FinServiceModel> finServices;
final List<EntertainmentServiceModel> entertainmentServices;
// ALLEGATI (Aggiunto)
final List<ServiceFileModel> files;
const ServiceModel({
this.id,
this.createdAt,
@@ -47,6 +51,7 @@ class ServiceModel extends Equatable {
this.energyServices = const [],
this.finServices = const [],
this.entertainmentServices = const [],
this.files = const [], // <-- Aggiunto default vuoto
this.customerDisplayName,
required this.companyId,
});
@@ -69,6 +74,7 @@ class ServiceModel extends Equatable {
List<EnergyServiceModel>? energyServices,
List<FinServiceModel>? finServices,
List<EntertainmentServiceModel>? entertainmentServices,
List<ServiceFileModel>? files, // <-- Aggiunto
String? customerDisplayName,
String? companyId,
}) {
@@ -91,6 +97,7 @@ class ServiceModel extends Equatable {
finServices: finServices ?? this.finServices,
entertainmentServices:
entertainmentServices ?? this.entertainmentServices,
files: files ?? this.files, // <-- Aggiunto
customerDisplayName: customerDisplayName ?? this.customerDisplayName,
companyId: companyId ?? this.companyId,
);
@@ -115,6 +122,7 @@ class ServiceModel extends Equatable {
energyServices,
finServices,
entertainmentServices,
files, // <-- Aggiunto
customerDisplayName,
companyId,
];
@@ -155,6 +163,13 @@ class ServiceModel extends Equatable {
.toList() ??
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
customerDisplayName: map['customer'] != null
? "${map['customer']['nome'] ?? ''}".myFormat()

View File

@@ -1,7 +1,10 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.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/models/service_file_model.dart';
class AttachmentsSection extends StatelessWidget {
const AttachmentsSection({super.key});
@@ -24,7 +27,7 @@ class AttachmentsSection extends StatelessWidget {
Widget build(BuildContext context) {
return BlocBuilder<ServicesCubit, ServicesState>(
builder: (context, state) {
final localFiles = state.localAttachments;
final files = state.files;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -49,7 +52,7 @@ class AttachmentsSection extends StatelessWidget {
),
const SizedBox(height: 12),
if (localFiles.isEmpty)
if (files.isEmpty)
Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
@@ -71,16 +74,20 @@ class AttachmentsSection extends StatelessWidget {
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: localFiles.length,
itemCount: files.length,
itemBuilder: (context, index) {
final file = localFiles[index];
final file = files[index];
// 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
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),
elevation: 0,
shape: RoundedRectangleBorder(
@@ -106,7 +113,8 @@ class AttachmentsSection extends StatelessWidget {
),
onPressed: () => context
.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 file_picker
import pdfx
import shared_preferences_foundation
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
PdfxPlugin.register(with: registry.registrar(forPlugin: "PdfxPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@@ -145,6 +145,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.8"
extension:
dependency: transitive
description:
name: extension
sha256: be3a6b7f8adad2f6e2e8c63c895d19811fcf203e23466c6296267941d0ff4f24
url: "https://pub.dev"
source: hosted
version: "0.6.0"
fake_async:
dependency: transitive
description:
@@ -177,6 +185,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "11.0.2"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
@@ -312,6 +328,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@@ -488,6 +512,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
pdfx:
dependency: "direct main"
description:
name: pdfx
sha256: "29db9b71d46bf2335e001f91693f2c3fbbf0760e4c2eb596bf4bafab211471c1"
url: "https://pub.dev"
source: hosted
version: "2.9.2"
petitparser:
dependency: transitive
description:
@@ -496,6 +528,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -685,6 +725,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -709,6 +757,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -773,6 +837,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.5"
uuid:
dependency: transitive
description:
name: uuid
sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
url: "https://pub.dev"
source: hosted
version: "4.5.3"
vector_graphics:
dependency: transitive
description:

View File

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

View File

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

View File

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