Files
worker/lib/shared/widgets/date_picker_field.dart
Phuoc Nguyen b5f90c364d update icon
2025-11-14 18:02:37 +07:00

377 lines
10 KiB
Dart

/// 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<DateTime>? onDateSelected;
final FormFieldValidator<String>? 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<DatePickerField> createState() => _DatePickerFieldState();
}
class _DatePickerFieldState extends State<DatePickerField> {
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<void> _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<DateTimeRange>? 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<DateRangePickerField> createState() => _DateRangePickerFieldState();
}
class _DateRangePickerFieldState extends State<DateRangePickerField> {
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<void> _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<DateTime>? onDateSelected;
final FormFieldValidator<String>? 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<TimeOfDay>? 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<TimePickerField> createState() => _TimePickerFieldState();
}
class _TimePickerFieldState extends State<TimePickerField> {
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<void> _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,
),
);
}
}