/// Date Picker Input Field /// /// Input field that opens a date picker dialog when tapped. /// Displays dates in Vietnamese format (dd/MM/yyyy). library; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import '../../core/utils/formatters.dart'; import '../../core/utils/validators.dart'; import '../../core/constants/ui_constants.dart'; /// Date picker input field class DatePickerField extends StatefulWidget { final TextEditingController? controller; final String? labelText; final String? hintText; final DateTime? initialDate; final DateTime? firstDate; final DateTime? lastDate; final ValueChanged? onDateSelected; final FormFieldValidator? validator; final bool enabled; final bool required; final Widget? prefixIcon; final Widget? suffixIcon; final InputDecoration? decoration; const DatePickerField({ super.key, this.controller, this.labelText, this.hintText, this.initialDate, this.firstDate, this.lastDate, this.onDateSelected, this.validator, this.enabled = true, this.required = true, this.prefixIcon, this.suffixIcon, this.decoration, }); @override State createState() => _DatePickerFieldState(); } class _DatePickerFieldState extends State { late TextEditingController _controller; bool _isControllerInternal = false; DateTime? _selectedDate; @override void initState() { super.initState(); _selectedDate = widget.initialDate; if (widget.controller == null) { _controller = TextEditingController( text: _selectedDate != null ? DateFormatter.formatDate(_selectedDate!) : '', ); _isControllerInternal = true; } else { _controller = widget.controller!; } } @override void dispose() { if (_isControllerInternal) { _controller.dispose(); } super.dispose(); } Future _selectDate(BuildContext context) async { if (!widget.enabled) return; final DateTime? picked = await showDatePicker( context: context, initialDate: _selectedDate ?? DateTime.now(), firstDate: widget.firstDate ?? DateTime(1900), lastDate: widget.lastDate ?? DateTime(2100), locale: const Locale('vi', 'VN'), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: ColorScheme.light( primary: Theme.of(context).primaryColor, ), ), child: child!, ); }, ); if (picked != null && picked != _selectedDate) { setState(() { _selectedDate = picked; _controller.text = DateFormatter.formatDate(picked); }); widget.onDateSelected?.call(picked); } } @override Widget build(BuildContext context) { return TextFormField( controller: _controller, readOnly: true, enabled: widget.enabled, onTap: () => _selectDate(context), decoration: widget.decoration ?? InputDecoration( labelText: widget.labelText ?? 'Ngày', hintText: widget.hintText ?? 'dd/MM/yyyy', prefixIcon: widget.prefixIcon ?? const FaIcon(FontAwesomeIcons.calendar, size: 20), suffixIcon: widget.suffixIcon, border: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), ), contentPadding: InputFieldSpecs.contentPadding, ), validator: widget.validator ?? (widget.required ? Validators.date : null), ); } } /// Date range picker field class DateRangePickerField extends StatefulWidget { final String? labelText; final String? hintText; final DateTimeRange? initialRange; final DateTime? firstDate; final DateTime? lastDate; final ValueChanged? onRangeSelected; final bool enabled; final Widget? prefixIcon; const DateRangePickerField({ super.key, this.labelText, this.hintText, this.initialRange, this.firstDate, this.lastDate, this.onRangeSelected, this.enabled = true, this.prefixIcon, }); @override State createState() => _DateRangePickerFieldState(); } class _DateRangePickerFieldState extends State { late TextEditingController _controller; DateTimeRange? _selectedRange; @override void initState() { super.initState(); _selectedRange = widget.initialRange; _controller = TextEditingController( text: _selectedRange != null ? '${DateFormatter.formatDate(_selectedRange!.start)} - ${DateFormatter.formatDate(_selectedRange!.end)}' : '', ); } @override void dispose() { _controller.dispose(); super.dispose(); } Future _selectDateRange(BuildContext context) async { if (!widget.enabled) return; final DateTimeRange? picked = await showDateRangePicker( context: context, initialDateRange: _selectedRange, firstDate: widget.firstDate ?? DateTime(1900), lastDate: widget.lastDate ?? DateTime(2100), locale: const Locale('vi', 'VN'), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: ColorScheme.light( primary: Theme.of(context).primaryColor, ), ), child: child!, ); }, ); if (picked != null && picked != _selectedRange) { setState(() { _selectedRange = picked; _controller.text = '${DateFormatter.formatDate(picked.start)} - ${DateFormatter.formatDate(picked.end)}'; }); widget.onRangeSelected?.call(picked); } } @override Widget build(BuildContext context) { return TextFormField( controller: _controller, readOnly: true, enabled: widget.enabled, onTap: () => _selectDateRange(context), decoration: InputDecoration( labelText: widget.labelText ?? 'Khoảng thời gian', hintText: widget.hintText ?? 'Chọn khoảng thời gian', prefixIcon: widget.prefixIcon ?? const FaIcon(FontAwesomeIcons.calendarDays, size: 20), border: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), ), contentPadding: InputFieldSpecs.contentPadding, ), ); } } /// Date of birth picker field class DateOfBirthField extends StatelessWidget { final TextEditingController? controller; final String? labelText; final String? hintText; final ValueChanged? onDateSelected; final FormFieldValidator? validator; final bool enabled; final int minAge; const DateOfBirthField({ super.key, this.controller, this.labelText, this.hintText, this.onDateSelected, this.validator, this.enabled = true, this.minAge = 18, }); @override Widget build(BuildContext context) { final now = DateTime.now(); final maxDate = DateTime(now.year - minAge, now.month, now.day); final minDate = DateTime(now.year - 100, now.month, now.day); return DatePickerField( controller: controller, labelText: labelText ?? 'Ngày sinh', hintText: hintText ?? 'dd/MM/yyyy', initialDate: maxDate, firstDate: minDate, lastDate: maxDate, onDateSelected: onDateSelected, validator: validator ?? (value) => Validators.age(value, minAge: minAge), enabled: enabled, prefixIcon: const FaIcon(FontAwesomeIcons.cakeCandles, size: 20), ); } } /// Time picker field class TimePickerField extends StatefulWidget { final TextEditingController? controller; final String? labelText; final String? hintText; final TimeOfDay? initialTime; final ValueChanged? onTimeSelected; final bool enabled; final Widget? prefixIcon; const TimePickerField({ super.key, this.controller, this.labelText, this.hintText, this.initialTime, this.onTimeSelected, this.enabled = true, this.prefixIcon, }); @override State createState() => _TimePickerFieldState(); } class _TimePickerFieldState extends State { late TextEditingController _controller; bool _isControllerInternal = false; TimeOfDay? _selectedTime; @override void initState() { super.initState(); _selectedTime = widget.initialTime; if (widget.controller == null) { _controller = TextEditingController( text: _selectedTime != null ? '${_selectedTime!.hour.toString().padLeft(2, '0')}:${_selectedTime!.minute.toString().padLeft(2, '0')}' : '', ); _isControllerInternal = true; } else { _controller = widget.controller!; } } @override void dispose() { if (_isControllerInternal) { _controller.dispose(); } super.dispose(); } Future _selectTime(BuildContext context) async { if (!widget.enabled) return; final TimeOfDay? picked = await showTimePicker( context: context, initialTime: _selectedTime ?? TimeOfDay.now(), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: ColorScheme.light( primary: Theme.of(context).primaryColor, ), ), child: child!, ); }, ); if (picked != null && picked != _selectedTime) { setState(() { _selectedTime = picked; _controller.text = '${picked.hour.toString().padLeft(2, '0')}:${picked.minute.toString().padLeft(2, '0')}'; }); widget.onTimeSelected?.call(picked); } } @override Widget build(BuildContext context) { return TextFormField( controller: _controller, readOnly: true, enabled: widget.enabled, onTap: () => _selectTime(context), decoration: InputDecoration( labelText: widget.labelText ?? 'Thời gian', hintText: widget.hintText ?? 'HH:mm', prefixIcon: widget.prefixIcon ?? const FaIcon(FontAwesomeIcons.clock, size: 20), border: OutlineInputBorder( borderRadius: BorderRadius.circular(InputFieldSpecs.borderRadius), ), contentPadding: InputFieldSpecs.contentPadding, ), ); } }