import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:signature/signature.dart'; import '../../../../widget/info_dialog.dart'; /// Upper bound the server accepts for a signature image. const int maxSignatureBytes = 256 * 1024; /// Lets the guardian draw a signature. Resolves to the PNG (transparent /// background, cropped to the strokes) or null when dismissed. Future showSignatureSheet( BuildContext context, { required String signerHint, }) => showModalBottomSheet( context: context, isScrollControlled: true, enableDrag: false, showDragHandle: false, builder: (_) => _SignatureSheet(signerHint: signerHint), ); class _SignatureSheet extends StatefulWidget { final String signerHint; const _SignatureSheet({required this.signerHint}); @override State<_SignatureSheet> createState() => _SignatureSheetState(); } class _SignatureSheetState extends State<_SignatureSheet> { // Paper-like pad in both themes: what is drawn is what gets exported. final SignatureController _controller = SignatureController( penStrokeWidth: 2.5, penColor: Colors.black, strokeCap: StrokeCap.round, strokeJoin: StrokeJoin.round, ); @override void initState() { super.initState(); _controller.addListener(_onChanged); } bool _isEmpty = true; // The controller notifies per drawn point; only the buttons depend on it. void _onChanged() { if (_controller.isEmpty == _isEmpty) return; setState(() => _isEmpty = _controller.isEmpty); } @override void dispose() { _controller.removeListener(_onChanged); _controller.dispose(); super.dispose(); } Future _accept() async { var png = await _controller.toPngBytes(); if (png != null && png.lengthInBytes > maxSignatureBytes) { png = await _controller.toPngBytes(width: 600); } if (!mounted) return; if (png == null || png.lengthInBytes > maxSignatureBytes) { InfoDialog.show( context, 'Die Unterschrift konnte nicht übernommen werden. Bitte versuche es ' 'erneut.', title: 'Unterschrift', ); return; } Navigator.pop(context, png); } @override Widget build(BuildContext context) => SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('Unterschrift', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 4), Text(widget.signerHint), const SizedBox(height: 12), ClipRRect( borderRadius: BorderRadius.circular(12), child: Signature( controller: _controller, height: 220, backgroundColor: Colors.white, ), ), const SizedBox(height: 8), Row( children: [ TextButton.icon( onPressed: _isEmpty ? null : _controller.clear, icon: const Icon(Icons.undo), label: const Text('Löschen'), ), const Spacer(), TextButton( onPressed: () => Navigator.pop(context), child: const Text('Abbrechen'), ), const SizedBox(width: 8), FilledButton( onPressed: _isEmpty ? null : _accept, child: const Text('Übernehmen'), ), ], ), ], ), ), ); }