moussa_pdf 0.1.0+8 copy "moussa_pdf: ^0.1.0+8" to clipboard
moussa_pdf: ^0.1.0+8 copied to clipboard

A secure, native PDF viewer Flutter plugin with drawing tools and snipping support for education workflows.

example/lib/main.dart

import 'dart:async';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';

import 'package:moussa_pdf/moussa_pdf.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'moussa_pdf example',
      theme: ThemeData.dark(),
      home: const PdfExampleScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class PdfExampleScreen extends StatefulWidget {
  const PdfExampleScreen({super.key});

  @override
  State<PdfExampleScreen> createState() => _PdfExampleScreenState();
}

class _PdfExampleScreenState extends State<PdfExampleScreen> {
  MoussaPdfController? _controller;
  StreamSubscription? _eventSub;
  StreamSubscription? _snipSub;

  // 👈 حط لينك PDF public (يفضل صغير للتجربة)
  final String pdfUrl = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";

  // 👈 دول أمثلة - عندك في مشروعك هتيجي من اليوزر
  final String userId = "user_123";
  final String fileId = "file_abc";

  String status = "Waiting...";
  double? progress; // 0..1

  @override
  void dispose() {
    _snipSub?.cancel();
    _eventSub?.cancel();
    _controller?.dispose();
    super.dispose();
  }

  Future<void> _open() async {
    final c = _controller;
    if (c == null) return;

    setState(() {
      status = "Opening...";
      progress = null;
    });

    await c.openUrl(url: pdfUrl, userId: userId, fileId: fileId);
  }

  Future<void> _showSnipPreview(BuildContext context, MoussaPdfSnip snip) async {
    final bytes = snip.bytes;

    await showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: const Color(0xFF111111),
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (_) {
        return Padding(
          padding: EdgeInsets.only(
            left: 16,
            right: 16,
            top: 12,
            bottom: 16 + MediaQuery.of(context).viewInsets.bottom,
          ),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 40,
                height: 4,
                margin: const EdgeInsets.only(bottom: 12),
                decoration: BoxDecoration(
                  color: Colors.white24,
                  borderRadius: BorderRadius.circular(10),
                ),
              ),
              Text(
                "Snip captured (page ${snip.page + 1})",
                style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
              ),
              const SizedBox(height: 12),
              ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: Image.memory(bytes, fit: BoxFit.contain),
              ),
              const SizedBox(height: 12),
              Row(
                children: [
                  Expanded(
                    child: OutlinedButton.icon(
                      onPressed: () async {
                        final path = await _saveSnipTemp(bytes);
                        if (mounted) {
                          Navigator.pop(context);
                          _toast("Saved temp: $path");
                        }
                      },
                      icon: const Icon(Icons.save_alt),
                      label: const Text("Save temp"),
                    ),
                  ),
                  const SizedBox(width: 12),
                  Expanded(
                    child: ElevatedButton.icon(
                      onPressed: () async {
                        Navigator.pop(context);
                        await _sendToTeacher(bytes: bytes, page: snip.page);
                      },
                      icon: const Icon(Icons.send),
                      label: const Text("Send"),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 8),
            ],
          ),
        );
      },
    );
  }

  Future<String> _saveSnipTemp(Uint8List bytes) async {
    final dir = await getTemporaryDirectory();
    final file = File("${dir.path}/moussa_snip_${DateTime.now().millisecondsSinceEpoch}.png");
    await file.writeAsBytes(bytes, flush: true);
    return file.path;
  }

  Future<void> _sendToTeacher({required Uint8List bytes, required int page}) async {
    // 👇 هنا تربط API بتاعك:
    // - multipart upload للصورة
    // - أو تبعتها Base64
    // - أو تعمل OCR وترسل النص
    //
    // في المثال ده هنمثل الإرسال بس.
    setState(() => status = "Sending snip (page ${page + 1}) ...");

    await Future.delayed(const Duration(milliseconds: 600)); // simulate

    if (!mounted) return;
    _toast("Sent (demo). bytes=${bytes.length}");
    setState(() => status = "Ready");
  }

  void _toast(String msg) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(msg), duration: const Duration(seconds: 2)),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("moussa_pdf example"),
        actions: [
          IconButton(
            tooltip: "Open URL",
            onPressed: _open,
            icon: const Icon(Icons.picture_as_pdf),
          ),
        ],
      ),
      body: Column(
        children: [
          if (progress != null)
            LinearProgressIndicator(value: progress),
          Padding(
            padding: const EdgeInsets.all(10),
            child: Row(
              children: [
                Expanded(child: Text(status, style: const TextStyle(fontSize: 12))),
                const SizedBox(width: 8),
                OutlinedButton(
                  onPressed: () => _controller?.setTool(MoussaPdfTool.hand),
                  child: const Text("Hand"),
                ),
                const SizedBox(width: 6),
                OutlinedButton(
                  onPressed: () => _controller?.setTool(MoussaPdfTool.snip),
                  child: const Text("Snip"),
                ),
              ],
            ),
          ),
          Expanded(
            child: MoussaPdfView(
              onCreated: (c) async {
                _controller = c;

                // Listen to all events (progress/errors/info)
                _eventSub = c.events.listen((e) {
                  // Debug print (اختياري)
                  // ignore: avoid_print
                  print("EVENT: ${e.type} ${e.data}");

                  if (e.type == "downloadProgress") {
                    final p = e.data["percent"];
                    final percent = (p is num) ? p.toDouble() : double.tryParse("$p");
                    if (percent != null) {
                      setState(() {
                        progress = (percent / 100.0).clamp(0.0, 1.0);
                        status = "Downloading... ${percent.toStringAsFixed(0)}%";
                      });
                    }
                  }

                  if (e.type == "opened") {
                    setState(() {
                      progress = null;
                      status = "Opened ✅";
                    });
                  }

                  if (e.type == "error") {
                    final msg = e.data["message"]?.toString() ?? "Unknown error";
                    setState(() {
                      progress = null;
                      status = "Error: $msg";
                    });
                  }
                });

                // Listen to snips
                _snipSub = c.snips.listen((snip) {
                  _showSnipPreview(context, snip);
                });

                // Auto open
                await _open();
              },
              onEvent: (e) {
                // لو عايز callbacks منفصلة غير stream
                // ignore: avoid_print
                // print("onEvent: $e");
              },
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
140
points
104
downloads

Publisher

unverified uploader

Weekly Downloads

A secure, native PDF viewer Flutter plugin with drawing tools and snipping support for education workflows.

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on moussa_pdf

Packages that implement moussa_pdf