import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../Utils/app_colors.dart'; import '../../Utils/commonServices.dart'; import '../../Utils/commonWidgets.dart'; import '../../Utils/dropdownTheme.dart'; import '../../Notifiers/hrmProvider/leaveApplicationListProvider.dart'; class AddLeaveRequest extends StatefulWidget { final String pageTitleName; const AddLeaveRequest({super.key, required this.pageTitleName}); @override State createState() => _AddLeaveRequestState(); } class _AddLeaveRequestState extends State { Dropdowntheme ddtheme = Dropdowntheme(); List focusNodes = List.generate(6, (index) => FocusNode()); Map _source = {ConnectivityResult.mobile: true}; final MyConnectivity _connectivity = MyConnectivity.instance; String? leaveType; List leaveTypes = ["Normal", "Medical"]; // Validation error messages String? fromDateError; String? fromTimeError; String? toDateError; String? toTimeError; String? leaveTypeError; String? reasonError; @override void initState() { super.initState(); _connectivity.initialise(); _connectivity.myStream.listen((source) { setState(() => _source = source); }); // Add listener to reason controller to clear error when user starts typing final provider = Provider.of(context, listen: false); provider.reasonController.addListener(() { if (reasonError != null && provider.reasonController.text.isNotEmpty) { setState(() => reasonError = null); } }); } @override void dispose() { focusNodes.map((e) => e.dispose()); _connectivity.disposeStream(); super.dispose(); } Future _onBackPressed(BuildContext context) async { Navigator.pop(context, true); return true; } // Function to validate all fields bool validateForm(LeaveApplicationListProvider provider) { String? newFromDateError = provider.fromDateField.text.isEmpty ? "From date is required" : null; String? newFromTimeError = provider.fromTimeField.text.isEmpty ? "From time is required" : null; String? newToDateError = provider.toDateField.text.isEmpty ? "To date is required" : null; String? newToTimeError = provider.toTimeField.text.isEmpty ? "To time is required" : null; String? newLeaveTypeError = leaveType == null ? "Leave type is required" : null; String? newReasonError = provider.reasonController.text.isEmpty ? "Reason is required" : null; // Only update if there are actual changes to avoid unnecessary rebuilds if (fromDateError != newFromDateError || fromTimeError != newFromTimeError || toDateError != newToDateError || toTimeError != newToTimeError || leaveTypeError != newLeaveTypeError || reasonError != newReasonError) { setState(() { fromDateError = newFromDateError; fromTimeError = newFromTimeError; toDateError = newToDateError; toTimeError = newToTimeError; leaveTypeError = newLeaveTypeError; reasonError = newReasonError; }); } return newFromDateError == null && newFromTimeError == null && newToDateError == null && newToTimeError == null && newLeaveTypeError == null && newReasonError == null; } @override Widget build(BuildContext context) { final leaveProvider = Provider.of(context); switch (_source.keys.toList()[0]) { case ConnectivityResult.mobile: case ConnectivityResult.wifi: connection = 'Online'; break; case ConnectivityResult.none: default: connection = 'Offline'; } return (connection == "Online") ? Platform.isAndroid ? WillPopScope( onWillPop: () => _onBackPressed(context), child: SafeArea( top: false, bottom: true, child: _scaffold(context, leaveProvider)), ) : _scaffold(context, leaveProvider) : NoNetwork(context); } Widget _scaffold(BuildContext context, LeaveApplicationListProvider provider) { return SafeArea( top: false, child: Scaffold( resizeToAvoidBottomInset: true, backgroundColor: AppColors.scaffold_bg_color, appBar: appbarNew(context, widget.pageTitleName, 0xFFFFFFFF), body: SingleChildScrollView( child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), margin: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.white, borderRadius: BorderRadius.circular(20), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// From Date TextWidget(context, "From Date"), GestureDetector( onTap: () { provider.showDatePickerDialog(context, isFromDate: true); if (fromDateError != null) { setState(() => fromDateError = null); } }, child: textFieldNew(context, provider.fromDateField, "Select Date", enabled: false), ), errorWidget(context, fromDateError), /// From Time TextWidget(context, "From Time"), GestureDetector( onTap: () async { TimeOfDay? picked = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (picked != null) { provider.fromTimeField.text = picked.format(context); if (fromTimeError != null) { setState(() => fromTimeError = null); } } }, child: textFieldNew(context, provider.fromTimeField, "Select Time", enabled: false), ), errorWidget(context, fromTimeError), /// To Date TextWidget(context, "To Date"), GestureDetector( onTap: () { provider.showDatePickerDialog(context, isFromDate: false); if (toDateError != null) { setState(() => toDateError = null); } }, child: textFieldNew(context, provider.toDateField, "Select Date", enabled: false), ), errorWidget(context, toDateError), /// To Time TextWidget(context, "To Time"), GestureDetector( onTap: () async { TimeOfDay? picked = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (picked != null) { provider.toTimeField.text = picked.format(context); if (toTimeError != null) { setState(() => toTimeError = null); } } }, child: textFieldNew(context, provider.toTimeField, "Select Time", enabled: false), ), errorWidget(context, toTimeError), /// Leave Type TextWidget(context, "Leave Type"), Container( margin: const EdgeInsets.only(bottom: 6), padding: const EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( color: AppColors.text_field_color, borderRadius: BorderRadius.circular(14), ), child: DropdownButtonHideUnderline( child: DropdownButton( isExpanded: true, hint: const Text("Select Leave Type", style: TextStyle(fontSize: 14, color: Colors.grey)), value: leaveType, items: leaveTypes .map((e) => DropdownMenuItem(value: e, child: Text(e))) .toList(), onChanged: (val) { setState(() { leaveType = val; if (leaveTypeError != null) { leaveTypeError = null; } }); }, ), ), ), errorWidget(context, leaveTypeError), /// Reason TextWidget(context, "Reason"), textFieldNew(context, provider.reasonController, "Enter Reason", maxLines: 2), errorWidget(context, reasonError), const SizedBox(height: 70), ], ), ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, bottomNavigationBar: InkResponse( onTap: () { if (validateForm(provider)) { provider.addLeaveRequest( context, fromDate: provider.fromDateField.text, fromTime: provider.fromTimeField.text, toDate: provider.toDateField.text, toTime: provider.toTimeField.text, leaveType: leaveType!, reason: provider.reasonController.text, ); // Reset after submit setState(() { provider.fromDateField.clear(); provider.fromTimeField.clear(); provider.toDateField.clear(); provider.toTimeField.clear(); provider.reasonController.clear(); leaveType = null; fromDateError = null; fromTimeError = null; toDateError = null; toTimeError = null; leaveTypeError = null; reasonError = null; }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Leave request submitted successfully!"), backgroundColor: Colors.black87, ), ); Navigator.pop(context, true); } }, child: Container( height: 45, alignment: Alignment.center, margin: const EdgeInsets.symmetric(horizontal: 18, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: AppColors.app_blue, borderRadius: BorderRadius.circular(15), ), child: provider.isSubmitting ? const CircularProgressIndicator(color: Colors.white) : const Text( "Submit", style: TextStyle( fontSize: 15, fontFamily: "JakartaMedium", color: Colors.white), ), ), ), ), ); } Widget textFieldNew( BuildContext context, TextEditingController controller, String hint, { bool enabled = true, int maxLines = 1, }) { return Container( margin: const EdgeInsets.only(bottom: 6), decoration: BoxDecoration( color: AppColors.text_field_color, borderRadius: BorderRadius.circular(14), ), child: TextFormField( controller: controller, enabled: enabled, maxLines: maxLines, style: TextStyle( color: Colors.black, // Entered text color fontSize: 14, // Optional: adjust font size ), decoration: InputDecoration( hintText: hint, hintStyle: TextStyle( color: Colors.grey.shade500, // Customize this color fontSize: 14, // Optional: tweak font size ), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), ), ); } Widget errorText(String msg) { return Padding( padding: const EdgeInsets.only(bottom: 12, left: 4), child: Text( msg, style: const TextStyle(color: Colors.red, fontSize: 13), ), ); } bool _validateForm(LeaveApplicationListProvider provider) { bool isValid = true; setState(() { fromDateError = provider.fromDateField.text.isEmpty ? "From date is required" : null; fromTimeError = provider.fromTimeField.text.isEmpty ? "From time is required" : null; toDateError = provider.toDateField.text.isEmpty ? "To date is required" : null; toTimeError = provider.toTimeField.text.isEmpty ? "To time is required" : null; leaveTypeError = leaveType == null ? "Please select leave type" : null; reasonError = provider.reasonController.text.isEmpty ? "Reason is required" : null; if (fromDateError != null || fromTimeError != null || toDateError != null || toTimeError != null || leaveTypeError != null || reasonError != null) { isValid = false; } }); return isValid; } }