UI: Cyber/IT design overhaul with semantic status tokens (Material 3)
Re-theme TasQ with a refined enterprise "Cyber/IT" identity on Material 3. Functionality is unchanged — only color/shape tokens and a few state widgets. Foundation: - Hand-tuned light + dark ColorSchemes (deep-slate surfaces, azure/teal/violet accents), tightened 12-16dp shape language, flat hairline-outlined cards. - New AppStatusColors theme extension (success/warning/danger/info/neutral, per-brightness) replacing scattered raw Colors.green/orange/red/grey so status color is consistent and legible in dark mode. - New TechChip mono primitive; StatusPill.kind; M3Card hairline variants; tightened AppSurfaces radii. Per-screen: - Tokenized status colors + radii across dashboard, tickets, tasks, IT service requests, attendance, whereabouts, network map, reports charts, auth, and shared/admin screens. Added _ticketStatusKind/_taskStatusKind/_isrStatusKind mappers so status carries meaning consistently. - Fixed white pie-slice borders for dark mode; dropped ad-hoc dialog radius overrides in favor of the theme default. Categorical palettes (leave-type icons, team color picker, distribution charts) intentionally preserved. Verified: flutter analyze lib clean; flutter build web green; visual QA in light and dark (dashboard, tickets, attendance). Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
@@ -111,8 +113,8 @@ class _GeofenceTestScreenState extends ConsumerState<GeofenceTestScreen> {
|
||||
.check_circle
|
||||
: Icons.cancel,
|
||||
color: inside
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
? AppStatusColors.of(context).success.color
|
||||
: AppStatusColors.of(context).danger.color,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
@@ -166,8 +168,8 @@ class _GeofenceTestScreenState extends ConsumerState<GeofenceTestScreen> {
|
||||
? Icons.check_circle
|
||||
: Icons.cancel,
|
||||
color: inside
|
||||
? Colors.green
|
||||
: Colors.red,
|
||||
? AppStatusColors.of(context).success.color
|
||||
: AppStatusColors.of(context).danger.color,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
@@ -275,10 +277,10 @@ class _GeofenceTestScreenState extends ConsumerState<GeofenceTestScreen> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
// `child` is used by newer flutter_map Marker API.
|
||||
child: const Icon(
|
||||
child: Icon(
|
||||
Icons.person_pin_circle,
|
||||
size: 36,
|
||||
color: Colors.blue,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -289,10 +291,12 @@ class _GeofenceTestScreenState extends ConsumerState<GeofenceTestScreen> {
|
||||
polygons: [
|
||||
Polygon(
|
||||
points: polygonPoints,
|
||||
color: Colors.green.withValues(
|
||||
alpha: 0.15,
|
||||
),
|
||||
borderColor: Colors.green,
|
||||
color: AppStatusColors.of(context)
|
||||
.success
|
||||
.color
|
||||
.withValues(alpha: 0.15),
|
||||
borderColor:
|
||||
AppStatusColors.of(context).success.color,
|
||||
borderStrokeWidth: 2,
|
||||
),
|
||||
],
|
||||
@@ -304,9 +308,10 @@ class _GeofenceTestScreenState extends ConsumerState<GeofenceTestScreen> {
|
||||
circles: [
|
||||
CircleMarker(
|
||||
point: LatLng(cfg!.lat!, cfg.lng!),
|
||||
color: Colors.green.withValues(
|
||||
alpha: 0.10,
|
||||
),
|
||||
color: AppStatusColors.of(context)
|
||||
.success
|
||||
.color
|
||||
.withValues(alpha: 0.10),
|
||||
borderStrokeWidth: 2,
|
||||
useRadiusInMeter: true,
|
||||
radius: cfg.radiusMeters ?? 100,
|
||||
@@ -387,11 +392,15 @@ class _GeofenceTestScreenState extends ConsumerState<GeofenceTestScreen> {
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green.withValues(
|
||||
color: AppStatusColors.of(
|
||||
context,
|
||||
).success.color.withValues(
|
||||
alpha: 0.25,
|
||||
),
|
||||
border: Border.all(
|
||||
color: Colors.green,
|
||||
color: AppStatusColors.of(
|
||||
context,
|
||||
).success.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -420,14 +429,16 @@ class _GeofenceTestScreenState extends ConsumerState<GeofenceTestScreen> {
|
||||
top: 6.0,
|
||||
),
|
||||
child: Row(
|
||||
children: const [
|
||||
children: [
|
||||
Icon(
|
||||
Icons.person_pin_circle,
|
||||
size: 14,
|
||||
color: Colors.blue,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text('You'),
|
||||
const SizedBox(width: 8),
|
||||
const Text('You'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -35,6 +35,7 @@ import 'logbook_day_activity.dart';
|
||||
import 'work_log_tab.dart';
|
||||
import '../../theme/m3_motion.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../utils/app_time.dart';
|
||||
import '../../utils/device_security.dart';
|
||||
import '../../utils/location_permission.dart';
|
||||
@@ -787,7 +788,7 @@ class _CheckInTabState extends ConsumerState<_CheckInTab> {
|
||||
} else if (diff.inMinutes <= 5) {
|
||||
timeColor = colors.error;
|
||||
} else if (diff.inMinutes <= 15) {
|
||||
timeColor = Colors.orange;
|
||||
timeColor = AppStatusColors.of(context).warning.color;
|
||||
} else if (diff.inMinutes <= 30) {
|
||||
timeColor = colors.tertiary;
|
||||
}
|
||||
@@ -847,7 +848,7 @@ class _CheckInTabState extends ConsumerState<_CheckInTab> {
|
||||
: Icons.cancel,
|
||||
size: 16,
|
||||
color: _insideGeofence || hasGeofenceOverride
|
||||
? Colors.green
|
||||
? AppStatusColors.of(context).success.color
|
||||
: colors.error,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
@@ -859,7 +860,7 @@ class _CheckInTabState extends ConsumerState<_CheckInTab> {
|
||||
: 'Outside geofence',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: _insideGeofence || hasGeofenceOverride
|
||||
? Colors.green
|
||||
? AppStatusColors.of(context).success.color
|
||||
: colors.error,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
@@ -1863,11 +1864,11 @@ class _CheckInTabState extends ConsumerState<_CheckInTab> {
|
||||
bg = colors.secondaryContainer;
|
||||
fg = colors.onSecondaryContainer;
|
||||
case 'Early Out':
|
||||
bg = Colors.orange.shade100;
|
||||
fg = Colors.orange.shade900;
|
||||
bg = AppStatusColors.of(context).warning.container;
|
||||
fg = AppStatusColors.of(context).warning.onContainer;
|
||||
case 'Noon Break':
|
||||
bg = Colors.blue.shade100;
|
||||
fg = Colors.blue.shade900;
|
||||
bg = AppStatusColors.of(context).info.container;
|
||||
fg = AppStatusColors.of(context).info.onContainer;
|
||||
case 'Absent':
|
||||
bg = colors.errorContainer;
|
||||
fg = colors.onErrorContainer;
|
||||
@@ -2841,13 +2842,14 @@ class _DateGroupTile extends StatelessWidget {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
final status = AppStatusColors.of(context);
|
||||
final statusColor = group.status == 'Absent'
|
||||
? Colors.red
|
||||
? status.danger.color
|
||||
: group.status == 'On duty'
|
||||
? Colors.orange
|
||||
? status.warning.color
|
||||
: group.status == 'On Leave'
|
||||
? Colors.teal
|
||||
: Colors.green;
|
||||
? status.info.color
|
||||
: status.success.color;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.fromLTRB(12, 8, 12, 0),
|
||||
@@ -2960,10 +2962,10 @@ class _DateGroupTile extends StatelessWidget {
|
||||
String statusLabel;
|
||||
switch (slip.status) {
|
||||
case 'approved':
|
||||
statusColor = Colors.green;
|
||||
statusColor = AppStatusColors.of(context).success.color;
|
||||
statusLabel = 'Approved';
|
||||
case 'pending':
|
||||
statusColor = Colors.orange;
|
||||
statusColor = AppStatusColors.of(context).warning.color;
|
||||
statusLabel = 'Pending';
|
||||
case 'rejected':
|
||||
statusColor = colors.error;
|
||||
@@ -3081,7 +3083,7 @@ class _DateGroupTile extends StatelessWidget {
|
||||
? Chip(
|
||||
label: Text(entry.isLeave ? 'On Leave' : 'Absent'),
|
||||
backgroundColor: entry.isLeave
|
||||
? Colors.teal.withValues(alpha: 0.15)
|
||||
? AppStatusColors.of(context).info.container
|
||||
: colors.errorContainer,
|
||||
)
|
||||
: entry.status == 'On duty'
|
||||
@@ -3107,7 +3109,7 @@ class _DateGroupTile extends StatelessWidget {
|
||||
switch (entry.verificationStatus) {
|
||||
case 'verified':
|
||||
icon = Icons.verified;
|
||||
color = Colors.green;
|
||||
color = AppStatusColors.of(context).success.color;
|
||||
tooltip = 'Verified';
|
||||
case 'unverified' || 'skipped':
|
||||
icon = Icons.warning_amber_rounded;
|
||||
@@ -3115,7 +3117,7 @@ class _DateGroupTile extends StatelessWidget {
|
||||
tooltip = 'Unverified';
|
||||
default:
|
||||
icon = Icons.hourglass_bottom;
|
||||
color = Colors.orange;
|
||||
color = AppStatusColors.of(context).warning.color;
|
||||
tooltip = 'Pending';
|
||||
}
|
||||
final badge = Tooltip(
|
||||
@@ -3526,7 +3528,7 @@ class _CheckInOutMapTab extends StatelessWidget {
|
||||
title: 'Check In',
|
||||
lat: entry.checkInLat,
|
||||
lng: entry.checkInLng,
|
||||
markerColor: Colors.green,
|
||||
markerColor: AppStatusColors.of(context).success.color,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -3536,7 +3538,7 @@ class _CheckInOutMapTab extends StatelessWidget {
|
||||
title: 'Check Out',
|
||||
lat: entry.checkOutLat,
|
||||
lng: entry.checkOutLng,
|
||||
markerColor: Colors.red,
|
||||
markerColor: AppStatusColors.of(context).danger.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -5830,7 +5832,7 @@ class _PassSlipTabState extends ConsumerState<_PassSlipTab> {
|
||||
IconData statusIcon;
|
||||
switch (slip.status) {
|
||||
case 'approved':
|
||||
statusColor = Colors.green;
|
||||
statusColor = AppStatusColors.of(context).success.color;
|
||||
statusIcon = Icons.check_circle;
|
||||
case 'rejected':
|
||||
statusColor = colors.error;
|
||||
@@ -5839,7 +5841,7 @@ class _PassSlipTabState extends ConsumerState<_PassSlipTab> {
|
||||
statusColor = colors.primary;
|
||||
statusIcon = Icons.task_alt;
|
||||
default:
|
||||
statusColor = Colors.orange;
|
||||
statusColor = AppStatusColors.of(context).warning.color;
|
||||
statusIcon = Icons.hourglass_top;
|
||||
}
|
||||
|
||||
@@ -6248,7 +6250,7 @@ class _LeaveTabState extends ConsumerState<_LeaveTab> {
|
||||
IconData statusIcon;
|
||||
switch (leave.status) {
|
||||
case 'approved':
|
||||
statusColor = Colors.teal;
|
||||
statusColor = AppStatusColors.of(context).success.color;
|
||||
statusIcon = Icons.check_circle;
|
||||
case 'rejected':
|
||||
statusColor = colors.error;
|
||||
@@ -6257,7 +6259,7 @@ class _LeaveTabState extends ConsumerState<_LeaveTab> {
|
||||
statusColor = colors.onSurfaceVariant;
|
||||
statusIcon = Icons.remove_circle_outline;
|
||||
default:
|
||||
statusColor = Colors.orange;
|
||||
statusColor = AppStatusColors.of(context).warning.color;
|
||||
statusIcon = Icons.hourglass_top;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../../models/it_service_request.model.dart';
|
||||
import '../../models/pass_slip.model.dart';
|
||||
import '../../models/task.model.dart';
|
||||
import '../../models/ticket.model.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../theme/m3_motion.dart';
|
||||
import '../../utils/app_time.dart';
|
||||
|
||||
@@ -427,7 +428,7 @@ class _SummaryChip extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../providers/auth_provider.dart';
|
||||
import '../../providers/tickets_provider.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../theme/m3_motion.dart';
|
||||
import '../../utils/snackbar.dart';
|
||||
|
||||
@@ -563,23 +564,24 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen>
|
||||
}
|
||||
|
||||
final normalized = (score / 6).clamp(0.0, 1.0);
|
||||
final s = AppStatusColors.of(context);
|
||||
String label;
|
||||
Color color;
|
||||
if (normalized <= 0.2) {
|
||||
label = 'Very weak';
|
||||
color = Colors.red;
|
||||
color = s.danger.color;
|
||||
} else if (normalized <= 0.4) {
|
||||
label = 'Weak';
|
||||
color = Colors.deepOrange;
|
||||
color = s.danger.color;
|
||||
} else if (normalized <= 0.6) {
|
||||
label = 'Fair';
|
||||
color = Colors.orange;
|
||||
color = s.warning.color;
|
||||
} else if (normalized <= 0.8) {
|
||||
label = 'Strong';
|
||||
color = Colors.green;
|
||||
color = s.info.color;
|
||||
} else {
|
||||
label = 'Excellent';
|
||||
color = Colors.teal;
|
||||
color = s.success.color;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
|
||||
@@ -38,6 +38,7 @@ import '../../widgets/app_breakpoints.dart';
|
||||
import '../../providers/realtime_controller.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../widgets/mono_text.dart';
|
||||
import '../../widgets/app_page_header.dart';
|
||||
import '../../utils/app_time.dart';
|
||||
@@ -776,14 +777,29 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
||||
}
|
||||
|
||||
Widget _sectionTitle(BuildContext context, String title) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 3,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(
|
||||
color: cs.primary,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
title.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.8,
|
||||
color: cs.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -918,21 +934,26 @@ class _MetricCard extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
final isDark = cs.brightness == Brightness.dark;
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeOutCubic,
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: cs.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: isDark ? cs.surfaceContainer : cs.surfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppSurfaces.of(context).cardRadius,
|
||||
),
|
||||
border: Border.all(color: cs.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
title.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.6,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
@@ -967,14 +988,16 @@ class _StaffTable extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
// M3 Expressive: tonal surface container, 28 dp radius for large containers.
|
||||
final isDark = cs.brightness == Brightness.dark;
|
||||
// Cyber/IT panel: flat tonal fill + hairline outline.
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: cs.surfaceContainerLow,
|
||||
color: isDark ? cs.surfaceContainer : cs.surfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppSurfaces.of(context).containerRadius,
|
||||
),
|
||||
border: Border.all(color: cs.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -1059,18 +1082,23 @@ class _StaffTableBody extends ConsumerWidget {
|
||||
);
|
||||
|
||||
if (providerState == 'loading') {
|
||||
return const Text('Loading staff...');
|
||||
return _panelHint(context, Icons.hourglass_empty_rounded, 'Loading staff…');
|
||||
}
|
||||
|
||||
if (providerState.startsWith('error:')) {
|
||||
final err = providerState.substring(6);
|
||||
return Text('Failed to load staff: $err');
|
||||
return _panelHint(
|
||||
context,
|
||||
Icons.error_outline_rounded,
|
||||
'Failed to load staff: $err',
|
||||
);
|
||||
}
|
||||
|
||||
if (staffRows.isEmpty) {
|
||||
return Text(
|
||||
return _panelHint(
|
||||
context,
|
||||
Icons.groups_outlined,
|
||||
'No IT staff available.',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1142,20 +1170,24 @@ class _StaffRow extends StatelessWidget {
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Center(
|
||||
child: Text(
|
||||
row.whereabouts,
|
||||
style: valueStyle?.copyWith(
|
||||
color: row.whereabouts == 'In premise'
|
||||
? Colors.green
|
||||
: row.whereabouts == 'Outside premise'
|
||||
? Colors.grey
|
||||
: row.whereabouts == 'Tracking off'
|
||||
? Colors.grey
|
||||
: row.whereabouts.startsWith('Last seen')
|
||||
? Colors.grey
|
||||
: null,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
final status = AppStatusColors.of(context);
|
||||
final Color? color = row.whereabouts == 'In premise'
|
||||
? status.success.color
|
||||
: (row.whereabouts == 'Outside premise' ||
|
||||
row.whereabouts == 'Tracking off' ||
|
||||
row.whereabouts.startsWith('Last seen'))
|
||||
? status.neutral.color
|
||||
: null;
|
||||
return Text(
|
||||
row.whereabouts,
|
||||
style: valueStyle?.copyWith(
|
||||
color: color,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -1202,21 +1234,24 @@ class _StaffActivityChart extends ConsumerWidget {
|
||||
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
final isDark = cs.brightness == Brightness.dark;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: cs.surfaceContainerLow,
|
||||
color: isDark ? cs.surfaceContainer : cs.surfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppSurfaces.of(context).containerRadius,
|
||||
),
|
||||
border: Border.all(color: cs.outlineVariant),
|
||||
),
|
||||
child: metricsAsync.when(
|
||||
data: (metrics) {
|
||||
final staffRows = metrics.staffRows;
|
||||
if (staffRows.isEmpty) {
|
||||
return Text(
|
||||
return _panelHint(
|
||||
context,
|
||||
Icons.bar_chart_outlined,
|
||||
'No staff activity data available.',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1393,16 +1428,16 @@ class _StaffActivityChart extends ConsumerWidget {
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withAlpha(
|
||||
(0.35 * 255).round(),
|
||||
color: cs.inverseSurface.withValues(alpha: 0.9),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppSurfaces.of(context).chipRadius,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
child: MonoText(
|
||||
total.toString(),
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(
|
||||
color: Colors.white,
|
||||
color: cs.onInverseSurface,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
@@ -1534,8 +1569,13 @@ class _StaffActivityChart extends ConsumerWidget {
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) => Text('Failed to load activity: $error'),
|
||||
loading: () =>
|
||||
_panelHint(context, Icons.hourglass_empty_rounded, 'Loading activity…'),
|
||||
error: (error, stack) => _panelHint(
|
||||
context,
|
||||
Icons.error_outline_rounded,
|
||||
'Failed to load activity: $error',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1556,29 +1596,28 @@ class _PulseStatusPill extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final s = AppStatusColors.of(context);
|
||||
final (Color bg, Color fg) = switch (label.toLowerCase()) {
|
||||
'arrival' => (Colors.amber.shade100, Colors.amber.shade900),
|
||||
'late' => (Colors.red.shade100, Colors.red.shade900),
|
||||
'noon break' => (Colors.blue.shade100, Colors.blue.shade900),
|
||||
'vacant' => (Colors.green.shade100, Colors.green.shade900),
|
||||
'on task' => (Colors.purple.shade100, Colors.purple.shade900),
|
||||
'on event' => (Colors.purple.shade100, Colors.purple.shade900),
|
||||
'in triage' => (Colors.orange.shade100, Colors.orange.shade900),
|
||||
'early out' => (Colors.deepOrange.shade100, Colors.deepOrange.shade900),
|
||||
'on leave' => (Colors.teal.shade100, Colors.teal.shade900),
|
||||
'absent' => (Colors.red.shade200, Colors.red.shade900),
|
||||
'off duty' => (Colors.grey.shade200, Colors.grey.shade700),
|
||||
_ => (
|
||||
Theme.of(context).colorScheme.tertiaryContainer,
|
||||
Theme.of(context).colorScheme.onTertiaryContainer,
|
||||
),
|
||||
'arrival' => (s.success.container, s.success.onContainer),
|
||||
'late' => (s.warning.container, s.warning.onContainer),
|
||||
'noon break' => (s.info.container, s.info.onContainer),
|
||||
'vacant' => (s.neutral.container, s.neutral.onContainer),
|
||||
'on task' => (cs.primaryContainer, cs.onPrimaryContainer),
|
||||
'on event' => (cs.tertiaryContainer, cs.onTertiaryContainer),
|
||||
'in triage' => (s.warning.container, s.warning.onContainer),
|
||||
'early out' => (s.warning.container, s.warning.onContainer),
|
||||
'on leave' => (s.info.container, s.info.onContainer),
|
||||
'absent' => (s.danger.container, s.danger.onContainer),
|
||||
'off duty' => (s.neutral.container, s.neutral.onContainer),
|
||||
_ => (cs.tertiaryContainer, cs.onTertiaryContainer),
|
||||
};
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
@@ -1591,6 +1630,31 @@ class _PulseStatusPill extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compact, muted, centered hint for empty/loading/error states inside a
|
||||
/// bounded dashboard panel (too small for the full-screen AppEmptyView).
|
||||
Widget _panelHint(BuildContext context, IconData icon, String message) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 18, color: cs.onSurfaceVariant),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
message,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(color: cs.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Duration? _averageDuration(List<Duration> durations) {
|
||||
if (durations.isEmpty) {
|
||||
return null;
|
||||
|
||||
@@ -29,6 +29,7 @@ import '../../widgets/gemini_animated_text_field.dart';
|
||||
import '../../widgets/m3_card.dart';
|
||||
import '../../widgets/mono_text.dart';
|
||||
import '../../widgets/responsive_body.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../widgets/status_pill.dart';
|
||||
import 'it_service_request_pdf.dart';
|
||||
|
||||
@@ -411,13 +412,13 @@ class _ItServiceRequestDetailScreenState
|
||||
),
|
||||
)
|
||||
: _hasRecentSave
|
||||
? const Padding(
|
||||
key: ValueKey('isr_saved'),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
? Padding(
|
||||
key: const ValueKey('isr_saved'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Icon(
|
||||
Icons.check_circle,
|
||||
size: 18,
|
||||
color: Colors.green,
|
||||
color: AppStatusColors.of(context).success.color,
|
||||
),
|
||||
)
|
||||
: const SizedBox(key: ValueKey('isr_idle')),
|
||||
@@ -555,7 +556,10 @@ class _ItServiceRequestDetailScreenState
|
||||
if (request.requestNumber != null)
|
||||
MonoText(request.requestNumber!),
|
||||
const SizedBox(width: 12),
|
||||
StatusPill(label: ItServiceRequestStatus.label(request.status)),
|
||||
StatusPill.kind(
|
||||
_isrStatusKind(request.status),
|
||||
label: ItServiceRequestStatus.label(request.status),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'Created ${AppTime.formatDate(request.createdAt)}',
|
||||
@@ -1231,10 +1235,10 @@ class _ItServiceRequestDetailScreenState
|
||||
child: const Icon(Icons.save, size: 14),
|
||||
)
|
||||
: isSaved
|
||||
? const Icon(
|
||||
? Icon(
|
||||
Icons.check_circle,
|
||||
key: ValueKey('label_saved'),
|
||||
color: Colors.green,
|
||||
key: const ValueKey('label_saved'),
|
||||
color: AppStatusColors.of(context).success.color,
|
||||
size: 14,
|
||||
)
|
||||
: const SizedBox(key: ValueKey('label_idle')),
|
||||
@@ -1584,9 +1588,6 @@ class _ItServiceRequestDetailScreenState
|
||||
final controller = TextEditingController();
|
||||
return AlertDialog(
|
||||
title: const Text('Cancellation Reason'),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
decoration: const InputDecoration(
|
||||
@@ -1836,7 +1837,6 @@ class _StaffPickerDialogState extends State<_StaffPickerDialog> {
|
||||
.toList();
|
||||
return AlertDialog(
|
||||
title: const Text('Select IT Staff'),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
|
||||
content: SizedBox(
|
||||
width: 350,
|
||||
height: 400,
|
||||
@@ -2245,3 +2245,16 @@ class _EvidenceSectionState extends ConsumerState<_EvidenceSection> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps an IT service request status to a semantic [StatusKind]
|
||||
/// (amber=pending approval, blue=scheduled/in-progress, green=completed,
|
||||
/// grey=cancelled).
|
||||
StatusKind _isrStatusKind(String status) => switch (status) {
|
||||
ItServiceRequestStatus.pendingApproval => StatusKind.warning,
|
||||
ItServiceRequestStatus.scheduled => StatusKind.info,
|
||||
ItServiceRequestStatus.inProgress => StatusKind.info,
|
||||
ItServiceRequestStatus.inProgressDryRun => StatusKind.info,
|
||||
ItServiceRequestStatus.completed => StatusKind.success,
|
||||
ItServiceRequestStatus.cancelled => StatusKind.neutral,
|
||||
_ => StatusKind.neutral,
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import '../../providers/tickets_provider.dart';
|
||||
import '../../theme/m3_motion.dart';
|
||||
import '../../utils/app_time.dart';
|
||||
import '../../utils/snackbar.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../widgets/m3_card.dart';
|
||||
import '../../widgets/mono_text.dart';
|
||||
import '../../widgets/reconnect_overlay.dart';
|
||||
@@ -381,9 +382,6 @@ class _ItServiceRequestsListScreenState
|
||||
builder: (ctx, setDialogState) {
|
||||
return AlertDialog(
|
||||
title: const Text('New IT Service Request'),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
content: SizedBox(
|
||||
width: 400,
|
||||
child: SingleChildScrollView(
|
||||
@@ -538,6 +536,7 @@ class _StatusSummaryRow extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final status = AppStatusColors.of(context);
|
||||
final pending = requests
|
||||
.where((r) => r.status == ItServiceRequestStatus.pendingApproval)
|
||||
.length;
|
||||
@@ -563,7 +562,7 @@ class _StatusSummaryRow extends StatelessWidget {
|
||||
_SummaryChip(
|
||||
label: 'Pending',
|
||||
count: pending,
|
||||
color: cs.tertiary,
|
||||
color: status.warning.color,
|
||||
selected: selectedStatus == ItServiceRequestStatus.pendingApproval,
|
||||
onTap: () => onStatusTap(ItServiceRequestStatus.pendingApproval),
|
||||
),
|
||||
@@ -571,7 +570,7 @@ class _StatusSummaryRow extends StatelessWidget {
|
||||
_SummaryChip(
|
||||
label: 'Scheduled',
|
||||
count: scheduled,
|
||||
color: cs.primary,
|
||||
color: status.info.color,
|
||||
selected: selectedStatus == ItServiceRequestStatus.scheduled,
|
||||
onTap: () => onStatusTap(ItServiceRequestStatus.scheduled),
|
||||
),
|
||||
@@ -587,7 +586,7 @@ class _StatusSummaryRow extends StatelessWidget {
|
||||
_SummaryChip(
|
||||
label: 'Completed',
|
||||
count: completed,
|
||||
color: Colors.green,
|
||||
color: status.success.color,
|
||||
selected: selectedStatus == ItServiceRequestStatus.completed,
|
||||
onTap: () => onStatusTap(ItServiceRequestStatus.completed),
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../models/network/network_device.dart';
|
||||
import '../../models/network/network_location.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../providers/network_map/network_devices_provider.dart';
|
||||
import '../../providers/network_map/network_sites_provider.dart';
|
||||
import '../../widgets/app_state_view.dart';
|
||||
@@ -231,11 +232,19 @@ class _NetworkMapDeviceEditScreenState
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
switch (_status) {
|
||||
case NetworkDeviceStatus.online:
|
||||
return const Icon(Icons.circle, size: 14, color: Color(0xFF388E3C));
|
||||
return Icon(
|
||||
Icons.circle,
|
||||
size: 14,
|
||||
color: AppStatusColors.of(context).success.color,
|
||||
);
|
||||
case NetworkDeviceStatus.offline:
|
||||
return Icon(Icons.circle, size: 14, color: cs.error);
|
||||
case NetworkDeviceStatus.warning:
|
||||
return const Icon(Icons.circle, size: 14, color: Color(0xFFF57C00));
|
||||
return Icon(
|
||||
Icons.circle,
|
||||
size: 14,
|
||||
color: AppStatusColors.of(context).warning.color,
|
||||
);
|
||||
case NetworkDeviceStatus.unknown:
|
||||
return Icon(Icons.circle_outlined, size: 14, color: cs.onSurfaceVariant);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../models/network/network_device.dart';
|
||||
import '../../../theme/app_status_colors.dart';
|
||||
import 'network_device_icon.dart';
|
||||
|
||||
/// A styled node representing one network device on the topology canvas.
|
||||
@@ -41,14 +42,15 @@ class DeviceNode extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Color? _statusColor(ColorScheme cs, NetworkDeviceStatus status) {
|
||||
Color? _statusColor(BuildContext context, NetworkDeviceStatus status) {
|
||||
final s = AppStatusColors.of(context);
|
||||
switch (status) {
|
||||
case NetworkDeviceStatus.online:
|
||||
return const Color(0xFF34A853); // Google green — universally understood
|
||||
return s.success.color;
|
||||
case NetworkDeviceStatus.offline:
|
||||
return cs.error;
|
||||
return Theme.of(context).colorScheme.error;
|
||||
case NetworkDeviceStatus.warning:
|
||||
return const Color(0xFFFBBC04); // Amber
|
||||
return s.warning.color;
|
||||
case NetworkDeviceStatus.unknown:
|
||||
return null; // No dot shown
|
||||
}
|
||||
@@ -59,7 +61,7 @@ class DeviceNode extends StatelessWidget {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final tt = Theme.of(context).textTheme;
|
||||
final accent = _accentForRole(cs, device.role);
|
||||
final statusColor = _statusColor(cs, device.status);
|
||||
final statusColor = _statusColor(context, device.status);
|
||||
|
||||
final bgColor = isSelected
|
||||
? cs.primaryContainer
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../models/network/network_device.dart';
|
||||
import '../../../models/network/network_link.dart';
|
||||
import '../../../theme/app_surfaces.dart';
|
||||
|
||||
/// Collapsible legend overlay explaining device role border colours and link
|
||||
/// type colours. Positioned at the bottom-left of the topology canvas view.
|
||||
@@ -85,11 +86,15 @@ class _TopologyLegendState extends State<TopologyLegend>
|
||||
// Expand/collapse toggle chip
|
||||
Material(
|
||||
color: cs.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppSurfaces.of(context).containerRadius,
|
||||
),
|
||||
elevation: 2,
|
||||
child: InkWell(
|
||||
onTap: _toggle,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppSurfaces.of(context).containerRadius,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
|
||||
@@ -109,7 +109,7 @@ class _RequestTypeChartState extends ConsumerState<RequestTypeChart> {
|
||||
radius: isTouched ? 60 : 50,
|
||||
color: colorFor(i, e.name),
|
||||
borderSide: isTouched
|
||||
? const BorderSide(color: Colors.white, width: 2)
|
||||
? BorderSide(color: Theme.of(context).colorScheme.surface, width: 2)
|
||||
: BorderSide.none,
|
||||
);
|
||||
}).toList(),
|
||||
@@ -240,7 +240,7 @@ class _RequestCategoryChartState extends ConsumerState<RequestCategoryChart> {
|
||||
radius: isTouched ? 60 : 50,
|
||||
color: colorFor(i, e.name),
|
||||
borderSide: isTouched
|
||||
? const BorderSide(color: Colors.white, width: 2)
|
||||
? BorderSide(color: Theme.of(context).colorScheme.surface, width: 2)
|
||||
: BorderSide.none,
|
||||
);
|
||||
}).toList(),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../providers/reports_provider.dart';
|
||||
import '../../../theme/app_status_colors.dart';
|
||||
import 'report_card_wrapper.dart';
|
||||
|
||||
/// Donut chart ticket counts per status with hover animation.
|
||||
@@ -84,7 +85,7 @@ class _TicketsByStatusChartState extends ConsumerState<TicketsByStatusChart> {
|
||||
radius: isTouched ? 60 : 50,
|
||||
color: _ticketStatusColor(context, e.status),
|
||||
borderSide: isTouched
|
||||
? const BorderSide(color: Colors.white, width: 2)
|
||||
? BorderSide(color: Theme.of(context).colorScheme.surface, width: 2)
|
||||
: BorderSide.none,
|
||||
);
|
||||
}).toList(),
|
||||
@@ -116,16 +117,18 @@ class _TicketsByStatusChartState extends ConsumerState<TicketsByStatusChart> {
|
||||
}
|
||||
|
||||
Color _ticketStatusColor(BuildContext context, String status) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final s = AppStatusColors.of(context);
|
||||
switch (status) {
|
||||
case 'critical':
|
||||
return s.danger.color;
|
||||
case 'pending':
|
||||
return colors.tertiary;
|
||||
return s.warning.color;
|
||||
case 'promoted':
|
||||
return colors.secondary;
|
||||
return s.info.color;
|
||||
case 'closed':
|
||||
return colors.primary;
|
||||
return s.success.color;
|
||||
default:
|
||||
return colors.outlineVariant;
|
||||
return s.neutral.color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,7 +211,7 @@ class _TasksByStatusChartState extends ConsumerState<TasksByStatusChart> {
|
||||
radius: isTouched ? 60 : 50,
|
||||
color: _taskStatusColor(context, e.status),
|
||||
borderSide: isTouched
|
||||
? const BorderSide(color: Colors.white, width: 2)
|
||||
? BorderSide(color: Theme.of(context).colorScheme.surface, width: 2)
|
||||
: BorderSide.none,
|
||||
);
|
||||
}).toList(),
|
||||
@@ -240,18 +243,20 @@ class _TasksByStatusChartState extends ConsumerState<TasksByStatusChart> {
|
||||
}
|
||||
|
||||
Color _taskStatusColor(BuildContext context, String status) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final s = AppStatusColors.of(context);
|
||||
switch (status) {
|
||||
case 'critical':
|
||||
return s.danger.color;
|
||||
case 'queued':
|
||||
return colors.surfaceContainerHighest;
|
||||
return s.neutral.color;
|
||||
case 'in_progress':
|
||||
return colors.secondary;
|
||||
return s.info.color;
|
||||
case 'completed':
|
||||
return colors.primary;
|
||||
return s.success.color;
|
||||
case 'cancelled':
|
||||
return colors.error;
|
||||
return s.danger.color;
|
||||
default:
|
||||
return colors.outlineVariant;
|
||||
return s.neutral.color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../providers/verification_session_provider.dart';
|
||||
import '../../services/face_verification.dart' as face;
|
||||
import '../../theme/app_status_colors.dart';
|
||||
|
||||
/// Screen opened on mobile when the user scans a QR code for cross-device
|
||||
/// face verification. Performs liveness detection and uploads the result.
|
||||
@@ -144,7 +145,11 @@ class _MobileVerificationScreenState
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.check_circle, size: 64, color: Colors.green),
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
size: 64,
|
||||
color: AppStatusColors.of(context).success.color,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('Verification Complete', style: theme.textTheme.headlineSmall),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../theme/m3_motion.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
@@ -67,7 +68,10 @@ class _PermissionsScreenState extends ConsumerState<PermissionsScreen> {
|
||||
title: Text(info.label),
|
||||
subtitle: Text(status?.toString() ?? 'unknown'),
|
||||
trailing: granted
|
||||
? const Icon(Icons.check, color: Colors.green)
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: AppStatusColors.of(context).success.color,
|
||||
)
|
||||
: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
||||
@@ -13,6 +13,8 @@ import '../../providers/teams_provider.dart';
|
||||
import '../../utils/app_time.dart';
|
||||
import '../../utils/snackbar.dart';
|
||||
import '../../widgets/app_breakpoints.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../widgets/m3_card.dart';
|
||||
import '../../widgets/mono_text.dart';
|
||||
import '../../widgets/profile_avatar.dart';
|
||||
@@ -231,24 +233,25 @@ class _ItJobChecklistTabState extends ConsumerState<ItJobChecklistTab> {
|
||||
}
|
||||
|
||||
Widget _buildSkeletonTile(BuildContext context, bool isWide) {
|
||||
final bone = Theme.of(context).colorScheme.surfaceContainerHighest;
|
||||
return M3Card.outlined(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(width: 60, height: 14, color: Colors.grey),
|
||||
Container(width: 60, height: 14, color: bone),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Container(height: 14, color: Colors.grey),
|
||||
child: Container(height: 14, color: bone),
|
||||
),
|
||||
if (isWide) ...[
|
||||
const SizedBox(width: 12),
|
||||
Container(width: 80, height: 22, color: Colors.grey),
|
||||
Container(width: 80, height: 22, color: bone),
|
||||
const SizedBox(width: 12),
|
||||
Container(width: 120, height: 14, color: Colors.grey),
|
||||
Container(width: 120, height: 14, color: bone),
|
||||
],
|
||||
const SizedBox(width: 12),
|
||||
Container(width: 24, height: 24, color: Colors.grey),
|
||||
Container(width: 24, height: 24, color: bone),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -292,20 +295,21 @@ class _StatsCard extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
|
||||
final status = AppStatusColors.of(context);
|
||||
final submittedChip = _StatChip(
|
||||
icon: Icons.check_circle_outline,
|
||||
label: 'Submitted',
|
||||
count: submitted,
|
||||
color: cs.primary,
|
||||
bgColor: cs.primaryContainer,
|
||||
color: status.success.onContainer,
|
||||
bgColor: status.success.container,
|
||||
);
|
||||
|
||||
final pendingChip = _StatChip(
|
||||
icon: Icons.pending_outlined,
|
||||
label: 'Pending',
|
||||
count: pending,
|
||||
color: cs.tertiary,
|
||||
bgColor: cs.tertiaryContainer,
|
||||
color: status.warning.onContainer,
|
||||
bgColor: status.warning.container,
|
||||
);
|
||||
|
||||
return M3Card.filled(
|
||||
@@ -399,7 +403,7 @@ class _StatChip extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -878,11 +882,14 @@ class _ItJobTileState extends ConsumerState<_ItJobTile> {
|
||||
_isChecked ? cs.primaryContainer.withValues(alpha: 0.18) : null;
|
||||
|
||||
// Status badge
|
||||
final statusSwatch = _isChecked
|
||||
? AppStatusColors.of(context).success
|
||||
: AppStatusColors.of(context).warning;
|
||||
final statusBadge = Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: _isChecked ? cs.primaryContainer : cs.tertiaryContainer,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: statusSwatch.container,
|
||||
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -892,13 +899,13 @@ class _ItJobTileState extends ConsumerState<_ItJobTile> {
|
||||
? Icons.check_circle_outline
|
||||
: Icons.hourglass_empty_outlined,
|
||||
size: 12,
|
||||
color: _isChecked ? cs.primary : cs.tertiary,
|
||||
color: statusSwatch.onContainer,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
_isChecked ? 'Submitted' : 'Pending',
|
||||
style: tt.labelSmall?.copyWith(
|
||||
color: _isChecked ? cs.primary : cs.tertiary,
|
||||
color: statusSwatch.onContainer,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -35,6 +35,7 @@ import '../../widgets/office_picker.dart';
|
||||
import '../../widgets/mono_text.dart';
|
||||
import '../../widgets/responsive_body.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../widgets/sync_pending_badge.dart';
|
||||
import '../../widgets/task_assignment_section.dart';
|
||||
import '../../widgets/typing_dots.dart';
|
||||
@@ -621,9 +622,9 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
color: Colors.orange,
|
||||
color: AppStatusColors.of(context).warning.color,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
@@ -2686,16 +2687,24 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
|
||||
final files =
|
||||
_attachments ?? [];
|
||||
if (files.isEmpty) {
|
||||
return const Center(
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(
|
||||
padding:
|
||||
const EdgeInsets.all(
|
||||
16.0,
|
||||
),
|
||||
child: Text(
|
||||
'No attachments yet',
|
||||
style: TextStyle(
|
||||
color: Colors.grey,
|
||||
),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.copyWith(
|
||||
color: Theme.of(
|
||||
context,
|
||||
)
|
||||
.colorScheme
|
||||
.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -2722,9 +2731,11 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
|
||||
).textTheme.bodySmall,
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(
|
||||
icon: Icon(
|
||||
Icons.delete,
|
||||
color: Colors.red,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.error,
|
||||
),
|
||||
onPressed: () =>
|
||||
_deleteTaskAttachment(
|
||||
@@ -2873,7 +2884,11 @@ class _TaskDetailScreenState extends ConsumerState<TaskDetailScreen>
|
||||
.colorScheme
|
||||
.surfaceContainerHighest,
|
||||
borderRadius:
|
||||
BorderRadius.circular(16),
|
||||
BorderRadius.circular(
|
||||
AppSurfaces.of(
|
||||
context,
|
||||
).cardRadius,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -4883,39 +4898,21 @@ class _TaskStatusChip extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final (icon, bg, fg) = switch (status) {
|
||||
'queued' => (
|
||||
Icons.inbox_rounded,
|
||||
cs.secondaryContainer,
|
||||
cs.onSecondaryContainer,
|
||||
),
|
||||
'in_progress' => (
|
||||
Icons.play_arrow_rounded,
|
||||
cs.primaryContainer,
|
||||
cs.onPrimaryContainer,
|
||||
),
|
||||
'completed' => (
|
||||
Icons.check_circle_rounded,
|
||||
cs.tertiaryContainer,
|
||||
cs.onTertiaryContainer,
|
||||
),
|
||||
'cancelled' => (
|
||||
Icons.cancel_rounded,
|
||||
cs.errorContainer,
|
||||
cs.onErrorContainer,
|
||||
),
|
||||
_ => (
|
||||
Icons.help_outline_rounded,
|
||||
cs.surfaceContainerHighest,
|
||||
cs.onSurface,
|
||||
),
|
||||
final s = AppStatusColors.of(context);
|
||||
final (IconData icon, StatusSwatch swatch) = switch (status) {
|
||||
'queued' => (Icons.inbox_rounded, s.neutral),
|
||||
'in_progress' => (Icons.play_arrow_rounded, s.info),
|
||||
'completed' => (Icons.check_circle_rounded, s.success),
|
||||
'cancelled' => (Icons.cancel_rounded, s.danger),
|
||||
_ => (Icons.help_outline_rounded, s.neutral),
|
||||
};
|
||||
final bg = swatch.container;
|
||||
final fg = swatch.onContainer;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -29,6 +29,7 @@ import '../../widgets/responsive_body.dart';
|
||||
import '../../widgets/tasq_adaptive_list.dart';
|
||||
import '../../widgets/typing_dots.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../utils/snackbar.dart';
|
||||
import '../../widgets/app_breakpoints.dart';
|
||||
import '../../widgets/app_page_header.dart';
|
||||
@@ -523,10 +524,10 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen>
|
||||
if (task.status == 'completed' &&
|
||||
task.hasIncompleteDetails) ...[
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
size: 16,
|
||||
color: Colors.orange,
|
||||
color: AppStatusColors.of(context).warning.color,
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -620,10 +621,10 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen>
|
||||
if (task.status == 'completed' &&
|
||||
task.hasIncompleteDetails) ...[
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
size: 16,
|
||||
color: Colors.orange,
|
||||
color: AppStatusColors.of(context).warning.color,
|
||||
),
|
||||
],
|
||||
if (showTyping) ...[
|
||||
@@ -1353,26 +1354,14 @@ class _StatusSummaryCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final background = switch (status) {
|
||||
'critical' => scheme.errorContainer,
|
||||
'queued' => scheme.surfaceContainerHighest,
|
||||
'in_progress' => scheme.secondaryContainer,
|
||||
'completed' => scheme.primaryContainer,
|
||||
_ => scheme.surfaceContainerHigh,
|
||||
};
|
||||
final foreground = switch (status) {
|
||||
'critical' => scheme.onErrorContainer,
|
||||
'queued' => scheme.onSurfaceVariant,
|
||||
'in_progress' => scheme.onSecondaryContainer,
|
||||
'completed' => scheme.onPrimaryContainer,
|
||||
_ => scheme.onSurfaceVariant,
|
||||
};
|
||||
final swatch = AppStatusColors.of(context).byKind(_taskStatusKind(status));
|
||||
final background = swatch.container;
|
||||
final foreground = swatch.onContainer;
|
||||
final label = status.replaceAll('_', ' ').toUpperCase();
|
||||
|
||||
return Material(
|
||||
color: background,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
@@ -1401,6 +1390,16 @@ class _StatusSummaryCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a task status to a semantic [StatusKind] (red=critical, blue=in
|
||||
/// progress, green=completed, grey=queued).
|
||||
StatusKind _taskStatusKind(String status) => switch (status) {
|
||||
'critical' => StatusKind.danger,
|
||||
'in_progress' => StatusKind.info,
|
||||
'completed' => StatusKind.success,
|
||||
'queued' => StatusKind.neutral,
|
||||
_ => StatusKind.neutral,
|
||||
};
|
||||
|
||||
String _assignedAgent(Map<String, Profile> profileById, String? userId) {
|
||||
if (userId == null || userId.isEmpty) {
|
||||
return 'Unassigned';
|
||||
@@ -1422,21 +1421,9 @@ class _StatusBadge extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final background = switch (status) {
|
||||
'critical' => scheme.errorContainer,
|
||||
'queued' => scheme.surfaceContainerHighest,
|
||||
'in_progress' => scheme.secondaryContainer,
|
||||
'completed' => scheme.primaryContainer,
|
||||
_ => scheme.surfaceContainerHighest,
|
||||
};
|
||||
final foreground = switch (status) {
|
||||
'critical' => scheme.onErrorContainer,
|
||||
'queued' => scheme.onSurfaceVariant,
|
||||
'in_progress' => scheme.onSecondaryContainer,
|
||||
'completed' => scheme.onPrimaryContainer,
|
||||
_ => scheme.onSurfaceVariant,
|
||||
};
|
||||
final swatch = AppStatusColors.of(context).byKind(_taskStatusKind(status));
|
||||
final background = swatch.container;
|
||||
final foreground = swatch.onContainer;
|
||||
|
||||
return Badge(
|
||||
backgroundColor: background,
|
||||
|
||||
@@ -22,6 +22,7 @@ import '../../widgets/responsive_body.dart';
|
||||
import '../../widgets/task_assignment_section.dart';
|
||||
import '../../widgets/typing_dots.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
|
||||
class TicketDetailScreen extends ConsumerStatefulWidget {
|
||||
const TicketDetailScreen({super.key, required this.ticketId});
|
||||
@@ -424,7 +425,9 @@ class _TicketDetailScreenState extends ConsumerState<TicketDetailScreen> {
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppSurfaces.of(context).cardRadius,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -467,7 +470,9 @@ class _TicketDetailScreenState extends ConsumerState<TicketDetailScreen> {
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppSurfaces.of(context).containerRadius,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16,
|
||||
@@ -1357,34 +1362,20 @@ class _TicketStatusChip extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final (icon, bg, fg) = switch (status) {
|
||||
'pending' => (
|
||||
Icons.hourglass_empty_rounded,
|
||||
cs.secondaryContainer,
|
||||
cs.onSecondaryContainer,
|
||||
),
|
||||
'promoted' => (
|
||||
Icons.trending_up_rounded,
|
||||
cs.primaryContainer,
|
||||
cs.onPrimaryContainer,
|
||||
),
|
||||
'closed' => (
|
||||
Icons.lock_rounded,
|
||||
cs.tertiaryContainer,
|
||||
cs.onTertiaryContainer,
|
||||
),
|
||||
_ => (
|
||||
Icons.help_outline_rounded,
|
||||
cs.surfaceContainerHighest,
|
||||
cs.onSurface,
|
||||
),
|
||||
final s = AppStatusColors.of(context);
|
||||
final (IconData icon, StatusSwatch swatch) = switch (status) {
|
||||
'pending' => (Icons.hourglass_empty_rounded, s.warning),
|
||||
'promoted' => (Icons.trending_up_rounded, s.info),
|
||||
'closed' => (Icons.lock_rounded, s.success),
|
||||
_ => (Icons.help_outline_rounded, s.neutral),
|
||||
};
|
||||
final bg = swatch.container;
|
||||
final fg = swatch.onContainer;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: bg,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -23,6 +23,7 @@ import '../../widgets/responsive_body.dart';
|
||||
import '../../widgets/tasq_adaptive_list.dart';
|
||||
import '../../widgets/typing_dots.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../utils/snackbar.dart';
|
||||
import '../../widgets/app_breakpoints.dart';
|
||||
import '../../widgets/app_page_header.dart';
|
||||
@@ -739,26 +740,14 @@ class _StatusSummaryCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final background = switch (status) {
|
||||
'critical' => scheme.errorContainer,
|
||||
'pending' => scheme.tertiaryContainer,
|
||||
'promoted' => scheme.secondaryContainer,
|
||||
'closed' => scheme.primaryContainer,
|
||||
_ => scheme.surfaceContainerHigh,
|
||||
};
|
||||
final foreground = switch (status) {
|
||||
'critical' => scheme.onErrorContainer,
|
||||
'pending' => scheme.onTertiaryContainer,
|
||||
'promoted' => scheme.onSecondaryContainer,
|
||||
'closed' => scheme.onPrimaryContainer,
|
||||
_ => scheme.onSurfaceVariant,
|
||||
};
|
||||
final swatch = AppStatusColors.of(context).byKind(_ticketStatusKind(status));
|
||||
final background = swatch.container;
|
||||
final foreground = swatch.onContainer;
|
||||
final label = status.replaceAll('_', ' ').toUpperCase();
|
||||
|
||||
return Material(
|
||||
color: background,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(AppSurfaces.of(context).chipRadius),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
child: Row(
|
||||
@@ -787,6 +776,16 @@ class _StatusSummaryCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a ticket status to a semantic [StatusKind] so its color carries meaning
|
||||
/// (red=critical, amber=pending, blue=promoted/escalated, green=closed).
|
||||
StatusKind _ticketStatusKind(String status) => switch (status) {
|
||||
'critical' => StatusKind.danger,
|
||||
'pending' => StatusKind.warning,
|
||||
'promoted' => StatusKind.info,
|
||||
'closed' => StatusKind.success,
|
||||
_ => StatusKind.neutral,
|
||||
};
|
||||
|
||||
/// First 8 chars of the UUID, like a short git hash; the full id is in the
|
||||
/// tooltip. Tickets have no human-friendly number.
|
||||
String _shortId(String id) =>
|
||||
@@ -813,21 +812,9 @@ class _StatusBadge extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final background = switch (status) {
|
||||
'critical' => scheme.errorContainer,
|
||||
'pending' => scheme.tertiaryContainer,
|
||||
'promoted' => scheme.secondaryContainer,
|
||||
'closed' => scheme.primaryContainer,
|
||||
_ => scheme.surfaceContainerHighest,
|
||||
};
|
||||
final foreground = switch (status) {
|
||||
'critical' => scheme.onErrorContainer,
|
||||
'pending' => scheme.onTertiaryContainer,
|
||||
'promoted' => scheme.onSecondaryContainer,
|
||||
'closed' => scheme.onPrimaryContainer,
|
||||
_ => scheme.onSurfaceVariant,
|
||||
};
|
||||
final swatch = AppStatusColors.of(context).byKind(_ticketStatusKind(status));
|
||||
final background = swatch.container;
|
||||
final foreground = swatch.onContainer;
|
||||
|
||||
return Badge(
|
||||
backgroundColor: background,
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../../providers/profile_provider.dart';
|
||||
import '../../providers/whereabouts_provider.dart';
|
||||
import '../../providers/workforce_provider.dart';
|
||||
import '../../theme/app_surfaces.dart';
|
||||
import '../../theme/app_status_colors.dart';
|
||||
import '../../widgets/app_page_header.dart';
|
||||
import '../../widgets/responsive_body.dart';
|
||||
import '../../utils/app_time.dart';
|
||||
@@ -232,8 +233,10 @@ class _WhereaboutsMap extends StatelessWidget {
|
||||
polygons: [
|
||||
Polygon(
|
||||
points: points,
|
||||
color: Colors.blue.withValues(alpha: 0.1),
|
||||
borderColor: Colors.blue,
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withValues(alpha: 0.12),
|
||||
borderColor: Theme.of(context).colorScheme.primary,
|
||||
borderStrokeWidth: 2.0,
|
||||
),
|
||||
],
|
||||
@@ -313,11 +316,20 @@ class _StaffLegendPanel extends StatelessWidget {
|
||||
).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const Spacer(),
|
||||
_RoleBadge(color: Colors.blue.shade700, label: 'Admin'),
|
||||
_RoleBadge(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
label: 'Admin',
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_RoleBadge(color: Colors.green.shade700, label: 'IT Staff'),
|
||||
_RoleBadge(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
label: 'IT Staff',
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_RoleBadge(color: Colors.orange.shade700, label: 'Dispatcher'),
|
||||
_RoleBadge(
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
label: 'Dispatcher',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -416,6 +428,7 @@ class _StaffLegendTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final status = AppStatusColors.of(context);
|
||||
final roleColor = _roleColor(profile.role, cs);
|
||||
|
||||
final hasPosition = position != null;
|
||||
@@ -445,30 +458,30 @@ class _StaffLegendTile extends StatelessWidget {
|
||||
? 'In premise (Tracking off) \u00b7 $timeLabel'
|
||||
: 'Outside premise (Tracking off) \u00b7 $timeLabel';
|
||||
statusColor = isStale
|
||||
? Colors.grey
|
||||
: (isInPremise ? Colors.green : Colors.grey);
|
||||
? status.neutral.color
|
||||
: (isInPremise ? status.success.color : status.neutral.color);
|
||||
} else if (inferredInPremise) {
|
||||
statusText = 'In premise (Checked in)';
|
||||
statusColor = Colors.green;
|
||||
statusColor = status.success.color;
|
||||
} else {
|
||||
statusText = 'Tracking off';
|
||||
statusColor = Colors.grey;
|
||||
statusColor = status.neutral.color;
|
||||
}
|
||||
} else if (isStale && hasPosition) {
|
||||
final timeLabel = _timeAgo(position!.updatedAt);
|
||||
statusText = isInPremise
|
||||
? 'Last seen in premise \u00b7 $timeLabel'
|
||||
: 'Last seen outside premise \u00b7 $timeLabel';
|
||||
statusColor = Colors.grey;
|
||||
statusColor = status.neutral.color;
|
||||
} else if (isActive) {
|
||||
statusText = 'In premise \u00b7 ${_timeAgo(position!.updatedAt)}';
|
||||
statusColor = Colors.green;
|
||||
statusColor = status.success.color;
|
||||
} else if (hasPosition) {
|
||||
statusText = 'Outside premise \u00b7 ${_timeAgo(position!.updatedAt)}';
|
||||
statusColor = Colors.grey;
|
||||
statusColor = status.neutral.color;
|
||||
} else {
|
||||
statusText = 'No location data';
|
||||
statusColor = Colors.grey;
|
||||
statusColor = status.neutral.color;
|
||||
}
|
||||
|
||||
final IconData statusIcon;
|
||||
|
||||
Reference in New Issue
Block a user