fdevs_web_processor 0.0.1+1-alpha
fdevs_web_processor: ^0.0.1+1-alpha copied to clipboard
A Flutter plugin for rendering HTML to PDF on Android.
example/lib/main.dart
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:fdevs_web_processor/fdevs_web_processor.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'services/pdf_generator_service.dart';
import 'templates/templates.dart';
import 'templates/receipts/receipts.dart';
import 'templates/invoices/invoices.dart';
void main() {
// Register all templates
PdfGeneratorService().registerTemplates([
const RestaurantReceiptTemplate(),
const RetailReceiptTemplate(),
const A4InvoiceTemplate(),
const CorporateInvoiceTemplate(),
const ProfessionalInvoiceTemplate(),
const PremiumInvoiceTemplate(),
const ModernLogoInvoiceTemplate(),
const DarkHeaderInvoiceTemplate(),
const CircleInvoiceTemplate(),
]);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FdevsWebProcessor Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const DemoScreen(),
);
}
}
class DemoScreen extends StatefulWidget {
const DemoScreen({super.key});
@override
State<DemoScreen> createState() => _DemoScreenState();
}
class _DemoScreenState extends State<DemoScreen> {
final PdfGeneratorService _generator = PdfGeneratorService();
bool _isLoading = false;
Uint8List? _pdfBytes;
String? _pdfFilePath;
String? _lastGeneratedName;
Future<void> _generateFromTemplate(
PdfTemplate template, {
bool open = false,
}) async {
setState(() {
_isLoading = true;
_lastGeneratedName = '${template.name.toLowerCase().replaceAll(' ', '_')}.pdf';
});
try {
final pdf = await _generator.generate(template);
_pdfBytes = pdf;
final dir = await getTemporaryDirectory();
final file = File('${dir.path}/$_lastGeneratedName');
await file.writeAsBytes(pdf);
_pdfFilePath = file.path;
setState(() => _isLoading = false);
if (open && mounted) {
await _openPdf(file.path);
} else if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Generated ${template.name}: ${pdf.length} bytes')),
);
}
} on ContentTooLargeException catch (e) {
setState(() => _isLoading = false);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Receipt too long (${e.contentHeightPx}px). '
'Maximum is ${e.maxHeightPx}px.',
),
backgroundColor: Colors.orange,
duration: const Duration(seconds: 5),
),
);
}
} on RenderException catch (e) {
setState(() => _isLoading = false);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: ${e.message}'),
backgroundColor: Colors.red,
),
);
}
}
}
Future<void> _openPdf(String path) async {
final result = await OpenFilex.open(path);
if (result.type != ResultType.done && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not open PDF: ${result.message}')),
);
}
}
Future<void> _shareLastPdf() async {
if (_pdfFilePath == null) return;
await Share.shareXFiles([XFile(_pdfFilePath!)], text: 'Generated PDF');
}
@override
Widget build(BuildContext context) {
final thermalReceipts = _generator.getTemplatesBySize(PaperSize.mm80);
final a4Invoices = _generator.getTemplatesBySize(PaperSize.a4);
return Scaffold(
appBar: AppBar(
title: const Text('PDF Templates'),
centerTitle: true,
actions: [
if (_pdfFilePath != null)
IconButton(
icon: const Icon(Icons.share),
tooltip: 'Share PDF',
onPressed: _shareLastPdf,
),
],
),
body: _isLoading
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Generating PDF...'),
],
),
)
: ListView(
padding: const EdgeInsets.all(16),
children: [
if (_pdfBytes != null) ...[
_PdfInfoCard(
size: _pdfBytes!.length,
name: _lastGeneratedName ?? 'document.pdf',
onOpen: () => _openPdf(_pdfFilePath!),
onShare: _shareLastPdf,
),
const SizedBox(height: 24),
],
_buildSectionHeader('Thermal Receipts (80mm)', Icons.receipt),
const SizedBox(height: 12),
...thermalReceipts.map((t) => _TemplateCard(
template: t,
onTap: () => _generateFromTemplate(t, open: true),
)),
const SizedBox(height: 24),
_buildSectionHeader('A4 Documents', Icons.description),
const SizedBox(height: 12),
...a4Invoices.map((t) => _TemplateCard(
template: t,
onTap: () => _generateFromTemplate(t, open: true),
)),
],
),
);
}
Widget _buildSectionHeader(String title, IconData icon) {
return Row(
children: [
Icon(icon, color: Theme.of(context).colorScheme.primary),
const SizedBox(width: 8),
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
);
}
}
class _TemplateCard extends StatelessWidget {
final PdfTemplate template;
final VoidCallback onTap;
const _TemplateCard({
required this.template,
required this.onTap,
});
Color get _color => switch (template.paperSize) {
PaperSize.mm58 => Colors.green,
PaperSize.mm80 => Colors.orange,
PaperSize.a4 => Colors.blue,
PaperSize.letter => Colors.purple,
};
IconData get _icon => switch (template.paperSize) {
PaperSize.mm58 => Icons.receipt_long,
PaperSize.mm80 => Icons.receipt,
PaperSize.a4 => Icons.description,
PaperSize.letter => Icons.article,
};
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
margin: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: _color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(_icon, color: _color, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
template.name,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
template.description,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
template.paperSize.description,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: _color,
fontWeight: FontWeight.w500,
),
),
],
),
),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: Colors.grey[400],
),
],
),
),
),
);
}
}
class _PdfInfoCard extends StatelessWidget {
final int size;
final String name;
final VoidCallback onOpen;
final VoidCallback onShare;
const _PdfInfoCard({
required this.size,
required this.name,
required this.onOpen,
required this.onShare,
});
@override
Widget build(BuildContext context) {
return Card(
color: Theme.of(context).colorScheme.primaryContainer,
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.picture_as_pdf,
size: 40,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
Text(
'${(size / 1024).toStringAsFixed(1)} KB',
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: onOpen,
icon: const Icon(Icons.open_in_new, size: 18),
label: const Text('Open'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: onShare,
icon: const Icon(Icons.share, size: 18),
label: const Text('Share'),
),
),
],
),
],
),
),
);
}
}