import 'Thresholds.dart'; /// Field-level validation. Returns null when valid, so it drops straight into /// a TextFormField validator. class Validators { static String? required(String? value, String field) { if (value == null || value.trim().isEmpty) { return "$field is required."; } return null; } static String? title(String? value) { final String? empty = required(value, "Title"); if (empty != null) { return empty; } if (value!.trim().length < 3) { return "Give it a name you will recognise later."; } return null; } /// The excuse gate. Free text only, minimum length, no template buttons — /// the friction is the point. static String? excuse(String? value) { if (value == null || value.trim().isEmpty) { return "An excuse is required to defer."; } if (value.trim().length < Thresholds.minExcuseLength) { return "Write at least ${Thresholds.minExcuseLength} characters."; } return null; } static String? estimateMinutes(String? value) { final String? empty = required(value, "Estimate"); if (empty != null) { return empty; } final int? minutes = int.tryParse(value!.trim()); if (minutes == null || minutes <= 0) { return "Enter the minutes you think it will take."; } if (minutes > 720) { return "Nothing on a daily plan takes more than 12 hours. Split it."; } return null; } /// A window, not a date. The end must actually close after the start. static String? window(DateTime? start, DateTime? end) { if (start == null || end == null) { return "Set a due window, not just a day."; } if (!end.isAfter(start)) { return "The window has to close after it opens."; } return null; } static String? username(String? value) { final String? empty = required(value, "Username"); if (empty != null) { return empty; } return null; } static String? password(String? value) { final String? empty = required(value, "Password"); if (empty != null) { return empty; } if (value!.length < 8) { return "Passwords are at least 8 characters."; } return null; } }