import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:signature/signature.dart'; import '../l10n/app_localizations.dart'; import '../models/tasks/signature_task.dart'; import '../widgets/offline_banner.dart'; class SignatureCaptureScreen extends StatefulWidget { final SignatureTask task; final void Function(String svg) onSignatureCompleted; const SignatureCaptureScreen({ super.key, required this.task, required this.onSignatureCompleted, }); @override State createState() => _SignatureCaptureScreenState(); } class _SignatureCaptureScreenState extends State { late final SignatureController _controller; bool _hasSignature = false; bool _isMobilePlatform = false; @override void initState() { super.initState(); _controller = SignatureController( penStrokeWidth: 3, penColor: Colors.black, exportBackgroundColor: Colors.white, ); // Listen to signature controller changes _controller.addListener(_onSignatureChanged); _detectPlatformAndSetOrientation(); } void _onSignatureChanged() { final bool hasPoints = _controller.points.isNotEmpty; if (hasPoints != _hasSignature) { setState(() { _hasSignature = hasPoints; }); } } void _detectPlatformAndSetOrientation() { // Check if we're on a mobile platform if (!kIsWeb) { switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.iOS: _isMobilePlatform = true; // Rotate screen 90 degrees to the right (landscape left) SystemChrome.setPreferredOrientations([ DeviceOrientation.landscapeLeft, ]); break; default: _isMobilePlatform = false; } } } void _restoreOrientation() { // Restore original orientation when leaving the screen if (_isMobilePlatform) { SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, ]); } } @override void dispose() { _controller.removeListener(_onSignatureChanged); _controller.dispose(); _restoreOrientation(); super.dispose(); } String _buildSvgFromPoints(List points, {double strokeWidth = 3.0, String strokeColor = '#000000'}) { // Convert collected signature points (with null separators for stroke breaks) into an SVG string // Determine bounds double? minX, minY, maxX, maxY; for (final p in points) { if (p == null) continue; final x = p.offset.dx; final y = p.offset.dy; if (minX == null || x < minX) minX = x; if (minY == null || y < minY) minY = y; if (maxX == null || x > maxX) maxX = x; if (maxY == null || y > maxY) maxY = y; } // Fallback bounds if empty or degenerate if (minX == null || minY == null || maxX == null || maxY == null) { minX = 0; minY = 0; maxX = 1; maxY = 1; } double width = (maxX - minX); double height = (maxY - minY); if (width <= 0) width = 1; if (height <= 0) height = 1; final StringBuffer d = StringBuffer(); bool newStroke = true; for (final p in points) { if (p == null) { newStroke = true; continue; } final x = (p.offset.dx - minX); final y = (p.offset.dy - minY); if (newStroke) { d.write('M${x.toStringAsFixed(2)} ${y.toStringAsFixed(2)} '); newStroke = false; } else { d.write('L${x.toStringAsFixed(2)} ${y.toStringAsFixed(2)} '); } } final String svg = ''; return svg; } Future _finish() async { try { // Ensure there is at least one non-null point in the signature final hasAnyPoint = _controller.points.isNotEmpty; if (!hasAnyPoint) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(AppLocalizations.of(context).signatureRequired)), ); return; } // Build SVG from the captured signature points final String svg = _buildSvgFromPoints(_controller.points); // Close this screen first to show the updated TaskView quickly if (!mounted) return; _restoreOrientation(); Navigator.of(context).pop(); // Then notify the caller (SVG only) widget.onSignatureCompleted(svg); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('${AppLocalizations.of(context).signatureError}: $e')), ); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context).signatureCapture), backgroundColor: Colors.deepPurple[100], leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { _restoreOrientation(); Navigator.of(context).pop(); }, ), actions: [ IconButton( tooltip: AppLocalizations.of(context).delete, onPressed: () { _controller.clear(); // The listener will automatically update _hasSignature when points are cleared }, icon: const Icon(Icons.delete_outline), ), ], ), body: Column( children: [ OfflineBanner(), Expanded( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( AppLocalizations.of(context).signatureInstruction, style: TextStyle(color: Colors.grey[700]), ), const SizedBox(height: 12), Expanded( child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[400]!), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 6, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: Signature( controller: _controller, backgroundColor: Colors.white, ), ), ), ), const SizedBox(height: 16), Row( children: [ OutlinedButton.icon( onPressed: () { _controller.clear(); // The listener will automatically update _hasSignature when points are cleared }, icon: const Icon(Icons.refresh), label: Text(AppLocalizations.of(context).clear), ), const Spacer(), SizedBox( width: 160, child: ElevatedButton( onPressed: _hasSignature ? _finish : null, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 14), ), child: Text(AppLocalizations.of(context).finish), ), ), ], ), ], ), ), ), ], ), ); } }