import 'package:flutter/material.dart'; import 'package:flutter_multi_formatter/flutter_multi_formatter.dart'; import 'package:pshared/models/payment/methods/card.dart'; import 'package:pweb/utils/text_field_styles.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; class CardFormMinimal extends StatefulWidget { final void Function(CardPaymentMethod) onChanged; final CardPaymentMethod? initialData; final bool isEditable; const CardFormMinimal({ super.key, required this.onChanged, this.initialData, required this.isEditable, }); @override State createState() => _CardFormMinimalState(); } class _CardFormMinimalState extends State { final _formKey = GlobalKey(); late TextEditingController _panController; late TextEditingController _firstNameController; late TextEditingController _lastNameController; late TextEditingController _expiryController; @override void initState() { super.initState(); _panController = TextEditingController(text: widget.initialData?.pan ?? ''); _firstNameController = TextEditingController(text: widget.initialData?.firstName ?? ''); _lastNameController = TextEditingController(text: widget.initialData?.lastName ?? ''); _expiryController = TextEditingController(text: _formatExpiry(widget.initialData)); WidgetsBinding.instance.addPostFrameCallback((_) => _emitIfValid()); } void _emitIfValid() { if (_formKey.currentState?.validate() ?? false) { final expiry = _parseExpiry(_expiryController.text); if (expiry == null) return; widget.onChanged( CardPaymentMethod( pan: _panController.text.replaceAll(' ', ''), firstName: _firstNameController.text, lastName: _lastNameController.text, expMonth: expiry.month, expYear: expiry.year, ), ); } } @override void didUpdateWidget(covariant CardFormMinimal oldWidget) { super.didUpdateWidget(oldWidget); final newData = widget.initialData; final oldData = oldWidget.initialData; if (newData == null && oldData != null) { _panController.clear(); _firstNameController.clear(); _lastNameController.clear(); _expiryController.clear(); return; } if (newData != null && newData != oldData) { final hasPanChange = newData.pan != _panController.text; final hasFirstNameChange = newData.firstName != _firstNameController.text; final hasLastNameChange = newData.lastName != _lastNameController.text; final hasExpiryChange = _formatExpiry(newData) != _expiryController.text; if (hasPanChange) _panController.text = newData.pan; if (hasFirstNameChange) _firstNameController.text = newData.firstName; if (hasLastNameChange) _lastNameController.text = newData.lastName; if (hasExpiryChange) _expiryController.text = _formatExpiry(newData); if (hasPanChange || hasFirstNameChange || hasLastNameChange || hasExpiryChange) { WidgetsBinding.instance.addPostFrameCallback((_) => _emitIfValid()); } } } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Form( key: _formKey, onChanged: _emitIfValid, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( readOnly: !widget.isEditable, controller: _panController, decoration: getInputDecoration(context, l10n.cardNumber, widget.isEditable), style: getTextFieldStyle(context, widget.isEditable), keyboardType: TextInputType.number, inputFormatters: [CreditCardNumberInputFormatter()], validator: (v) => (v == null || v.replaceAll(' ', '').length < 12) ? l10n.enterCardNumber : null, ), const SizedBox(height: 12), TextFormField( readOnly: !widget.isEditable, controller: _firstNameController, decoration: getInputDecoration(context, l10n.firstName, widget.isEditable), style: getTextFieldStyle(context, widget.isEditable), validator: (v) => (v == null || v.isEmpty) ? l10n.enterFirstName : null, ), const SizedBox(height: 12), TextFormField( readOnly: !widget.isEditable, controller: _lastNameController, decoration: getInputDecoration(context, l10n.lastName, widget.isEditable), style: getTextFieldStyle(context, widget.isEditable), validator: (v) => (v == null || v.isEmpty) ? l10n.enterLastName : null, ), const SizedBox(height: 12), TextFormField( readOnly: !widget.isEditable, controller: _expiryController, decoration: getInputDecoration(context, l10n.expiryDate, widget.isEditable), style: getTextFieldStyle(context, widget.isEditable), keyboardType: TextInputType.number, inputFormatters: [CreditCardExpirationDateFormatter()], validator: (v) => _parseExpiry(v ?? '') == null ? l10n.enterExpiryDate : null, ), ], ), ); } @override void dispose() { _panController.dispose(); _firstNameController.dispose(); _lastNameController.dispose(); _expiryController.dispose(); super.dispose(); } String _formatExpiry(CardPaymentMethod? data) { if (data == null) return ''; if (data.expMonth == null || data.expYear == null) return ''; final month = data.expMonth!.toString().padLeft(2, '0'); final year = (data.expYear! % 100).toString().padLeft(2, '0'); return '$month/$year'; } _CardExpiry? _parseExpiry(String value) { final normalized = value.trim(); final match = RegExp(r'^(\d{2})\s*/\s*(\d{2})$').firstMatch(normalized); if (match == null) return null; final month = int.tryParse(match.group(1)!); final year = int.tryParse(match.group(2)!); if (month == null || year == null || month < 1 || month > 12) return null; final normalizedYear = 2000 + year; return _CardExpiry(month: month, year: normalizedYear); } } class _CardExpiry { final int month; final int year; const _CardExpiry({required this.month, required this.year}); }