fdevs_web_processor 0.0.1+1-alpha copy "fdevs_web_processor: ^0.0.1+1-alpha" to clipboard
fdevs_web_processor: ^0.0.1+1-alpha copied to clipboard

PlatformAndroid

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'),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}