Card expiry date and few small fixes

This commit is contained in:
Arseni
2026-01-12 21:24:58 +03:00
parent dedde76dd7
commit 0cb5101ed9
7 changed files with 57 additions and 5 deletions

View File

@@ -360,6 +360,7 @@
"enterBik": "Enter BIK",
"add": "Add",
"expiryDate": "Expiry (MM/YY)",
"enterExpiryDate": "Enter card expiry date",
"firstName": "First Name",
"enterFirstName": "Enter First Name",
"lastName": "Last Name",

View File

@@ -360,6 +360,7 @@
"enterBik": "Введите БИК",
"add": "Добавить",
"expiryDate": "Срок действия (ММ/ГГ)",
"enterExpiryDate": "Введите срок действия карты",
"firstName": "Имя",
"enterFirstName": "Введите имя",
"lastName": "Фамилия",

View File

@@ -30,6 +30,7 @@ class _CardFormMinimalState extends State<CardFormMinimal> {
late TextEditingController _panController;
late TextEditingController _firstNameController;
late TextEditingController _lastNameController;
late TextEditingController _expiryController;
@override
void initState() {
@@ -37,17 +38,23 @@ class _CardFormMinimalState extends State<CardFormMinimal> {
_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,
),
);
}
@@ -63,6 +70,7 @@ class _CardFormMinimalState extends State<CardFormMinimal> {
_panController.clear();
_firstNameController.clear();
_lastNameController.clear();
_expiryController.clear();
return;
}
@@ -70,12 +78,14 @@ class _CardFormMinimalState extends State<CardFormMinimal> {
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) {
if (hasPanChange || hasFirstNameChange || hasLastNameChange || hasExpiryChange) {
WidgetsBinding.instance.addPostFrameCallback((_) => _emitIfValid());
}
}
@@ -115,6 +125,16 @@ class _CardFormMinimalState extends State<CardFormMinimal> {
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,
),
],
),
);
@@ -125,6 +145,35 @@ class _CardFormMinimalState extends State<CardFormMinimal> {
_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});
}

View File

@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/recipient/provider.dart';
import 'package:pweb/pages/dashboard/payouts/form.dart';
import 'package:pweb/pages/dashboard/payouts/form.dart';
import 'package:pweb/pages/payment_methods/payment_page/back_button.dart';
import 'package:pweb/pages/payment_methods/payment_page/header.dart';
import 'package:pweb/pages/payment_methods/payment_page/method_selector.dart';

View File

@@ -23,7 +23,7 @@ class PaymentMethodDropdown extends StatelessWidget {
@override
Widget build(BuildContext context) => DropdownButtonFormField<Wallet>(
dropdownColor: Theme.of(context).colorScheme.onSecondary,
value: _getSelectedMethod(),
initialValue: _getSelectedMethod(),
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.whereGetMoney,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),