feat(geofence): add OutsideWorkAreaBanner for non-blocking warning when clocked in outside geofence
This commit is contained in:
@@ -982,7 +982,8 @@
|
|||||||
"override_hint": "Enter your justification...",
|
"override_hint": "Enter your justification...",
|
||||||
"override_submit": "Clock In",
|
"override_submit": "Clock In",
|
||||||
"overridden_title": "Location Not Verified",
|
"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": {
|
"availability": {
|
||||||
|
|||||||
@@ -977,7 +977,8 @@
|
|||||||
"override_hint": "Ingrese su justificación...",
|
"override_hint": "Ingrese su justificación...",
|
||||||
"override_submit": "Registrar Entrada",
|
"override_submit": "Registrar Entrada",
|
||||||
"overridden_title": "Ubicación No Verificada",
|
"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": {
|
"availability": {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class ClockInActionSection extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const GeofenceStatusBanner(),
|
GeofenceStatusBanner(isClockedIn: isCheckedIn),
|
||||||
const SizedBox(height: UiConstants.space3),
|
const SizedBox(height: UiConstants.space3),
|
||||||
EarlyCheckInBanner(
|
EarlyCheckInBanner(
|
||||||
availabilityTime: checkInAvailabilityTime ?? soonLabel,
|
availabilityTime: checkInAvailabilityTime ?? soonLabel,
|
||||||
@@ -120,7 +120,7 @@ class ClockInActionSection extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const GeofenceStatusBanner(),
|
GeofenceStatusBanner(isClockedIn: isCheckedIn),
|
||||||
const SizedBox(height: UiConstants.space3),
|
const SizedBox(height: UiConstants.space3),
|
||||||
EarlyCheckOutBanner(
|
EarlyCheckOutBanner(
|
||||||
availabilityTime: checkOutAvailabilityTime ?? soonLabel,
|
availabilityTime: checkOutAvailabilityTime ?? soonLabel,
|
||||||
@@ -146,7 +146,7 @@ class ClockInActionSection extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
spacing: UiConstants.space4,
|
spacing: UiConstants.space4,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
const GeofenceStatusBanner(),
|
GeofenceStatusBanner(isClockedIn: isCheckedIn),
|
||||||
_currentInteraction.buildActionWidget(
|
_currentInteraction.buildActionWidget(
|
||||||
isCheckedIn: isCheckedIn,
|
isCheckedIn: isCheckedIn,
|
||||||
isDisabled: isGeofenceBlocking,
|
isDisabled: isGeofenceBlocking,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
|
|
||||||
import '../../bloc/geofence/geofence_bloc.dart';
|
import '../../bloc/geofence/geofence_bloc.dart';
|
||||||
import '../../bloc/geofence/geofence_state.dart';
|
import '../../bloc/geofence/geofence_state.dart';
|
||||||
|
import 'outside_work_area_banner.dart';
|
||||||
import 'permission_denied_banner.dart';
|
import 'permission_denied_banner.dart';
|
||||||
import 'permission_denied_forever_banner.dart';
|
import 'permission_denied_forever_banner.dart';
|
||||||
import 'service_disabled_banner.dart';
|
import 'service_disabled_banner.dart';
|
||||||
@@ -17,9 +18,18 @@ import 'verifying_banner.dart';
|
|||||||
///
|
///
|
||||||
/// Reads [GeofenceBloc] state directly and renders the appropriate
|
/// Reads [GeofenceBloc] state directly and renders the appropriate
|
||||||
/// banner variant based on permission, location, and verification conditions.
|
/// 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 {
|
class GeofenceStatusBanner extends StatelessWidget {
|
||||||
/// Creates a [GeofenceStatusBanner].
|
/// 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -77,6 +87,12 @@ class GeofenceStatusBanner extends StatelessWidget {
|
|||||||
if (!state.isLocationVerified &&
|
if (!state.isLocationVerified &&
|
||||||
!state.isLocationTimedOut &&
|
!state.isLocationTimedOut &&
|
||||||
state.distanceFromTarget != null) {
|
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!);
|
return TooFarBanner(distanceMeters: state.distanceFromTarget!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user