// lib/utils/validators.dart import 'package:flutter/services.dart'; /// Common validators for Indian IDs, payments, and typical form fields. class Validators { // ----------------------------- // Regexes (compiled once) // ----------------------------- static final RegExp _panRegex = RegExp(r'^[A-Z]{5}[0-9]{4}[A-Z]$'); // GSTIN: 2-digit/valid state code, PAN, entity code (1-9A-Z), 'Z', checksum (0-9A-Z) static final RegExp _gstRegex = RegExp( r'^(0[1-9]|1[0-9]|2[0-9]|3[0-7])[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$', ); // Aadhaar digits only static final RegExp _aadhaarRegex = RegExp(r'^[2-9]\d{11}$'); // Name (letters + spaces + dots + hyphen/apostrophe) static final RegExp _nameRegex = RegExp(r"^[A-Za-z][A-Za-z .'\-]{1,49}$"); // Email (generic) static final RegExp _emailRegex = RegExp(r"^[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$"); // Indian mobile static final RegExp _mobileRegex = RegExp(r'^[6-9]\d{9}$'); // Pincode (India: 6 digits starting 1–9) static final RegExp _pincodeRegex = RegExp(r'^[1-9][0-9]{5}$'); // IFSC (4 letters + 0 + 6 alphanumeric) static final RegExp _ifscRegex = RegExp(r'^[A-Z]{4}0[A-Z0-9]{6}$'); // Bank account number (9–18 digits) static final RegExp _bankAccountRegex = RegExp(r'^\d{9,18}$'); // UPI ID (name@bank, simple check) static final RegExp _upiRegex = RegExp(r'^[\w.\-]{2,}@[\w]{2,}$', caseSensitive: false); // Strong password (8+ chars, upper, lower, digit, special) static final RegExp _passwordRegex = RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$'); // Date dd/mm/yyyy (basic validation) static final RegExp _dateRegex = RegExp(r'^([0-2][0-9]|3[0-1])/(0[1-9]|1[0-2])/[0-9]{4}$'); // URL static final RegExp _urlRegex = RegExp( r'^(https?:\/\/)?([a-zA-Z0-9.-]+)\.[a-zA-Z]{2,}(:\d+)?(\/\S*)?$'); // Transaction ID (alphanumeric, dashes/underscores, 8–36 chars) static final RegExp _transactionIdRegex = RegExp(r'^[A-Za-z0-9\-_]{8,36}$'); // ----------------------------- // PAN // ----------------------------- static bool isValidPAN(String? input) { if (input == null) return false; return _panRegex.hasMatch(input.trim().toUpperCase()); } // ----------------------------- // GSTIN // ----------------------------- static bool isValidGSTIN(String? input) { if (input == null) return false; return _gstRegex.hasMatch(_compact(input).toUpperCase()); } // ----------------------------- // Aadhaar // ----------------------------- static bool isValidAadhaar(String? input, {bool enforceChecksum = true}) { if (input == null) return false; final a = _digitsOnly(input); if (!_aadhaarRegex.hasMatch(a)) return false; return enforceChecksum ? _verhoeffValidate(a) : true; } // ----------------------------- // Mobile // ----------------------------- static bool isValidIndianMobile(String? input) { if (input == null) return false; final s = _digitsOnly(input.replaceFirst(RegExp(r'^(?:\+?91|0)'), '')); return _mobileRegex.hasMatch(s); } // ----------------------------- // Email // ----------------------------- static bool isValidEmail(String? input, {bool gmailOnly = false}) { if (input == null) return false; final e = input.trim(); if (!_emailRegex.hasMatch(e)) return false; if (!gmailOnly) return true; final domain = e.split('@').last.toLowerCase(); return domain == 'gmail.com' || domain == 'googlemail.com'; } static bool isValidGmail(String? input) => isValidEmail(input, gmailOnly: true); // ----------------------------- // Name // ----------------------------- static bool isValidName(String? input, {int minLen = 2, int maxLen = 50}) { if (input == null) return false; final s = input.trim(); if (s.length < minLen || s.length > maxLen) return false; return _nameRegex.hasMatch(s); } // ----------------------------- // Transaction ID // ----------------------------- static bool isValidTransactionId(String? input) { if (input == null) return false; return _transactionIdRegex.hasMatch(input.trim()); } // ----------------------------- // Other fields // ----------------------------- static bool isValidPincode(String? input) => input != null && _pincodeRegex.hasMatch(input.trim()); static bool isValidIFSC(String? input) => input != null && _ifscRegex.hasMatch(input.trim().toUpperCase()); static bool isValidBankAccount(String? input) => input != null && _bankAccountRegex.hasMatch(_digitsOnly(input)); static bool isValidUPI(String? input) => input != null && _upiRegex.hasMatch(input.trim()); static bool isValidPassword(String? input) => input != null && _passwordRegex.hasMatch(input.trim()); static bool isValidDate(String? input) => input != null && _dateRegex.hasMatch(input.trim()); static bool isValidURL(String? input) => input != null && _urlRegex.hasMatch(input.trim()); // ----------------------------- // Numbers // ----------------------------- static bool isInt(String? input) => input != null && int.tryParse(input.trim()) != null; static bool isDouble(String? input) => input != null && double.tryParse(input.trim()) != null; static bool isNumeric(String? input) => isInt(input) || isDouble(input); static bool isInRange(num? value, {num? min, num? max, bool inclusive = true}) { if (value == null) return false; if (min != null && (inclusive ? value < min : value <= min)) return false; if (max != null && (inclusive ? value > max : value >= max)) return false; return true; } // ----------------------------- // Flutter-friendly validator lambdas (return null when valid) // ----------------------------- static String? requiredField(String? v, {String fieldName = 'This field'}) => (v == null || v.trim().isEmpty) ? '$fieldName is required' : null; static String? panValidator(String? v) => isValidPAN(v) ? null : 'Enter a valid PAN (e.g., ABCDE1234F)'; static String? gstValidator(String? v, {bool optional = false}) { if (optional && (v == null || v.trim().isEmpty)) return null; return isValidGSTIN(v) ? null : 'Enter a valid GSTIN'; } static String? aadhaarValidator(String? v) => isValidAadhaar(v) ? null : 'Enter a valid Aadhaar (12 digits)'; static String? mobileValidator(String? v) => isValidIndianMobile(v) ? null : 'Enter a valid 10-digit mobile'; static String? emailValidator(String? v, {bool gmailOnly = false}) => isValidEmail(v, gmailOnly: gmailOnly) ? null : gmailOnly ? 'Enter a valid Gmail address' : 'Enter a valid email address'; static String? nameValidator(String? v, {int minLen = 2, int maxLen = 50}) => isValidName(v, minLen: minLen, maxLen: maxLen) ? null : 'Enter a valid name ($minLen–$maxLen chars)'; static String? transactionIdValidator(String? v) => isValidTransactionId(v) ? null : 'Enter a valid Transaction ID (8–36 chars, letters/numbers)'; static String? pincodeValidator(String? v) => isValidPincode(v) ? null : 'Enter a valid 6-digit pincode'; static String? ifscValidator(String? v) => isValidIFSC(v) ? null : 'Enter a valid IFSC code'; static String? bankAccountValidator(String? v) => isValidBankAccount(v) ? null : 'Enter a valid bank account (9–18 digits)'; static String? upiValidator(String? v) => isValidUPI(v) ? null : 'Enter a valid UPI ID'; static String? passwordValidator(String? v) => isValidPassword(v) ? null : 'Password must be 8+ chars with upper, lower, digit, special'; static String? dateValidator(String? v) => isValidDate(v) ? null : 'Enter date in dd/mm/yyyy format'; static String? urlValidator(String? v) => isValidURL(v) ? null : 'Enter a valid URL'; // ----------------------------- // Helpers // ----------------------------- static String _digitsOnly(String s) => s.replaceAll(RegExp(r'\D'), ''); static String _compact(String s) => s.replaceAll(RegExp(r'\s'), ''); // ----------------------------- // Verhoeff checksum (for Aadhaar) // ----------------------------- static const List> _verhoeffD = [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 2, 3, 4, 0, 6, 7, 8, 9, 5], [2, 3, 4, 0, 1, 7, 8, 9, 5, 6], [3, 4, 0, 1, 2, 8, 9, 5, 6, 7], [4, 0, 1, 2, 3, 9, 5, 6, 7, 8], [5, 9, 8, 7, 6, 0, 4, 3, 2, 1], [6, 5, 9, 8, 7, 1, 0, 4, 3, 2], [7, 6, 5, 9, 8, 2, 1, 0, 4, 3], [8, 7, 6, 5, 9, 3, 2, 1, 0, 4], [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], ]; static const List> _verhoeffP = [ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 5, 7, 6, 2, 8, 3, 0, 9, 4], [5, 8, 0, 3, 7, 9, 6, 1, 4, 2], [8, 9, 1, 6, 0, 4, 3, 5, 2, 7], [9, 4, 5, 3, 1, 2, 6, 8, 7, 0], [4, 2, 8, 6, 5, 7, 3, 9, 0, 1], [2, 7, 9, 3, 8, 0, 5, 4, 1, 6], [7, 0, 4, 6, 9, 1, 2, 3, 5, 8], ]; static bool _verhoeffValidate(String numStr) { int c = 0; final rev = numStr.split('').reversed.map(int.parse).toList(); for (int i = 0; i < rev.length; i++) { c = _verhoeffD[c][_verhoeffP[(i % 8)][rev[i]]]; } return c == 0; } } /// Common input formatters/masks useful in TextFields. class InputFormatters { static final digitsOnly = FilteringTextInputFormatter.digitsOnly; static final upperAlnum = FilteringTextInputFormatter.allow(RegExp(r'[A-Z0-9]')); static final upperLetters = FilteringTextInputFormatter.allow(RegExp(r'[A-Z]')); static final name = FilteringTextInputFormatter.allow(RegExp(r"[A-Za-z .'\-]")); static final alnumWithSpace = FilteringTextInputFormatter.allow(RegExp(r"[A-Za-z0-9 ]")); static LengthLimitingTextInputFormatter maxLen(int n) => LengthLimitingTextInputFormatter(n); }