auto-claude: subtask-4-2 - Update routes_page.dart to use new dialog components

- Replaced inline loading dialogs with LoadingDialog component
- Replaced inline notes dialog with NotesDialog component
- Replaced inline photo confirmation dialog with PhotoCaptureDialog component
- Added missing localization strings for completingDelivery, markingAsUncompleted, and deliveryMarkedUncompleted
- Fixed BuildContext usage across async gaps by capturing l10n early
- Fixed unused result warnings by using ref.invalidate instead of ref.refresh
- Removed unnecessary non-null assertions

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mathias Beaulieu-Duncan 2026-01-20 11:45:39 -05:00
parent c8c2ec0921
commit 697b724f02
6 changed files with 84 additions and 177 deletions

View File

@ -146,5 +146,8 @@
} }
}, },
"serverError": "Server error - Please contact support", "serverError": "Server error - Please contact support",
"retake": "Retake" "retake": "Retake",
"completingDelivery": "Completing delivery...",
"markingAsUncompleted": "Marking as uncompleted...",
"deliveryMarkedUncompleted": "Delivery marked as uncompleted"
} }

View File

@ -146,5 +146,8 @@
} }
}, },
"serverError": "Erreur serveur - Veuillez contacter le support", "serverError": "Erreur serveur - Veuillez contacter le support",
"retake": "Reprendre" "retake": "Reprendre",
"completingDelivery": "Completion de la livraison...",
"markingAsUncompleted": "Marquage comme a livrer...",
"deliveryMarkedUncompleted": "Livraison marquee comme a livrer"
} }

View File

@ -631,6 +631,24 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Retake'** /// **'Retake'**
String get retake; String get retake;
/// No description provided for @completingDelivery.
///
/// In en, this message translates to:
/// **'Completing delivery...'**
String get completingDelivery;
/// No description provided for @markingAsUncompleted.
///
/// In en, this message translates to:
/// **'Marking as uncompleted...'**
String get markingAsUncompleted;
/// No description provided for @deliveryMarkedUncompleted.
///
/// In en, this message translates to:
/// **'Delivery marked as uncompleted'**
String get deliveryMarkedUncompleted;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View File

@ -302,4 +302,13 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get retake => 'Retake'; String get retake => 'Retake';
@override
String get completingDelivery => 'Completing delivery...';
@override
String get markingAsUncompleted => 'Marking as uncompleted...';
@override
String get deliveryMarkedUncompleted => 'Delivery marked as uncompleted';
} }

View File

@ -302,4 +302,13 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get retake => 'Reprendre'; String get retake => 'Reprendre';
@override
String get completingDelivery => 'Completion de la livraison...';
@override
String get markingAsUncompleted => 'Marquage comme a livrer...';
@override
String get deliveryMarkedUncompleted => 'Livraison marquee comme a livrer';
} }

View File

@ -11,10 +11,12 @@ import '../providers/providers.dart';
import '../api/client.dart'; import '../api/client.dart';
import '../utils/toast_helper.dart'; import '../utils/toast_helper.dart';
import '../api/openapi_config.dart'; import '../api/openapi_config.dart';
import '../utils/breakpoints.dart';
import '../utils/http_client_factory.dart'; import '../utils/http_client_factory.dart';
import '../components/collapsible_routes_sidebar.dart'; import '../components/collapsible_routes_sidebar.dart';
import '../components/dark_mode_map.dart'; import '../components/dark_mode_map.dart';
import '../components/loading_dialog.dart';
import '../components/notes_dialog.dart';
import '../components/photo_capture_dialog.dart';
import '../services/location_permission_service.dart'; import '../services/location_permission_service.dart';
import 'deliveries_page.dart'; import 'deliveries_page.dart';
import 'settings_page.dart'; import 'settings_page.dart';
@ -81,13 +83,14 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
Delivery delivery, Delivery delivery,
int routeFragmentId, int routeFragmentId,
) async { ) async {
// Capture l10n before async operations to avoid BuildContext across async gaps
final l10n = AppLocalizations.of(context);
final authService = ref.read(authServiceProvider); final authService = ref.read(authServiceProvider);
// Ensure we have a valid token (automatically refreshes if needed) // Ensure we have a valid token (automatically refreshes if needed)
final token = await authService.ensureValidToken(); final token = await authService.ensureValidToken();
if (token == null) { if (token == null) {
if (mounted) { if (mounted) {
final l10n = AppLocalizations.of(context)!;
ToastHelper.showError(context, l10n.authenticationRequired); ToastHelper.showError(context, l10n.authenticationRequired);
} }
return; return;
@ -102,27 +105,7 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
switch (action) { switch (action) {
case 'complete': case 'complete':
if (mounted) { if (mounted) {
showDialog( LoadingDialog.show(context, message: l10n.completingDelivery);
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return const Center(
child: Card(
child: Padding(
padding: EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Completing delivery...'),
],
),
),
),
);
},
);
} }
final result = await authClient.executeCommand( final result = await authClient.executeCommand(
@ -134,7 +117,7 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
result.when( result.when(
success: (_) async { success: (_) async {
if (mounted) { if (mounted) {
Navigator.of(context).pop(); LoadingDialog.hide(context);
} }
if (mounted) { if (mounted) {
@ -176,23 +159,21 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
} }
if (mounted) { if (mounted) {
final l10n = AppLocalizations.of(context)!;
ToastHelper.showSuccess(context, l10n.deliverySuccessful); ToastHelper.showSuccess(context, l10n.deliverySuccessful);
} }
} }
}, },
onError: (error) { onError: (error) {
if (mounted) { if (mounted) {
Navigator.of(context).pop(); LoadingDialog.hide(context);
} }
debugPrint('Complete delivery failed - Type: ${error.type}, Message: ${error.message}'); debugPrint('Complete delivery failed - Type: ${error.type}, Message: ${error.message}');
debugPrint('Error details: ${error.details}'); debugPrint('Error details: ${error.details}');
if (mounted) { if (mounted) {
final l10n = AppLocalizations.of(context)!;
String errorMessage = l10n.error(error.message); String errorMessage = l10n.error(error.message);
if (error.statusCode == 500) { if (error.statusCode == 500) {
errorMessage = 'Server error - Please contact support'; errorMessage = l10n.serverError;
} }
ToastHelper.showError(context, errorMessage); ToastHelper.showError(context, errorMessage);
} }
@ -202,37 +183,17 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
case 'uncomplete': case 'uncomplete':
if (mounted) { if (mounted) {
showDialog( LoadingDialog.show(context, message: l10n.markingAsUncompleted);
context: context,
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return const Center(
child: Card(
child: Padding(
padding: EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Marking as uncompleted...'),
],
),
),
),
);
},
);
} }
final result = await authClient.executeCommand( final uncompleteResult = await authClient.executeCommand(
endpoint: 'markDeliveryAsUncompleted', endpoint: 'markDeliveryAsUncompleted',
command: MarkDeliveryAsUncompletedCommand(deliveryId: delivery.id), command: MarkDeliveryAsUncompletedCommand(deliveryId: delivery.id),
); );
result.when( uncompleteResult.when(
success: (_) async { success: (_) async {
if (mounted) { if (mounted) {
Navigator.of(context).pop(); LoadingDialog.hide(context);
} }
if (mounted) { if (mounted) {
@ -257,18 +218,16 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
} }
if (mounted) { if (mounted) {
final l10n = AppLocalizations.of(context)!; ToastHelper.showSuccess(context, l10n.deliveryMarkedUncompleted);
ToastHelper.showSuccess(context, 'Delivery marked as uncompleted');
} }
} }
}, },
onError: (error) { onError: (error) {
if (mounted) { if (mounted) {
Navigator.of(context).pop(); LoadingDialog.hide(context);
} }
if (mounted) { if (mounted) {
final l10n = AppLocalizations.of(context)!;
ToastHelper.showError(context, l10n.error(error.message)); ToastHelper.showError(context, l10n.error(error.message));
} }
}, },
@ -289,12 +248,12 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
Delivery delivery, Delivery delivery,
) async { ) async {
final authService = ref.read(authServiceProvider); final authService = ref.read(authServiceProvider);
final l10n = AppLocalizations.of(context);
// Ensure we have a valid token (automatically refreshes if needed) // Ensure we have a valid token (automatically refreshes if needed)
final token = await authService.ensureValidToken(); final token = await authService.ensureValidToken();
if (token == null) { if (token == null) {
if (mounted) { if (mounted) {
final l10n = AppLocalizations.of(context)!;
ToastHelper.showError(context, l10n.authenticationRequired); ToastHelper.showError(context, l10n.authenticationRequired);
} }
return; return;
@ -309,7 +268,7 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
); );
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
ToastHelper.showError(context, 'Camera error: $e'); ToastHelper.showError(context, l10n.cameraError(e.toString()));
} }
return; return;
} }
@ -320,45 +279,11 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
if (!mounted) return; if (!mounted) return;
final bool? confirmed = await showDialog<bool>( // Show photo confirmation dialog
context: context, final bool? confirmed = await PhotoCaptureDialog.show(
builder: (BuildContext dialogContext) { context,
return AlertDialog( imageFile: File(pickedFile.path),
title: const Text('Confirm Photo'), deliveryName: delivery.name,
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(dialogContext).size.height * 0.5,
maxWidth: MediaQuery.of(dialogContext).size.width * 0.8,
),
child: Image.file(
File(pickedFile!.path),
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
Text(
'Upload this photo for ${delivery.name}?',
textAlign: TextAlign.center,
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(false),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () => Navigator.of(dialogContext).pop(true),
child: const Text('Upload'),
),
],
);
},
); );
if (confirmed != true) { if (confirmed != true) {
@ -367,27 +292,8 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
if (!mounted) return; if (!mounted) return;
showDialog( // Show uploading dialog
context: context, LoadingDialog.show(context, message: l10n.uploadingPhoto);
barrierDismissible: false,
builder: (BuildContext dialogContext) {
return const Center(
child: Card(
child: Padding(
padding: EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Uploading photo...'),
],
),
),
),
);
},
);
try { try {
final Uri uploadUrl = Uri.parse( final Uri uploadUrl = Uri.parse(
@ -408,33 +314,31 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
client.close(); client.close();
if (mounted) { if (mounted) {
Navigator.of(context).pop(); LoadingDialog.hide(context);
} }
if (response.statusCode >= 200 && response.statusCode < 300) { if (response.statusCode >= 200 && response.statusCode < 300) {
if (mounted) { if (mounted) {
ToastHelper.showSuccess(context, 'Photo uploaded successfully'); ToastHelper.showSuccess(context, l10n.photoUploadSuccess);
} }
ref.refresh(allDeliveriesProvider); ref.invalidate(allDeliveriesProvider);
} else { } else {
debugPrint('Photo upload failed - Status: ${response.statusCode}'); debugPrint('Photo upload failed - Status: ${response.statusCode}');
debugPrint('Response body: ${response.body}'); debugPrint('Response body: ${response.body}');
if (mounted) { if (mounted) {
String errorMessage = 'Upload failed'; String errorMessage = l10n.photoUploadFailed(response.statusCode);
if (response.statusCode == 500) { if (response.statusCode == 500) {
errorMessage = 'Server error - Please contact support'; errorMessage = l10n.serverError;
} else if (response.statusCode == 401) { } else if (response.statusCode == 401) {
errorMessage = 'Authentication required - Please log in again'; errorMessage = l10n.authenticationRequired;
} else {
errorMessage = 'Upload failed: ${response.statusCode}';
} }
ToastHelper.showError(context, errorMessage); ToastHelper.showError(context, errorMessage);
} }
} }
} catch (e) { } catch (e) {
if (mounted) { if (mounted) {
Navigator.of(context).pop(); LoadingDialog.hide(context);
ToastHelper.showError(context, 'Upload error: $e'); ToastHelper.showError(context, l10n.uploadError(e.toString()));
} }
} }
} }
@ -462,53 +366,14 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
} }
Future<void> _showNotesDialog(Delivery delivery) async { Future<void> _showNotesDialog(Delivery delivery) async {
final notes = delivery.orders
.where((order) => order.note != null && order.note!.isNotEmpty)
.map((order) => order.note!)
.toList();
if (!mounted) return; if (!mounted) return;
if (notes.isEmpty) { final l10n = AppLocalizations.of(context);
ToastHelper.showInfo(context, 'No notes attached to this delivery'); final hasNotes = await NotesDialog.show(context, delivery);
return;
}
await showDialog( if (!hasNotes && mounted) {
context: context, ToastHelper.showInfo(context, l10n.noNotesMessage);
builder: (BuildContext dialogContext) { }
return AlertDialog(
title: Text('Notes for ${delivery.name}'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: notes.map((note) => Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Text(
note,
style: Theme.of(dialogContext).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
)).toList(),
),
),
actionsAlignment: MainAxisAlignment.center,
actionsPadding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
actions: [
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('Close'),
),
),
],
);
},
);
} }
@override @override
@ -516,7 +381,7 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
final routesData = ref.watch(deliveryRoutesProvider); final routesData = ref.watch(deliveryRoutesProvider);
final allDeliveriesData = ref.watch(allDeliveriesProvider); final allDeliveriesData = ref.watch(allDeliveriesProvider);
final userProfile = ref.watch(userProfileProvider); final userProfile = ref.watch(userProfileProvider);
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -535,8 +400,8 @@ class _RoutesPageState extends ConsumerState<RoutesPage> {
onPressed: (routesData.isLoading || allDeliveriesData.isLoading) onPressed: (routesData.isLoading || allDeliveriesData.isLoading)
? null ? null
: () { : () {
ref.refresh(deliveryRoutesProvider); ref.invalidate(deliveryRoutesProvider);
ref.refresh(allDeliveriesProvider); ref.invalidate(allDeliveriesProvider);
}, },
tooltip: 'Refresh', tooltip: 'Refresh',
), ),