/// Form Validators for Vietnamese Locale /// /// Provides validation utilities for forms with Vietnamese-specific /// validations for phone numbers, email, passwords, etc. library; /// Form field validators class Validators { Validators._(); // ======================================================================== // Required Field Validators // ======================================================================== /// Validate required field static String? required(String? value, {String? fieldName}) { if (value == null || value.trim().isEmpty) { return fieldName != null ? '$fieldName là bắt buộc' : 'Trường này là bắt buộc'; } return null; } // ======================================================================== // Phone Number Validators // ======================================================================== /// Validate Vietnamese phone number /// /// Accepts formats: /// - 0xxx xxx xxx (10 digits starting with 0) /// - +84xxx xxx xxx (starts with +84) /// - 84xxx xxx xxx (starts with 84) static String? phone(String? value) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập số điện thoại'; } final cleaned = value.replaceAll(RegExp(r'\D'), ''); // Check if starts with valid Vietnamese mobile prefix final vietnamesePattern = RegExp(r'^(0|\+?84)(3|5|7|8|9)[0-9]{8}$'); if (!vietnamesePattern.hasMatch(value.replaceAll(RegExp(r'[^\d+]'), ''))) { return 'Số điện thoại không hợp lệ'; } if (cleaned.length < 10 || cleaned.length > 11) { return 'Số điện thoại phải có 10 chữ số'; } return null; } /// Validate phone number (optional) static String? phoneOptional(String? value) { if (value == null || value.trim().isEmpty) { return null; // Optional, so null is valid } return phone(value); } // ======================================================================== // Email Validators // ======================================================================== /// Validate email address static String? email(String? value) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập email'; } final emailRegex = RegExp( r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', ); if (!emailRegex.hasMatch(value)) { return 'Email không hợp lệ'; } return null; } /// Validate email (optional) static String? emailOptional(String? value) { if (value == null || value.trim().isEmpty) { return null; } return email(value); } // ======================================================================== // Password Validators // ======================================================================== /// Validate password strength /// /// Requirements: /// - At least 8 characters /// - At least 1 uppercase letter /// - At least 1 lowercase letter /// - At least 1 number /// - At least 1 special character static String? password(String? value) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập mật khẩu'; } if (value.length < 8) { return 'Mật khẩu phải có ít nhất 8 ký tự'; } if (!RegExp(r'[A-Z]').hasMatch(value)) { return 'Mật khẩu phải có ít nhất 1 chữ hoa'; } if (!RegExp(r'[a-z]').hasMatch(value)) { return 'Mật khẩu phải có ít nhất 1 chữ thường'; } if (!RegExp(r'[0-9]').hasMatch(value)) { return 'Mật khẩu phải có ít nhất 1 số'; } if (!RegExp(r'[!@#$%^&*(),.?":{}|<>]').hasMatch(value)) { return 'Mật khẩu phải có ít nhất 1 ký tự đặc biệt'; } return null; } /// Validate password confirmation static String? confirmPassword(String? value, String? password) { if (value == null || value.trim().isEmpty) { return 'Vui lòng xác nhận mật khẩu'; } if (value != password) { return 'Mật khẩu không khớp'; } return null; } /// Simple password validator (minimum length only) static String? passwordSimple(String? value, {int minLength = 6}) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập mật khẩu'; } if (value.length < minLength) { return 'Mật khẩu phải có ít nhất $minLength ký tự'; } return null; } // ======================================================================== // OTP Validators // ======================================================================== /// Validate OTP code static String? otp(String? value, {int length = 6}) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập mã OTP'; } if (value.length != length) { return 'Mã OTP phải có $length chữ số'; } if (!RegExp(r'^[0-9]+$').hasMatch(value)) { return 'Mã OTP chỉ được chứa số'; } return null; } // ======================================================================== // Text Length Validators // ======================================================================== /// Validate minimum length static String? minLength(String? value, int min, {String? fieldName}) { if (value == null || value.trim().isEmpty) { return fieldName != null ? '$fieldName là bắt buộc' : 'Trường này là bắt buộc'; } if (value.length < min) { return fieldName != null ? '$fieldName phải có ít nhất $min ký tự' : 'Phải có ít nhất $min ký tự'; } return null; } /// Validate maximum length static String? maxLength(String? value, int max, {String? fieldName}) { if (value != null && value.length > max) { return fieldName != null ? '$fieldName không được vượt quá $max ký tự' : 'Không được vượt quá $max ký tự'; } return null; } /// Validate length range static String? lengthRange( String? value, int min, int max, { String? fieldName, }) { if (value == null || value.trim().isEmpty) { return fieldName != null ? '$fieldName là bắt buộc' : 'Trường này là bắt buộc'; } if (value.length < min || value.length > max) { return fieldName != null ? '$fieldName phải có từ $min đến $max ký tự' : 'Phải có từ $min đến $max ký tự'; } return null; } // ======================================================================== // Number Validators // ======================================================================== /// Validate number static String? number(String? value, {String? fieldName}) { if (value == null || value.trim().isEmpty) { return fieldName != null ? '$fieldName là bắt buộc' : 'Trường này là bắt buộc'; } if (double.tryParse(value) == null) { return fieldName != null ? '$fieldName phải là số' : 'Giá trị phải là số'; } return null; } /// Validate integer static String? integer(String? value, {String? fieldName}) { if (value == null || value.trim().isEmpty) { return fieldName != null ? '$fieldName là bắt buộc' : 'Trường này là bắt buộc'; } if (int.tryParse(value) == null) { return fieldName != null ? '$fieldName phải là số nguyên' : 'Giá trị phải là số nguyên'; } return null; } /// Validate positive number static String? positiveNumber(String? value, {String? fieldName}) { final numberError = number(value, fieldName: fieldName); if (numberError != null) return numberError; final num = double.parse(value!); if (num <= 0) { return fieldName != null ? '$fieldName phải lớn hơn 0' : 'Giá trị phải lớn hơn 0'; } return null; } /// Validate number range static String? numberRange( String? value, double min, double max, { String? fieldName, }) { final numberError = number(value, fieldName: fieldName); if (numberError != null) return numberError; final num = double.parse(value!); if (num < min || num > max) { return fieldName != null ? '$fieldName phải từ $min đến $max' : 'Giá trị phải từ $min đến $max'; } return null; } // ======================================================================== // Date Validators // ======================================================================== /// Validate date format (dd/MM/yyyy) static String? date(String? value) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập ngày'; } final dateRegex = RegExp(r'^\d{2}/\d{2}/\d{4}$'); if (!dateRegex.hasMatch(value)) { return 'Định dạng ngày không hợp lệ (dd/MM/yyyy)'; } try { final parts = value.split('/'); final day = int.parse(parts[0]); final month = int.parse(parts[1]); final year = int.parse(parts[2]); final date = DateTime(year, month, day); if (date.day != day || date.month != month || date.year != year) { return 'Ngày không hợp lệ'; } } catch (e) { return 'Ngày không hợp lệ'; } return null; } /// Validate age (must be at least 18 years old) static String? age(String? value, {int minAge = 18}) { final dateError = date(value); if (dateError != null) return dateError; try { final parts = value!.split('/'); final birthDate = DateTime( int.parse(parts[2]), int.parse(parts[1]), int.parse(parts[0]), ); final today = DateTime.now(); final age = today.year - birthDate.year - (today.month > birthDate.month || (today.month == birthDate.month && today.day >= birthDate.day) ? 0 : 1); if (age < minAge) { return 'Bạn phải từ $minAge tuổi trở lên'; } return null; } catch (e) { return 'Ngày sinh không hợp lệ'; } } // ======================================================================== // Address Validators // ======================================================================== /// Validate Vietnamese address static String? address(String? value) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập địa chỉ'; } if (value.length < 10) { return 'Địa chỉ quá ngắn'; } return null; } // ======================================================================== // Tax ID Validators // ======================================================================== /// Validate Vietnamese Tax ID (Mã số thuế) /// Format: 10 or 13 digits static String? taxId(String? value) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập mã số thuế'; } final cleaned = value.replaceAll(RegExp(r'\D'), ''); if (cleaned.length != 10 && cleaned.length != 13) { return 'Mã số thuế phải có 10 hoặc 13 chữ số'; } return null; } /// Validate tax ID (optional) static String? taxIdOptional(String? value) { if (value == null || value.trim().isEmpty) { return null; } return taxId(value); } // ======================================================================== // URL Validators // ======================================================================== /// Validate URL static String? url(String? value) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập URL'; } final urlRegex = RegExp( r'^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$', ); if (!urlRegex.hasMatch(value)) { return 'URL không hợp lệ'; } return null; } // ======================================================================== // Combination Validators // ======================================================================== /// Combine multiple validators static String? Function(String?) combine( List validators, ) { return (String? value) { for (final validator in validators) { final error = validator(value); if (error != null) return error; } return null; }; } // ======================================================================== // Custom Pattern Validators // ======================================================================== /// Validate against custom regex pattern static String? pattern(String? value, RegExp pattern, String errorMessage) { if (value == null || value.trim().isEmpty) { return 'Trường này là bắt buộc'; } if (!pattern.hasMatch(value)) { return errorMessage; } return null; } // ======================================================================== // Match Validators // ======================================================================== /// Validate that value matches another value static String? match(String? value, String? matchValue, String fieldName) { if (value == null || value.trim().isEmpty) { return 'Vui lòng nhập $fieldName'; } if (value != matchValue) { return '$fieldName không khớp'; } return null; } } /// Password strength enum enum PasswordStrength { weak, medium, strong, veryStrong } /// Password strength calculator class PasswordStrengthCalculator { /// Calculate password strength static PasswordStrength calculate(String password) { if (password.isEmpty) return PasswordStrength.weak; var score = 0; // Length check if (password.length >= 8) score++; if (password.length >= 12) score++; if (password.length >= 16) score++; // Character variety check if (RegExp(r'[a-z]').hasMatch(password)) score++; if (RegExp(r'[A-Z]').hasMatch(password)) score++; if (RegExp(r'[0-9]').hasMatch(password)) score++; if (RegExp(r'[!@#$%^&*(),.?":{}|<>]').hasMatch(password)) score++; // Return strength based on score if (score <= 2) return PasswordStrength.weak; if (score <= 4) return PasswordStrength.medium; if (score <= 6) return PasswordStrength.strong; return PasswordStrength.veryStrong; } /// Get strength label in Vietnamese static String getLabel(PasswordStrength strength) { switch (strength) { case PasswordStrength.weak: return 'Yếu'; case PasswordStrength.medium: return 'Trung bình'; case PasswordStrength.strong: return 'Mạnh'; case PasswordStrength.veryStrong: return 'Rất mạnh'; } } }