Files
unionflow-mobile-apps/lib/core/validation/validators.dart
dahoud d094d6db9c Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
2026-03-15 16:30:08 +00:00

356 lines
9.3 KiB
Dart

/// Core validation utilities for form fields
library validators;
/// Validator function type
typedef FieldValidator = String? Function(String?)?;
/// Compose multiple validators
FieldValidator composeValidators(List<FieldValidator> validators) {
return (String? value) {
for (final validator in validators) {
final result = validator?.call(value);
if (result != null) {
return result;
}
}
return null;
};
}
/// Common validators
class Validators {
Validators._(); // Prevent instantiation
/// Required field validator
static FieldValidator required({String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return message ?? 'Ce champ est requis';
}
return null;
};
}
/// Minimum length validator
static FieldValidator minLength(int length, {String? message}) {
return (String? value) {
if (value != null && value.trim().length < length) {
return message ?? 'Minimum $length caractères requis';
}
return null;
};
}
/// Maximum length validator
static FieldValidator maxLength(int length, {String? message}) {
return (String? value) {
if (value != null && value.length > length) {
return message ?? 'Maximum $length caractères autorisés';
}
return null;
};
}
/// Email validator
static FieldValidator email({String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null; // Use required() separately if needed
}
final emailRegex = RegExp(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
);
if (!emailRegex.hasMatch(value.trim())) {
return message ?? 'Adresse email invalide';
}
return null;
};
}
/// Numeric validator
static FieldValidator numeric({String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null; // Use required() separately if needed
}
if (double.tryParse(value.trim()) == null) {
return message ?? 'Veuillez entrer un nombre valide';
}
return null;
};
}
/// Minimum value validator (for numeric fields)
static FieldValidator minValue(double min, {String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
final numValue = double.tryParse(value.trim());
if (numValue == null) {
return 'Nombre invalide';
}
if (numValue < min) {
return message ?? 'La valeur doit être au moins $min';
}
return null;
};
}
/// Maximum value validator (for numeric fields)
static FieldValidator maxValue(double max, {String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
final numValue = double.tryParse(value.trim());
if (numValue == null) {
return 'Nombre invalide';
}
if (numValue > max) {
return message ?? 'La valeur doit être au maximum $max';
}
return null;
};
}
/// Range validator (for numeric fields)
static FieldValidator range(double min, double max, {String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
final numValue = double.tryParse(value.trim());
if (numValue == null) {
return 'Nombre invalide';
}
if (numValue < min || numValue > max) {
return message ?? 'La valeur doit être entre $min et $max';
}
return null;
};
}
/// Phone number validator (simple version)
static FieldValidator phone({String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
// Allow digits, spaces, +, -, ()
final phoneRegex = RegExp(r'^[\d\s\+\-\(\)]+$');
if (!phoneRegex.hasMatch(value.trim())) {
return message ?? 'Numéro de téléphone invalide';
}
// Check minimum length (at least 8 digits)
final digitsOnly = value.replaceAll(RegExp(r'[^\d]'), '');
if (digitsOnly.length < 8) {
return message ?? 'Numéro de téléphone trop court';
}
return null;
};
}
/// URL validator
static FieldValidator url({String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
try {
final uri = Uri.parse(value.trim());
if (!uri.hasScheme || !uri.hasAuthority) {
return message ?? 'URL invalide';
}
} catch (e) {
return message ?? 'URL invalide';
}
return null;
};
}
/// Pattern/Regex validator
static FieldValidator pattern(RegExp regex, {String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
if (!regex.hasMatch(value.trim())) {
return message ?? 'Format invalide';
}
return null;
};
}
/// Match validator (confirm password, etc.)
static FieldValidator match(String otherValue, {String? message}) {
return (String? value) {
if (value != otherValue) {
return message ?? 'Les valeurs ne correspondent pas';
}
return null;
};
}
/// Custom validator
static FieldValidator custom(bool Function(String?) test, {String? message}) {
return (String? value) {
if (!test(value)) {
return message ?? 'Valeur invalide';
}
return null;
};
}
/// Alphanumeric validator
static FieldValidator alphanumeric({String? message}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
final alphanumericRegex = RegExp(r'^[a-zA-Z0-9]+$');
if (!alphanumericRegex.hasMatch(value.trim())) {
return message ?? 'Seuls les caractères alphanumériques sont autorisés';
}
return null;
};
}
/// No whitespace validator
static FieldValidator noWhitespace({String? message}) {
return (String? value) {
if (value == null) return null;
if (value.contains(' ')) {
return message ?? 'Les espaces ne sont pas autorisés';
}
return null;
};
}
}
/// Finance-specific validators
class FinanceValidators {
FinanceValidators._();
/// Amount validator (positive number with max 2 decimals)
static FieldValidator amount({
double? min,
double? max,
String? message,
}) {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return null;
}
// Check if numeric
final numValue = double.tryParse(value.trim());
if (numValue == null) {
return 'Montant invalide';
}
// Check if positive
if (numValue <= 0) {
return 'Le montant doit être positif';
}
// Check min/max
if (min != null && numValue < min) {
return message ?? 'Le montant minimum est $min';
}
if (max != null && numValue > max) {
return message ?? 'Le montant maximum est $max';
}
// Check max 2 decimals
final parts = value.trim().split('.');
if (parts.length > 1 && parts[1].length > 2) {
return 'Maximum 2 décimales autorisées';
}
return null;
};
}
/// Budget line name validator
static FieldValidator budgetLineName() {
return composeValidators([
Validators.required(message: 'Le nom de la ligne budgétaire est requis'),
Validators.minLength(3, message: 'Minimum 3 caractères'),
Validators.maxLength(100, message: 'Maximum 100 caractères'),
]);
}
/// Budget description validator
static FieldValidator budgetDescription({bool required = false}) {
return composeValidators([
if (required)
Validators.required(message: 'La description est requise'),
Validators.maxLength(500, message: 'Maximum 500 caractères'),
]);
}
/// Rejection reason validator
static FieldValidator rejectionReason() {
return composeValidators([
Validators.required(message: 'La raison du rejet est requise'),
Validators.minLength(10, message: 'Veuillez fournir une raison plus détaillée (min 10 caractères)'),
Validators.maxLength(500, message: 'Maximum 500 caractères'),
]);
}
/// Approval comment validator (optional but with constraints if provided)
static FieldValidator approvalComment() {
return composeValidators([
Validators.maxLength(500, message: 'Maximum 500 caractères'),
]);
}
/// Budget name validator
static FieldValidator budgetName() {
return composeValidators([
Validators.required(message: 'Le nom du budget est requis'),
Validators.minLength(3, message: 'Minimum 3 caractères'),
Validators.maxLength(200, message: 'Maximum 200 caractères'),
]);
}
/// Fiscal year validator
static FieldValidator fiscalYear() {
return (String? value) {
if (value == null || value.trim().isEmpty) {
return 'L\'année est requise';
}
final year = int.tryParse(value.trim());
if (year == null) {
return 'Année invalide';
}
final currentYear = DateTime.now().year;
if (year < currentYear - 5 || year > currentYear + 10) {
return 'L\'année doit être entre ${currentYear - 5} et ${currentYear + 10}';
}
return null;
};
}
}