import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; import '../../Notifiers/hrmProvider/attendanceListProvider.dart'; import '../../Utils/app_colors.dart'; import '../../Utils/dropdownTheme.dart'; class AddLiveAttendanceScreen extends StatefulWidget { const AddLiveAttendanceScreen({Key? key}) : super(key: key); @override State createState() => _AddLiveAttendanceScreenState(); } class _AddLiveAttendanceScreenState extends State { String? typeError; String? locationError; String? proofError; String? selectedType; Dropdowntheme ddtheme = Dropdowntheme(); final TextEditingController locationController = TextEditingController(); final TextEditingController descriptionController = TextEditingController(); final List types = ["Check In", "Check Out"]; final ImagePicker picker = ImagePicker(); XFile? proofFile; bool isSubmitting = false; String get locationHeading => selectedType == null ? "Location" : "$selectedType Location"; String get descriptionHeading => selectedType == null ? "Description" : "$selectedType Description"; String get proofButtonText => selectedType == null ? "Attach Proof" : "Attach $selectedType Proof"; bool get isSubmitEnabled => selectedType != null && locationController.text.trim().isNotEmpty && proofFile != null; @override void initState() { super.initState(); _autoFetchLocation(); } Future _autoFetchLocation() async { Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, ); // Save raw coordinates separately (for submission) final coords = "${position.latitude},${position.longitude}"; // Convert to address for display final placemarks = await placemarkFromCoordinates(position.latitude, position.longitude); String displayAddress; if (placemarks.isNotEmpty) { final place = placemarks.first; displayAddress = "${place.name}, ${place.locality}, ${place.administrativeArea}, ${place.country}"; } else { displayAddress = coords; // fallback } setState(() { locationController.text = displayAddress; // what user sees _rawCoordinates = coords; // keep coords hidden for backend }); } // Add this field at the top of your State class: String? _rawCoordinates; Future getCurrentLocation() async { try { LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { return "Location permission denied"; } } if (permission == LocationPermission.deniedForever) { return "Location permissions permanently denied"; } Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); List placemarks = await placemarkFromCoordinates(position.latitude, position.longitude); if (placemarks.isNotEmpty) { Placemark place = placemarks.first; return "${place.name}, ${place.locality}, ${place.administrativeArea}, ${place.country}"; } else { return "${position.latitude}, ${position.longitude}"; } } catch (e) { return "Error: $e"; } } void _showPicker(BuildContext context) { showModalBottomSheet( context: context, builder: (BuildContext bc) { return SafeArea( child: Wrap( children: [ ListTile( leading: const Icon(Icons.photo_camera), title: const Text('Camera'), onTap: () async { Navigator.of(context).pop(); final XFile? image = await picker.pickImage(source: ImageSource.camera); if (image != null) { setState(() => proofFile = image); } }, ), ListTile( leading: const Icon(Icons.photo_library), title: const Text('Gallery'), onTap: () async { Navigator.of(context).pop(); final XFile? image = await picker.pickImage(source: ImageSource.gallery); if (image != null) { setState(() => proofFile = image); } }, ), ], ), ); }, ); } void submitAttendance(BuildContext context) async { setState(() { typeError = null; locationError = null; proofError = null; }); bool hasError = false; if (selectedType == null) { typeError = "Please select a type"; hasError = true; } if (locationController.text.trim().isEmpty) { locationError = "Please enter a location"; hasError = true; } if (proofFile == null) { proofError = "Please attach proof"; hasError = true; } if (hasError) { setState(() {}); return; } setState(() => isSubmitting = true); final provider = Provider.of(context, listen: false); await provider.addAttendanceRequest( context, process: "Live", type: selectedType ?? "", loc: _rawCoordinates ?? "", // send actual coordinates checkDate: DateTime.now().toString().split(" ").first, checkInTime: selectedType == "Check In" ? TimeOfDay.now().format(context) : null, checkInLoc: selectedType == "Check In" ? locationController.text : null, checkInProof: selectedType == "Check In" ? File(proofFile!.path) : null, checkOutTime: selectedType == "Check Out" ? TimeOfDay.now().format(context) : null, checkOutLoc: selectedType == "Check Out" ? locationController.text : null, checkOutProof: selectedType == "Check Out" ? File(proofFile!.path) : null, note: descriptionController.text, ); setState(() { isSubmitting = false; selectedType = null; locationController.clear(); descriptionController.clear(); proofFile = null; }); _showSnack(context, "Attendance Submitted Successfully!"); _autoFetchLocation(); Navigator.pop(context, true); } void _showSnack(BuildContext context, String msg) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(msg), backgroundColor: Colors.black87), ); } @override Widget build(BuildContext context) { return SafeArea( top: false, child: Scaffold( backgroundColor: Colors.white, appBar: AppBar( automaticallyImplyLeading: false, backgroundColor: Colors.white, elevation: 0, title: Row( children: [ InkResponse( onTap: () => Navigator.pop(context, true), child: SvgPicture.asset( "assets/svg/appbar_back_button.svg", height: 25, ), ), const SizedBox(width: 10), Text( "Add Live Attendance", style: TextStyle( fontSize: 18, fontFamily: "JakartaMedium", fontWeight: FontWeight.w600, color: AppColors.semi_black, ), ), ], ), ), body: SingleChildScrollView( padding: const EdgeInsets.all(18), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Type Dropdown const Text("Type", style: TextStyle( fontSize: 15, fontFamily: "JakartaMedium", fontWeight: FontWeight.w500)), const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 2), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), ), child: DropdownButtonHideUnderline( child: DropdownButton2( isExpanded: true, hint: const Text( "Select Type", overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 15, fontFamily: "JakartaMedium", fontWeight: FontWeight.w400), ), value: selectedType, items: types .map((e) => DropdownMenuItem( value: e, child: Text( e, style: const TextStyle( fontSize: 14, fontFamily: "JakartaMedium"), overflow: TextOverflow.ellipsis, ), )) .toList(), onChanged: (val) => setState(() => selectedType = val), iconStyleData: ddtheme.iconStyleData, dropdownStyleData: ddtheme.dropdownStyleData, ), ), ), if (typeError != null) ...[ const SizedBox(height: 4), Text(typeError!, style: const TextStyle( color: Colors.red, fontSize: 13, fontFamily: "JakartaMedium")), ], const SizedBox(height: 16), /// Location Text(locationHeading, style: const TextStyle( fontSize: 15, fontFamily: "JakartaMedium", fontWeight: FontWeight.w500)), const SizedBox(height: 6), TextField( controller: locationController, decoration: _inputDecoration("Enter location"), ), if (locationError != null) ...[ const SizedBox(height: 4), Text(locationError!, style: const TextStyle( color: Colors.red, fontSize: 13, fontFamily: "JakartaMedium")), ], const SizedBox(height: 16), /// Description Text(descriptionHeading, style: const TextStyle( fontSize: 15, fontFamily: "JakartaMedium", fontWeight: FontWeight.w500)), const SizedBox(height: 6), TextField( controller: descriptionController, maxLines: 3, decoration: _inputDecoration("Write Description"), ), const SizedBox(height: 20), /// Attach Proof InkResponse( onTap: () => _showPicker(context), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( color: Colors.blue.shade50, border: Border.all(color: Colors.blue.shade200), borderRadius: BorderRadius.circular(14), ), child: Center( child: Text( proofButtonText, style: const TextStyle( fontSize: 16, color: Colors.blue, fontFamily: "JakartaMedium", fontWeight: FontWeight.w500, ), ), ), ), ), if (proofError != null) ...[ const SizedBox(height: 4), Text(proofError!, style: const TextStyle( color: Colors.red, fontSize: 13, fontFamily: "JakartaMedium")), ], if (proofFile != null) ...[ const SizedBox(height: 10), Row( children: [ const Icon(Icons.check_circle, color: Colors.green), const SizedBox(width: 8), Expanded( child: Text("Attached: ${proofFile!.name}", overflow: TextOverflow.ellipsis, style: const TextStyle( fontFamily: "JakartaMedium", fontSize: 14))), IconButton( icon: const Icon(Icons.close, color: Colors.red), onPressed: () => setState(() => proofFile = null), ), ], ) ], const SizedBox(height: 24), /// Submit Button InkResponse( onTap: isSubmitEnabled && !isSubmitting ? () => submitAttendance(context) : null, child: Container( height: 48, alignment: Alignment.center, decoration: BoxDecoration( color: isSubmitEnabled ? AppColors.app_blue : Colors.grey.shade400, borderRadius: BorderRadius.circular(12), ), child: isSubmitting ? const CircularProgressIndicator( color: Colors.white, strokeWidth: 2) : const Text( "Submit", style: TextStyle( fontSize: 16, fontFamily: "JakartaMedium", color: Colors.white, ), ), ), ), ], ), ), ), ); } InputDecoration _inputDecoration(String hint) { return InputDecoration( hintText: hint, hintStyle: const TextStyle( fontSize: 14, fontFamily: "JakartaMedium", fontWeight: FontWeight.w400, color: Colors.grey, ), filled: true, fillColor: Colors.grey.shade100, enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: AppColors.app_blue), ), ); } }