import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:generp/screens/notifierExports.dart'; import 'package:generp/Utils/app_colors.dart'; import 'package:generp/Utils/commonWidgets.dart'; import 'package:generp/screens/CheckInScreen.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import '../Utils/commonServices.dart'; class AttendanceScreen extends StatefulWidget { const AttendanceScreen({super.key}); @override State createState() => _AttendanceScreenState(); } class _AttendanceScreenState extends State { // var homeProvider; Map _source = {ConnectivityResult.mobile: true}; final MyConnectivity _connectivity = MyConnectivity.instance; @override void initState() { super.initState(); _connectivity.initialise(); _connectivity.myStream.listen((source) { setState(() => _source = source); }); final homeProvider = Provider.of( context, listen: false, ); WidgetsBinding.instance.addPostFrameCallback((_) { final attProvider = Provider.of( context, listen: false, ); attProvider.getAttendanceList(context); attProvider.init(context); }); } @override void dispose() { // TODO: implement dispose super.dispose(); _connectivity.disposeStream(); } // Responsive text size function double getResponsiveTextSize(BuildContext context, double baseSize) { final double scale = MediaQuery.of(context).textScaleFactor; final double width = MediaQuery.of(context).size.width; if (width < 360) { // Small phones return baseSize * 0.9; } else if (width < 400) { // Medium phones return baseSize; } else { // Large phones return baseSize * 1.1; } } // Responsive padding function double getResponsivePadding(BuildContext context) { final double width = MediaQuery.of(context).size.width; return width * 0.04; // 4% of screen width } // Responsive height function double getResponsiveHeight(BuildContext context, double baseHeight) { final double height = MediaQuery.of(context).size.height; if (height < 700) { // Small height devices return baseHeight * 0.8; } else if (height < 800) { // Medium height devices return baseHeight; } else { // Large height devices return baseHeight * 1.1; } } @override Widget build(BuildContext context) { switch (_source.keys.toList()[0]) { case ConnectivityResult.mobile: connection = 'Online'; break; 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), ), ) : _scaffold(context) : NoNetwork(context); } Widget _scaffold(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { final bool isSmallScreen = constraints.maxWidth < 360; final bool isLargeScreen = constraints.maxWidth > 400; return Consumer( builder: (context, attendance, index) { final timeString = attendance.attendanceHistory.firstOrNull?['check_in_time'] .toString() ?? ""; final match = RegExp( r'^(\d{1,2}:\d{2})(am|pm)$', caseSensitive: false, ).firstMatch(timeString.replaceAll(' ', '').toLowerCase()); final timeString2 = attendance.attendanceHistory.firstOrNull?['check_out_time'] .toString() ?? ""; final match2 = RegExp( r'^(\d{1,2}:\d{2})(am|pm)$', caseSensitive: false, ).firstMatch(timeString2.replaceAll(' ', '').toLowerCase()); String formattedTime = match?.group(1) ?? "-"; String period = match?.group(2)?.toUpperCase() ?? ""; String formattedTime2 = match2?.group(1) ?? "-"; String period2 = match2?.group(2)?.toUpperCase() ?? ""; final dateArrayList = attendance.dateArrayList; final penalityArrayList = attendance.penalityArrayList; var selectedIndex = attendance.selectedIndex; // Calculate the custom period: 26th of previous month to 25th of current month final startDate = DateTime( attendance.month.year, attendance.month.month - 1, 26, ); final endDate = DateTime( attendance.month.year, attendance.month.month, 25, ); final daysInPeriod = endDate.difference(startDate).inDays + 1; final startingIndex = startDate.weekday % 7; // 0 for Sunday, 1 for Monday, etc. final currentDate = DateTime.now(); // Format the month label (e.g., "Mar - Apr 2025") final prevMonthName = DateFormat( 'MMM', ).format(DateTime(attendance.month.year, attendance.month.month - 1)); final currentMonthName = DateFormat('MMM').format(attendance.month); final year = DateFormat('yyyy').format(attendance.month); final monthLabel = '$currentMonthName $year'; return RefreshIndicator.adaptive( color: AppColors.app_blue, onRefresh: () async { await Future.delayed(const Duration(milliseconds: 600)); attendance.getAttendanceList(context); attendance.init(context); attendance.loadAttendanceDetails(context); }, child: Scaffold( resizeToAvoidBottomInset: true, backgroundColor: AppColors.scaffold_bg_color, appBar: appbarNew(context, "Attendance", 0xFFFFFFFF), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical( bottom: Radius.circular(20), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.only( left: getResponsivePadding(context), right: getResponsivePadding(context), top: 15, ), child: Row( children: List.generate(2, (j) { final heads = ["Check-in", "Check-out"]; return Expanded( child: Text( heads[j], style: TextStyle( color: AppColors.grey_thick, fontSize: getResponsiveTextSize(context, 14), fontFamily: "JakartaMedium", ), ), ); }), ), ), Container( padding: EdgeInsets.symmetric(horizontal: getResponsivePadding(context)-6), child: Row( children: List.generate(2, (iii) { final times = [formattedTime, formattedTime2]; final periods = [period, period2]; final locations = [ attendance.attendanceHistory.isNotEmpty ? attendance .attendanceHistory .first['check_in_location'] .toString() ?? "-" : "-", attendance.attendanceHistory.isNotEmpty ? attendance .attendanceHistory .first['check_out_location'] .toString() ?? "-" : "-", ]; return Expanded( child: Container( padding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 8 : 10, vertical: isSmallScreen ? 11 : 16, ), margin: EdgeInsets.symmetric( horizontal: isSmallScreen ? 3 : 5, vertical: isSmallScreen ? 3 : 5, ), decoration: BoxDecoration( color: Color(0xFFE6F6FF), borderRadius: BorderRadius.circular(20), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( children: [ TextSpan( text: times[iii], style: TextStyle( color: times[iii] != "-" ? Color(0xFF1286C7) : Color(0xFF1286C7), fontFamily: "JakartaRegular", fontSize: getResponsiveTextSize(context, 20), ), ), TextSpan( text: periods[iii], style: TextStyle( color: periods[iii] != "-" ? Color(0xFF1286C7) : Color(0xFFED3424), fontFamily: "JakartaRegular", fontSize: getResponsiveTextSize(context, 14), ), ), ], ), ), SizedBox(height: isSmallScreen ? 12 : 16), Text( locations[iii], style: TextStyle( color: AppColors.semi_black, fontSize: getResponsiveTextSize(context, 12), ), ), ], ), ), ); }), ), ), Container( padding: EdgeInsets.only( left: getResponsivePadding(context), top: 10 ), child: Text( "Attendance Details", style: TextStyle( fontFamily: "JakartaMedium", fontSize: getResponsiveTextSize(context, 14), color: Color(0xFF818181), ), ), ), Container( padding: EdgeInsets.symmetric( horizontal: getResponsivePadding(context), vertical: 10, ), child: GridView.builder( itemCount: 4, shrinkWrap: true, physics: NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: isSmallScreen ? 8 : 10, mainAxisSpacing: isSmallScreen ? 8 : 10, childAspectRatio: isSmallScreen ? 18/9 : 20/10, ), itemBuilder: (context, index) { final numbers = [ attendance.presentDays, attendance.absentDays, attendance.holidays, attendance.latePenalties, ]; final names = [ "Present \nDays", "Absent \nDays", "Holidays", "Late \nPoints", ]; final colors = [ 0xFFE7FFE5, 0xFFFFEFEF, 0xFFF3EDFF, 0xFFFFF6F0, ]; final textcolors = [ 0xFF0D9C00, 0xFFFF0000, 0xFF493272, 0xFF91481B, ]; final assetNames = [ "assets/svg/attendance/present_ic.svg", "assets/svg/attendance/absent_ic.svg", "assets/svg/attendance/holidays_ic.svg", "assets/svg/attendance/penalty_ic.svg", ]; return Container( padding: EdgeInsets.symmetric( horizontal: isSmallScreen ? 10 : 13 ), decoration: BoxDecoration( color: Color(colors[index]), borderRadius: BorderRadius.circular(20), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( numbers[index].toString(), style: TextStyle( fontSize: getResponsiveTextSize(context, 20), fontFamily: "JakartaMedium", color: Color(textcolors[index]), ), ), Row( children: [ Expanded( flex: 4, child: Text( names[index], style: TextStyle( fontSize: getResponsiveTextSize(context, 14), fontFamily: "JakartaRegular", color: AppColors.semi_black, ), ), ), Expanded( flex: 1, child: SvgPicture.asset( assetNames[index], width: isSmallScreen ? 20 : 38, height: isSmallScreen ? 20 : 38, ), ), ], ), ], ), ); }, ), ), ], ), ), SizedBox(height: 15), ///calendar Container( margin: EdgeInsets.symmetric( horizontal: getResponsivePadding(context), vertical: 10 ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), color: Colors.white, ), child: Column( children: [ Padding( padding: EdgeInsets.fromLTRB( isSmallScreen ? 20 : 30, 10, isSmallScreen ? 20 : 30, 0 ), child: Container( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( child: Row( children: [ GestureDetector( onTap: () { attendance.setPreviousMonth(context); }, child: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset( "assets/svg/crm/calendar_left.svg", width: isSmallScreen ? 18 : 24, height: isSmallScreen ? 18 : 24, ), ), ), Padding( padding: const EdgeInsets.symmetric( horizontal: 5.0, ), child: Text( monthLabel, style: TextStyle( overflow: TextOverflow.ellipsis, fontFamily: "JakartaMedium", fontSize: getResponsiveTextSize(context, 14.5), color: Color(0xFF2D2D2D), ), ), ), GestureDetector( onTap: () { attendance.setNextMonth(context); }, child: Padding( padding: const EdgeInsets.all(4.0), child: SvgPicture.asset( "assets/svg/crm/calendar_right.svg", width: isSmallScreen ? 18 : 24, height: isSmallScreen ? 18 : 24, ), ), ), ], ), ), InkResponse( onTap: () { _showInfoBottomSheet(context); }, child: SizedBox( width: isSmallScreen ? 18 : 20, height: isSmallScreen ? 18 : 20, child: SvgPicture.asset( "assets/svg/ic_info_new.svg", width: isSmallScreen ? 18 : 20, height: isSmallScreen ? 18 : 20, ), ), ), ], ), ), ), SizedBox(height: 5), Padding( padding: const EdgeInsets.fromLTRB(4, 10, 2, 0), child: Container( child: Row( children: [ for ( var i = 0; i < [ 'S', 'M', 'T', 'W', 'T', 'F', 'S', ].length; i++ ) Expanded( child: Text( ['S', 'M', 'T', 'W', 'T', 'F', 'S'][i], textAlign: TextAlign.center, style: TextStyle( fontSize: getResponsiveTextSize(context, 15), overflow: TextOverflow.ellipsis, color: i == 0 ? Color(0xFFFF0000) : AppColors.semi_black, ), ), ), ], ), ), ), SizedBox(height: 3,), Padding( padding: const EdgeInsets.fromLTRB(4, 0, 2, 10), child: Container( child: GridView.builder( itemCount: daysInPeriod + startingIndex, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, crossAxisSpacing: 2, mainAxisSpacing: 1, childAspectRatio: isSmallScreen ? 1.0 : (255 / 245), ), padding: const EdgeInsets.fromLTRB(0, 0, 0, 0), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemBuilder: (context, index) { if (index < startingIndex) { return SizedBox.shrink(); } final dayIndex = index - startingIndex; final currentDateInPeriod = startDate.add( Duration(days: dayIndex), ); final currentDay = currentDateInPeriod.day; final isFutureDate = currentDateInPeriod .isAfter(currentDate); // Find matching date in dateArrayList Map? dateMap; try { dateMap = dateArrayList[dayIndex]; } catch (e) { dateMap = {}; } // Find matching penalty Map? penaltyMap; try { penaltyMap = penalityArrayList[dayIndex]; } catch (e) { penaltyMap = {}; } String? dateColor = dateMap.isNotEmpty ? dateMap.values.first : null; String? penaltyKey = penaltyMap.isNotEmpty ? penaltyMap.keys.first : null; int? datePenalty = penaltyMap.isNotEmpty ? penaltyMap.values.first : 0; // Determine if this is the current day final isCurrentDay = currentDateInPeriod.day == currentDate.day && currentDateInPeriod.month == currentDate.month && currentDateInPeriod.year == currentDate.year; return InkWell( onTap: isFutureDate ? null : () { selectedIndex = index; if (penaltyKey != null) { attendance.dateWiseAttendance( penaltyKey, context, ); } attendance.selectedDate = currentDay.toString(); _showAttDetailsBottomSheet(context); }, child: Card( elevation: 0, child: Column( children: [ Center( child: Container( padding : EdgeInsets.all(2), decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( color: (isCurrentDay || (selectedIndex == index)) ? Color(0xFF1487C9) : Colors.transparent, ), color: isFutureDate ? Colors.transparent : (isCurrentDay || (selectedIndex == index)) ? Color(0xFFFFFFFF) : dateColor == 'g' ? Color( 0xFFE8FFE6, ) : dateColor == 'r' ? Color( 0xFFFF0000, ).withAlpha(50) : dateColor == 'b' ? Color( 0xFF493272, ).withAlpha(50) : dateColor == 'br' ? Color(0xFFFFE8D0) : dateColor == 'y' ? Color(0xFFFFF9B2) : Colors.transparent, ), child: Center( child: Text( currentDay.toString(), style: TextStyle( fontSize: getResponsiveTextSize(context, 13), fontWeight: FontWeight.w400, color: isFutureDate ? AppColors.semi_black : (isCurrentDay || (selectedIndex == index)) ? Color(0xFF2D2D2D) : dateColor == 'g' ? Color(0xFF0D9C00) : dateColor == 'r' ? Color(0xFFFF0000) : dateColor == 'b' ? Color(0xFF493272) : dateColor == 'br' ? Color(0xFF6B3A02) : dateColor == 'y' ? Color(0xFF605C00) : Colors.transparent, ), ), ), ), ), ], ), ), ); }, ), ), ), ], ), ), SizedBox(height: 25), ], ), ), bottomNavigationBar: attendance.attendanceStatus == 0 || attendance.attendanceStatus == 1 ? Container( height: getResponsiveHeight(context, 75), decoration: BoxDecoration(color: Colors.white), padding: EdgeInsets.symmetric(vertical: 10), child: Align( alignment: Alignment.center, child: InkWell( onTap: () async { var res = await Navigator.push( context, MaterialPageRoute( builder: (context) => CheckInOutScreen( getAttendanceStatus: attendance.attendanceStatus, ), ), ); if (res == true) { attendance.getAttendanceList(context); attendance.init(context); attendance.loadAttendanceDetails(context); } var f = FocusScope.of(context); if (!f.hasPrimaryFocus) { f.unfocus(); } }, child: Container( alignment: Alignment.bottomCenter, height: 45, margin: EdgeInsets.symmetric( horizontal: getResponsivePadding(context) ), width: MediaQuery.of(context).size.width, decoration: BoxDecoration( color: AppColors.app_blue, borderRadius: BorderRadius.circular(15.0), ), child: Center( child: Text( attendance.attendanceStatus == 0 ? "Check In" : "Check Out", textAlign: TextAlign.center, style: TextStyle( fontSize: getResponsiveTextSize(context, 16), color: Colors.white, fontFamily: "JakartaMedium", ), ), ), ), ), ), ) : SizedBox(height: 0), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ), ); }, ); }, ); } Future _showInfoBottomSheet(BuildContext context) { return showModalBottomSheet( useSafeArea: true, isDismissible: true, isScrollControlled: true, showDragHandle: true, enableDrag: true, context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { return SafeArea( child: Container( margin: EdgeInsets.only( bottom: 15, left: 15, right: 15, top: 15, ), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Indicators Info", style: TextStyle( color: AppColors.app_blue, fontFamily: "JakartaSemiBold", fontSize: getResponsiveTextSize(context, 16), ), ), SizedBox(height: 15), GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 0, mainAxisExtent: 40, mainAxisSpacing: 10, childAspectRatio: 6 / 1, ), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: 5, itemBuilder: (context, index) { final colors = [ Color(0xFF493272).withAlpha(50), Color(0xFFE7FFE5), Color(0xFFFF0000).withAlpha(50), Color(0xFFFFE8D0), Color(0xFFFFF9B2), ]; final textColors = [ Color(0xFF493272), Color(0xFF0D9C00), Color(0xFFFF0000), Color(0xFF6B3A02), Color(0xFF605C00), ]; final textSubs = ["24", "7", "13", "17", "2"]; final text = [ "Holiday", "Present", "Absent", "Half Day", "Not Checked Out", ]; return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 1, child: Container( width: 25, height: 25, decoration: BoxDecoration( shape: BoxShape.circle, color: colors[index], ), child: Center( child: Text( textSubs[index], style: TextStyle( color: textColors[index], fontSize: getResponsiveTextSize(context, 12), ), ), ), ), ), SizedBox(width: 5), Expanded( flex: 4, child: Text( text[index], maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: getResponsiveTextSize(context, 14), color: Colors.black, ), ), ), ], ); }, ), ], ), ], ), ), ), ); }, ); }, ); } Future _showAttDetailsBottomSheet(BuildContext context) { return showModalBottomSheet( useSafeArea: true, isDismissible: true, isScrollControlled: true, showDragHandle: true, enableDrag: true, context: context, builder: (context) { return StatefulBuilder( builder: (context, setState) { return Consumer( builder: (context, provider, child) { return SafeArea( child: Container( margin: EdgeInsets.only( bottom: 15, left: 15, right: 15, top: 15, ), padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Attendance Info", style: TextStyle( color: AppColors.app_blue, fontFamily: "JakartaSemiBold", fontSize: getResponsiveTextSize(context, 16), ), ), Text( provider.date, style: TextStyle( color: Color(0xFF818181), fontSize: getResponsiveTextSize(context, 12), ), ), ], ), Spacer(), Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 10, ), decoration: BoxDecoration( color: Color(0xFFFFF6F0), borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Text( "Late Points ", style: TextStyle( fontFamily: "JakartaMedium", fontSize: getResponsiveTextSize(context, 14), ), ), Text( provider.penalties, style: TextStyle( color: Color(0xFF91481B), fontSize: getResponsiveTextSize(context, 14), ), ), ], ), ), ], ), Padding( padding: EdgeInsets.symmetric(horizontal: 5, vertical: 15), child: Divider( color: Color(0xFFD7D7D7), thickness: 0.5, ), ), ...List.generate(4, (j) { final values = [ "Check-in Time", "Check-in Location", "Check-out Time", "Check-out Location", ]; final subvalues = [ (provider.intime), (provider.inlocation), (provider.outtime), (provider.outlocation), ]; return Container( margin: EdgeInsets.only(bottom: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 2, child: Text( values[j], style: TextStyle( fontFamily: "JakartaMedium", color: AppColors.semi_black, fontSize: getResponsiveTextSize(context, 14), ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), SizedBox(width: 10), Expanded( flex: 3, child: Text( subvalues[j].isEmpty ? "-" : subvalues[j], style: TextStyle( color: Color(0xFF818181), fontSize: getResponsiveTextSize(context, 14), ), maxLines: 3, overflow: TextOverflow.ellipsis, ), ), ], ), ); }), SizedBox(height: 10), ], ), ), ), ); }, ); }, ); }, ); } }