feat(geofence): add OutsideWorkAreaBanner for non-blocking warning when clocked in outside geofence

This commit is contained in:
Achintha Isuru
2026-03-18 18:02:17 -04:00
parent baf426935e
commit f488577e6b
5 changed files with 52 additions and 6 deletions

View File

@@ -982,7 +982,8 @@
"override_hint": "Enter your justification...",
"override_submit": "Clock In",
"overridden_title": "Location Not Verified",
"overridden_desc": "You are clocking in without location verification. Your justification has been recorded."
"overridden_desc": "You are clocking in without location verification. Your justification has been recorded.",
"outside_work_area_warning": "You've moved away from the work area"
}
},
"availability": {

View File

@@ -977,7 +977,8 @@
"override_hint": "Ingrese su justificación...",
"override_submit": "Registrar Entrada",
"overridden_title": "Ubicación No Verificada",
"overridden_desc": "Está registrando entrada sin verificación de ubicación. Su justificación ha sido registrada."
"overridden_desc": "Está registrando entrada sin verificación de ubicación. Su justificación ha sido registrada.",
"outside_work_area_warning": "Te has alejado del área de trabajo"
}
},
"availability": {

View File

@@ -107,7 +107,7 @@ class ClockInActionSection extends StatelessWidget {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const GeofenceStatusBanner(),
GeofenceStatusBanner(isClockedIn: isCheckedIn),
const SizedBox(height: UiConstants.space3),
EarlyCheckInBanner(
availabilityTime: checkInAvailabilityTime ?? soonLabel,
@@ -120,7 +120,7 @@ class ClockInActionSection extends StatelessWidget {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const GeofenceStatusBanner(),
GeofenceStatusBanner(isClockedIn: isCheckedIn),
const SizedBox(height: UiConstants.space3),
EarlyCheckOutBanner(
availabilityTime: checkOutAvailabilityTime ?? soonLabel,
@@ -146,7 +146,7 @@ class ClockInActionSection extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
spacing: UiConstants.space4,
children: <Widget>[
const GeofenceStatusBanner(),
GeofenceStatusBanner(isClockedIn: isCheckedIn),
_currentInteraction.buildActionWidget(
isCheckedIn: isCheckedIn,
isDisabled: isGeofenceBlocking,

View File

@@ -4,6 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
import '../../bloc/geofence/geofence_bloc.dart';
import '../../bloc/geofence/geofence_state.dart';
import 'outside_work_area_banner.dart';
import 'permission_denied_banner.dart';
import 'permission_denied_forever_banner.dart';
import 'service_disabled_banner.dart';
@@ -17,9 +18,18 @@ import 'verifying_banner.dart';
///
/// Reads [GeofenceBloc] state directly and renders the appropriate
/// banner variant based on permission, location, and verification conditions.
/// When [isClockedIn] is true and the worker is too far, a non-blocking
/// informational banner is shown instead of the override flow.
class GeofenceStatusBanner extends StatelessWidget {
/// Creates a [GeofenceStatusBanner].
const GeofenceStatusBanner({super.key});
const GeofenceStatusBanner({this.isClockedIn = false, super.key});
/// Whether the worker is currently clocked in.
///
/// When true and the device is outside the geofence, a lightweight
/// [OutsideWorkAreaBanner] is shown instead of [TooFarBanner] so that
/// the clock-out slider remains accessible.
final bool isClockedIn;
@override
Widget build(BuildContext context) {
@@ -77,6 +87,12 @@ class GeofenceStatusBanner extends StatelessWidget {
if (!state.isLocationVerified &&
!state.isLocationTimedOut &&
state.distanceFromTarget != null) {
// When already clocked in, show a non-blocking informational banner
// instead of the "Clock in anyway" override flow so the clock-out
// slider remains accessible.
if (isClockedIn) {
return const OutsideWorkAreaBanner();
}
return TooFarBanner(distanceMeters: state.distanceFromTarget!);
}

View File

@@ -0,0 +1,28 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart';
import 'package:flutter/material.dart';
/// Non-blocking warning banner shown when the worker is already clocked in
/// but has moved outside the geofence radius.
///
/// Unlike [TooFarBanner], this banner does not include a "Clock in anyway"
/// action button and does not block the clock-out slider.
class OutsideWorkAreaBanner extends StatelessWidget {
/// Creates an [OutsideWorkAreaBanner].
const OutsideWorkAreaBanner({super.key});
@override
Widget build(BuildContext context) {
final TranslationsStaffClockInGeofenceEn i18n = Translations.of(
context,
).staff.clock_in.geofence;
return UiNoticeBanner(
backgroundColor: UiColors.tagPending,
icon: UiIcons.warning,
iconColor: UiColors.textWarning,
title: i18n.outside_work_area_warning,
titleColor: UiColors.textWarning,
);
}
}