import 'dart:math' as math; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../data/model/model.dart'; class PdfController extends StateNotifier { PdfController() : super(PdfState.initial()); static const int samplePageCount = 5; void openSample() { state = state.copyWith( loaded: true, pageCount: samplePageCount, currentPage: 1, markedForSigning: false, pickedPdfPath: null, signedPage: null, ); } void openPicked({ required String path, int pageCount = samplePageCount, Uint8List? bytes, }) { state = state.copyWith( loaded: true, pageCount: pageCount, currentPage: 1, markedForSigning: false, pickedPdfPath: path, pickedPdfBytes: bytes, signedPage: null, ); } void jumpTo(int page) { if (!state.loaded) return; final clamped = page.clamp(1, state.pageCount); state = state.copyWith(currentPage: clamped); } void toggleMark() { if (!state.loaded) return; if (state.signedPage != null) { state = state.copyWith(markedForSigning: false, signedPage: null); } else { state = state.copyWith( markedForSigning: true, signedPage: state.currentPage, ); } } void setPageCount(int count) { if (!state.loaded) return; state = state.copyWith(pageCount: count.clamp(1, 9999)); } } final pdfProvider = StateNotifierProvider( (ref) => PdfController(), ); class SignatureController extends StateNotifier { SignatureController() : super(SignatureState.initial()); static const Size pageSize = Size(400, 560); void resetForNewPage() { state = SignatureState.initial(); } void placeDefaultRect() { final w = 120.0, h = 60.0; state = state.copyWith( rect: Rect.fromCenter( center: Offset(pageSize.width / 2, pageSize.height * 0.75), width: w, height: h, ), ); } void loadSample() { final w = 120.0, h = 60.0; state = state.copyWith( rect: Rect.fromCenter( center: Offset(pageSize.width / 2, pageSize.height * 0.75), width: w, height: h, ), ); } void setInvalidSelected(BuildContext context) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Invalid or unsupported file')), ); } void drag(Offset delta) { if (state.rect == null) return; final moved = state.rect!.shift(delta); state = state.copyWith(rect: _clampRectToPage(moved)); } void resize(Offset delta) { if (state.rect == null) return; final r = state.rect!; double newW = r.width + delta.dx; double newH = r.height + delta.dy; if (state.aspectLocked) { final aspect = r.width / r.height; // Keep ratio based on the dominant proportional delta final dxRel = (delta.dx / r.width).abs(); final dyRel = (delta.dy / r.height).abs(); if (dxRel >= dyRel) { newW = newW.clamp(20.0, double.infinity); newH = newW / aspect; } else { newH = newH.clamp(20.0, double.infinity); newW = newH * aspect; } // Scale down to fit within page bounds while preserving ratio final scaleW = pageSize.width / newW; final scaleH = pageSize.height / newH; final scale = math.min(1.0, math.min(scaleW, scaleH)); newW *= scale; newH *= scale; // Ensure minimum size of 20x20, scaling up proportionally if needed final minScale = math.max(1.0, math.max(20.0 / newW, 20.0 / newH)); newW *= minScale; newH *= minScale; Rect resized = Rect.fromLTWH(r.left, r.top, newW, newH); resized = _clampRectPositionToPage(resized); state = state.copyWith(rect: resized); return; } // Unlocked aspect: clamp each dimension independently newW = newW.clamp(20.0, pageSize.width); newH = newH.clamp(20.0, pageSize.height); Rect resized = Rect.fromLTWH(r.left, r.top, newW, newH); resized = _clampRectToPage(resized); state = state.copyWith(rect: resized); } Rect _clampRectToPage(Rect r) { // Ensure size never exceeds page bounds first, to avoid invalid clamp ranges final double w = r.width.clamp(20.0, pageSize.width); final double h = r.height.clamp(20.0, pageSize.height); final double left = r.left.clamp(0.0, pageSize.width - w); final double top = r.top.clamp(0.0, pageSize.height - h); return Rect.fromLTWH(left, top, w, h); } Rect _clampRectPositionToPage(Rect r) { final double left = r.left.clamp(0.0, pageSize.width - r.width); final double top = r.top.clamp(0.0, pageSize.height - r.height); return Rect.fromLTWH(left, top, r.width, r.height); } void toggleAspect(bool v) => state = state.copyWith(aspectLocked: v); void setBgRemoval(bool v) => state = state.copyWith(bgRemoval: v); void setContrast(double v) => state = state.copyWith(contrast: v); void setBrightness(double v) => state = state.copyWith(brightness: v); void setStrokes(List> strokes) => state = state.copyWith(strokes: strokes); void ensureRectForStrokes() { state = state.copyWith( rect: state.rect ?? Rect.fromCenter( center: Offset(pageSize.width / 2, pageSize.height * 0.75), width: 140, height: 70, ), ); } void setImageBytes(Uint8List bytes) { state = state.copyWith(imageBytes: bytes); if (state.rect == null) { placeDefaultRect(); } } } final signatureProvider = StateNotifierProvider( (ref) => SignatureController(), );