feat: Centralized Error Handling & Crash Fixes
This commit is contained in:
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user