pdf_marks 0.0.1
pdf_marks: ^0.0.1 copied to clipboard
A Flutter package for viewing and annotating PDF documents with drawing, notes, shapes, and highlights.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:pdfx/pdfx.dart';
import 'package:pdf_marks/pdf_marks.dart';
import 'package:file_picker/file_picker.dart';
import 'debug_panel.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PDF Marks Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const PdfMarksExample(),
);
}
}
class PdfMarksExample extends StatefulWidget {
const PdfMarksExample({super.key});
@override
State<PdfMarksExample> createState() => _PdfMarksExampleState();
}
class _PdfMarksExampleState extends State<PdfMarksExample> {
PdfDocument? _document;
bool _isLoading = false;
bool _showDebugPanel = false;
Map<int, List<Annotation>> _annotations = {};
// Current annotation state for debugging
AnnotationMode _currentMode = AnnotationMode.view;
Color _currentColor = Colors.red;
double _currentStrokeWidth = 2.0;
ShapeType _currentShapeType = ShapeType.rectangle;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('PDF Marks Debug App'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
if (_document != null) ...[
IconButton(
icon: Icon(_showDebugPanel ? Icons.visibility_off : Icons.bug_report),
tooltip: _showDebugPanel ? 'Hide Debug Panel' : 'Show Debug Panel',
onPressed: () {
setState(() {
_showDebugPanel = !_showDebugPanel;
});
},
),
IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Reset App',
onPressed: _resetApp,
),
],
],
),
body: _document == null
? _buildLoadingView()
: Row(
children: [
// Main PDF viewer
Expanded(
child: PdfAnnotator(
document: _document!,
backgroundColor: Colors.grey[100]!, // Light grey background
onAnnotationsChanged: (annotations) {
setState(() {
_annotations = annotations;
});
debugPrint('đ Annotations changed: ${annotations.keys.length} pages, ${_getTotalAnnotations(annotations)} total');
},
),
),
// Debug panel
if (_showDebugPanel)
DebugPanel(
annotations: _annotations,
currentMode: _currentMode,
currentColor: _currentColor,
currentStrokeWidth: _currentStrokeWidth,
currentShapeType: _currentShapeType,
onClearAnnotations: _clearAllAnnotations,
),
],
),
);
}
Widget _buildLoadingView() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_isLoading) ...[
const CircularProgressIndicator(),
const SizedBox(height: 16),
const Text('Loading PDF...'),
] else ...[
const Icon(
Icons.picture_as_pdf,
size: 64,
color: Colors.grey,
),
const SizedBox(height: 16),
const Text(
'Load a PDF to start annotating',
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 8),
const Text(
'đ¯ Try the Multi-Page PDF (4 pages) for better testing!',
style: TextStyle(fontSize: 14, color: Colors.green),
),
const SizedBox(height: 8),
const Text(
'đą New: Auto-switch to move mode after adding annotations!',
style: TextStyle(fontSize: 12, color: Colors.orange),
),
const SizedBox(height: 24),
ElevatedButton.icon(
onPressed: _loadSamplePdf,
icon: const Icon(Icons.file_open),
label: const Text('Load Simple PDF (1 page)'),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: _loadMultiPagePdf,
icon: const Icon(Icons.description),
label: const Text('Load Multi-Page PDF'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: _loadFromFile,
icon: const Icon(Icons.folder_open),
label: const Text('Choose Your Own PDF File'),
),
],
],
),
);
}
Future<void> _loadSamplePdf() async {
await _loadPdfFromAssets('assets/sample.pdf', 'Simple PDF');
}
Future<void> _loadMultiPagePdf() async {
await _loadPdfFromAssets('assets/multi_page_sample.pdf', 'Multi-Page PDF');
}
Future<void> _loadPdfFromAssets(String assetPath, String displayName) async {
setState(() {
_isLoading = true;
});
try {
debugPrint('đ Loading $displayName from assets...');
final document = await PdfDocument.openAsset(assetPath);
setState(() {
_document = document;
_annotations = {}; // Clear any existing annotations
});
debugPrint('â
$displayName loaded successfully: ${document.pagesCount} pages');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('đ $displayName loaded (${document.pagesCount} pages)'),
backgroundColor: Colors.green,
action: SnackBarAction(
label: 'Debug Panel',
textColor: Colors.white,
onPressed: () {
setState(() {
_showDebugPanel = true;
});
},
),
),
);
}
} catch (e) {
debugPrint('â Failed to load $displayName: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('â Failed to load $displayName: $e'),
backgroundColor: Colors.red,
),
);
}
} finally {
setState(() {
_isLoading = false;
});
}
}
Future<void> _loadFromFile() async {
try {
debugPrint('đ Opening file picker...');
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['pdf'],
dialogTitle: 'Select a PDF file to annotate',
);
if (result != null && result.files.single.path != null) {
setState(() {
_isLoading = true;
});
final filePath = result.files.single.path!;
final fileName = result.files.single.name;
debugPrint('đ Loading PDF from file: $fileName');
final document = await PdfDocument.openFile(filePath);
setState(() {
_document = document;
_annotations = {}; // Clear any existing annotations
_isLoading = false;
});
debugPrint('â
PDF loaded from file: $fileName (${document.pagesCount} pages)');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('đ $fileName loaded (${document.pagesCount} pages)'),
backgroundColor: Colors.green,
),
);
}
} else {
debugPrint('âšī¸ File picker cancelled');
}
} catch (e) {
debugPrint('â Failed to load PDF from file: $e');
setState(() {
_isLoading = false;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('â Failed to load PDF: $e'),
backgroundColor: Colors.red,
),
);
}
}
}
void _resetApp() {
debugPrint('đ Resetting app state...');
setState(() {
_document = null;
_annotations = {};
_isLoading = false;
_showDebugPanel = false;
_currentMode = AnnotationMode.view;
_currentColor = Colors.red;
_currentStrokeWidth = 2.0;
_currentShapeType = ShapeType.rectangle;
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('đ App reset successfully'),
backgroundColor: Colors.blue,
),
);
}
}
void _clearAllAnnotations() {
debugPrint('đī¸ Clearing all annotations...');
setState(() {
_annotations = {};
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('đī¸ All annotations cleared'),
backgroundColor: Colors.orange,
),
);
}
}
int _getTotalAnnotations(Map<int, List<Annotation>> annotations) {
return annotations.values.fold(0, (sum, list) => sum + list.length);
}
@override
void dispose() {
debugPrint('đ Disposing PDF Marks Example...');
// Note: PdfDocument disposal is handled automatically by pdfx
super.dispose();
}
}