feat: Centralized Error Handling & Crash Fixes

This commit is contained in:
2026-02-11 18:52:23 +05:30
parent ea06510474
commit c1112ac01c
51 changed files with 2104 additions and 960 deletions

View File

@@ -1,3 +1,4 @@
import 'package:core_localization/core_localization.dart';
import 'package:design_system/design_system.dart'; // Re-added for UiIcons/Colors as they are used in expanded logic
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -136,20 +137,18 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
}
if (state is ShiftActionSuccess) {
_isApplying = false;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: UiColors.success,
),
UiSnackbar.show(
context,
message: state.message,
type: UiSnackbarType.success,
);
Modular.to.toShifts(selectedDate: state.shiftDate);
} else if (state is ShiftDetailsError) {
if (_isApplying || widget.shift == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(state.message),
backgroundColor: UiColors.destructive,
),
UiSnackbar.show(
context,
message: translateErrorKey(state.message),
type: UiSnackbarType.error,
);
}
_isApplying = false;
@@ -170,9 +169,10 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
displayShift = widget.shift;
}
final i18n = Translations.of(context).staff_shifts.shift_details;
if (displayShift == null) {
return const Scaffold(
body: Center(child: Text("Shift not found")),
return Scaffold(
body: Center(child: Text(Translations.of(context).staff_shifts.list.no_shifts)),
);
}
@@ -202,7 +202,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"VENDOR",
i18n.vendor,
style: UiTypography.titleUppercase4b.textSecondary,
),
const SizedBox(height: UiConstants.space2),
@@ -245,7 +245,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"SHIFT DATE",
i18n.shift_date,
style: UiTypography.titleUppercase4b.textSecondary,
),
const SizedBox(height: UiConstants.space2),
@@ -284,7 +284,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
),
const SizedBox(width: UiConstants.space2),
Text(
"$openSlots slots remaining",
i18n.slots_remaining(count: openSlots),
style: UiTypography.footnote1m.textSuccess,
),
],
@@ -298,14 +298,14 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
children: [
Expanded(
child: _buildTimeBox(
"START TIME",
i18n.start_time,
displayShift.startTime,
),
),
const SizedBox(width: UiConstants.space4),
Expanded(
child: _buildTimeBox(
"END TIME",
i18n.end_time,
displayShift.endTime,
),
),
@@ -320,15 +320,15 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
child: _buildStatCard(
UiIcons.dollar,
"\$${displayShift.hourlyRate.toStringAsFixed(0)}/hr",
"Base Rate",
i18n.base_rate,
),
),
const SizedBox(width: UiConstants.space4),
Expanded(
child: _buildStatCard(
UiIcons.clock,
"${duration.toInt()} hours",
"Duration",
i18n.hours_label(count: duration.toInt()),
i18n.duration,
),
),
const SizedBox(width: UiConstants.space4),
@@ -336,7 +336,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
child: _buildStatCard(
UiIcons.wallet,
"\$${estimatedTotal.toStringAsFixed(0)}",
"Est. Total",
i18n.est_total,
),
),
],
@@ -348,7 +348,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"LOCATION",
i18n.location,
style: UiTypography.titleUppercase4b.textSecondary,
),
const SizedBox(height: UiConstants.space3),
@@ -396,7 +396,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
UiIcons.arrowRight,
size: 16,
),
label: const Text("Open in Maps"),
label: Text(i18n.open_in_maps),
style: TextButton.styleFrom(
foregroundColor: UiColors.primary,
padding: EdgeInsets.zero,
@@ -412,7 +412,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
// Description / Instructions
if ((displayShift.description ?? '').isNotEmpty) ...[
Text(
"JOB DESCRIPTION",
i18n.job_description,
style: UiTypography.titleUppercase4b.textSecondary,
),
const SizedBox(height: UiConstants.space2),
@@ -460,15 +460,16 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
BuildContext context,
Shift shift,
) {
final i18n = Translations.of(context).staff_shifts.shift_details.book_dialog;
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Book Shift'),
content: const Text('Do you want to instantly book this shift?'),
title: Text(i18n.title),
content: Text(i18n.message),
actions: [
TextButton(
onPressed: () => Modular.to.pop(),
child: const Text('Cancel'),
child: Text(Translations.of(context).common.cancel),
),
TextButton(
onPressed: () {
@@ -485,7 +486,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
style: TextButton.styleFrom(
foregroundColor: UiColors.success,
),
child: const Text('Book'),
child: Text(Translations.of(context).staff_shifts.shift_details.apply_now),
),
],
),
@@ -493,17 +494,16 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
}
void _declineShift(BuildContext context, String id) {
final i18n = Translations.of(context).staff_shifts.shift_details.decline_dialog;
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Decline Shift'),
content: const Text(
'Are you sure you want to decline this shift? It will be hidden from your available jobs.',
),
title: Text(i18n.title),
content: Text(i18n.message),
actions: [
TextButton(
onPressed: () => Modular.to.pop(),
child: const Text('Cancel'),
child: Text(Translations.of(context).common.cancel),
),
TextButton(
onPressed: () {
@@ -514,7 +514,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
style: TextButton.styleFrom(
foregroundColor: UiColors.destructive,
),
child: const Text('Decline'),
child: Text(Translations.of(context).staff_shifts.shift_details.decline),
),
],
),
@@ -525,12 +525,13 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
if (_actionDialogOpen) return;
_actionDialogOpen = true;
_isApplying = true;
final i18n = Translations.of(context).staff_shifts.shift_details.applying_dialog;
showDialog(
context: context,
useRootNavigator: true,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
title: const Text('Applying'),
title: Text(i18n.title),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -576,6 +577,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
Widget _buildBottomButton(Shift shift, BuildContext context) {
final String status = shift.status ?? 'open';
final i18n = Translations.of(context).staff_shifts.shift_details;
if (status == 'confirmed') {
return Row(
children: [
@@ -591,7 +593,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
),
elevation: 0,
),
child: Text("CANCEL SHIFT", style: UiTypography.body2b.white),
child: Text(i18n.cancel_shift, style: UiTypography.body2b.white),
),
),
const SizedBox(width: UiConstants.space4),
@@ -607,7 +609,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
),
elevation: 0,
),
child: Text("CLOCK IN", style: UiTypography.body2b.white),
child: Text(i18n.clock_in, style: UiTypography.body2b.white),
),
),
],
@@ -628,7 +630,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
),
child: Text("DECLINE", style: UiTypography.body2b.textError),
child: Text(i18n.decline, style: UiTypography.body2b.textError),
),
),
const SizedBox(width: UiConstants.space4),
@@ -644,7 +646,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
),
elevation: 0,
),
child: Text("ACCEPT SHIFT", style: UiTypography.body2b.white),
child: Text(i18n.accept_shift, style: UiTypography.body2b.white),
),
),
],
@@ -665,7 +667,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
),
),
child: Text("DECLINE", style: UiTypography.body2b.textSecondary),
child: Text(i18n.decline, style: UiTypography.body2b.textSecondary),
),
),
const SizedBox(width: UiConstants.space4),
@@ -681,7 +683,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
),
elevation: 0,
),
child: Text("APPLY NOW", style: UiTypography.body2b.white),
child: Text(i18n.apply_now, style: UiTypography.body2b.white),
),
),
],
@@ -692,15 +694,16 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
}
void _openCancelDialog(BuildContext context) {
final i18n = Translations.of(context).staff_shifts.shift_details.cancel_dialog;
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Cancel Shift'),
content: const Text('Are you sure you want to cancel this shift?'),
title: Text(i18n.title),
content: Text(i18n.message),
actions: [
TextButton(
onPressed: () => Modular.to.pop(),
child: const Text('No'),
child: Text(Translations.of(context).common.cancel),
),
TextButton(
onPressed: () {
@@ -712,7 +715,7 @@ class _ShiftDetailsPageState extends State<ShiftDetailsPage> {
style: TextButton.styleFrom(
foregroundColor: UiColors.destructive,
),
child: const Text('Yes, cancel it'),
child: Text(Translations.of(context).common.ok),
),
],
),

View File

@@ -72,11 +72,10 @@ class _ShiftsPageState extends State<ShiftsPage> {
child: BlocConsumer<ShiftsBloc, ShiftsState>(
listener: (context, state) {
if (state is ShiftsError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(translateErrorKey(state.message)),
behavior: SnackBarBehavior.floating,
),
UiSnackbar.show(
context,
message: translateErrorKey(state.message),
type: UiSnackbarType.error,
);
}
},

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:design_system/design_system.dart';
import 'package:core_localization/core_localization.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../blocs/shifts/shifts_bloc.dart';
import '../my_shift_card.dart';
@@ -115,11 +116,10 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
onPressed: () {
Navigator.of(context).pop();
context.read<ShiftsBloc>().add(AcceptShiftEvent(id));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Shift confirmed!'),
backgroundColor: UiColors.success,
),
UiSnackbar.show(
context,
message: 'Shift confirmed!',
type: UiSnackbarType.success,
);
},
style: TextButton.styleFrom(
@@ -149,11 +149,10 @@ class _MyShiftsTabState extends State<MyShiftsTab> {
onPressed: () {
Navigator.of(context).pop();
context.read<ShiftsBloc>().add(DeclineShiftEvent(id));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Shift declined.'),
backgroundColor: UiColors.destructive,
),
UiSnackbar.show(
context,
message: 'Shift declined.',
type: UiSnackbarType.error,
);
},
style: TextButton.styleFrom(