From 1bbd306ca0cd76132947c64b32fefd1136727d8d Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Fri, 13 Mar 2026 20:43:51 -0400 Subject: [PATCH] feat: Update geofence handling to allow checkout when checked in and show verified banner for overridden geofence --- .../presentation/widgets/clock_in_action_section.dart | 9 +++++---- .../geofence_override_modal.dart | 11 ++++++++--- .../geofence_status_banner.dart | 6 ++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_action_section.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_action_section.dart index 758dbdea..279f6357 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_action_section.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/clock_in_action_section.dart @@ -119,9 +119,10 @@ class ClockInActionSection extends StatelessWidget { final bool hasCoordinates = selectedShift?.latitude != null && selectedShift?.longitude != null; - // Disable swipe when the shift has coordinates and the user is - // not verified, not timed out, and has not overridden the geofence. - final bool isGeofenceBlocking = hasCoordinates && + // Geofence only gates clock-in, never clock-out. When already + // checked in the swipe must always be enabled for checkout. + final bool isGeofenceBlocking = !isCheckedIn && + hasCoordinates && !geofenceState.isLocationVerified && !geofenceState.isLocationTimedOut && !geofenceState.isGeofenceOverridden; @@ -135,7 +136,7 @@ class ClockInActionSection extends StatelessWidget { SwipeToCheckIn( isCheckedIn: isCheckedIn, mode: checkInMode, - isDisabled: isCheckedIn || isGeofenceBlocking, + isDisabled: isGeofenceBlocking, isLoading: isActionInProgress, onCheckIn: () => _handleCheckIn(context), onCheckOut: () => _handleCheckOut(context), diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_override_modal.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_override_modal.dart index 43778d2d..ea7a2d1c 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_override_modal.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_override_modal.dart @@ -20,8 +20,12 @@ class GeofenceOverrideModal extends StatefulWidget { /// Shows the override modal as a bottom sheet. /// - /// Requires [ClockInBloc] to be available in [context]. + /// Requires [GeofenceBloc] to be available in [context]. static void show(BuildContext context) { + // Capture the bloc before opening the sheet so we don't access a + // deactivated widget's ancestor inside the builder. + final GeofenceBloc bloc = ReadContext(context).read(); + showModalBottomSheet( context: context, isScrollControlled: true, @@ -31,7 +35,7 @@ class GeofenceOverrideModal extends StatefulWidget { ), ), builder: (_) => BlocProvider.value( - value: ReadContext(context).read(), + value: bloc, child: const GeofenceOverrideModal(), ), ); @@ -111,6 +115,7 @@ class _GeofenceOverrideModalState extends State { GeofenceOverrideApproved(notes: justification), ); - Modular.to.popSafe(); + Navigator.of(context).pop(); + //Modular.to.popSafe(); } } diff --git a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_status_banner.dart b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_status_banner.dart index 2a4e4993..ad9e9700 100644 --- a/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_status_banner.dart +++ b/apps/mobile/packages/features/staff/clock_in/lib/src/presentation/widgets/geofence_status_banner/geofence_status_banner.dart @@ -35,6 +35,12 @@ class GeofenceStatusBanner extends StatelessWidget { /// Determines which banner variant to display based on the current state. Widget _buildBannerForState(GeofenceState state) { + // If the worker overrode the geofence check, show the verified banner + // instead of any failure state — the justification has been recorded. + if (state.isGeofenceOverridden) { + return const VerifiedBanner(); + } + // 1. Location services disabled. if (state.permissionStatus == LocationPermissionStatus.serviceDisabled || (state.isLocationTimedOut && !state.isLocationServiceEnabled)) {