From 7f281ed91bdddec12500061e2e254f4a7aace624 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:22:25 -0500 Subject: [PATCH 01/12] auto-claude: subtask-1-1 - Update pubspec.yaml with safe patch and minor pack Updated the following packages to their latest safe versions: - go_router: ^17.0.0 -> ^17.0.1 (patch) - shared_preferences: ^2.5.3 -> ^2.5.4 (patch) - build_runner: ^2.4.14 -> ^2.10.5 (minor) - json_serializable: ^6.9.2 -> ^6.11.1 (minor, capped due to analyzer conflict) - google_navigation_flutter: ^0.7.0 -> ^0.8.2 (minor) Note: json_serializable was capped at 6.11.1 instead of 6.11.4 due to analyzer version conflict with riverpod_generator ^3.0.3. Co-Authored-By: Claude --- pubspec.lock | 16 ++++++++-------- pubspec.yaml | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index ee1feae..ab5892a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -101,10 +101,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "7b5b569f3df370590a85029148d6fc66c7d0201fc6f1847c07dd85d365ae9fcd" + sha256: b4d854962a32fd9f8efc0b76f98214790b833af8b2e9b2df6bfc927c0415a072 url: "https://pub.dev" source: hosted - version: "2.10.3" + version: "2.10.5" built_collection: dependency: transitive description: @@ -457,18 +457,18 @@ packages: dependency: "direct main" description: name: go_router - sha256: c92d18e1fe994cb06d48aa786c46b142a5633067e8297cff6b5a3ac742620104 + sha256: eff94d2a6fc79fa8b811dde79c7549808c2346037ee107a1121b4a644c745f2a url: "https://pub.dev" source: hosted - version: "17.0.0" + version: "17.0.1" google_navigation_flutter: dependency: "direct main" description: name: google_navigation_flutter - sha256: fdf79ddeda8bbba9d8b9218c41551b918447032004cbe72ea7365287c9d9bf80 + sha256: "12f8bc6f5cf694b0778772a2e22969c9a2da44e76b496c47488deb482a2fe063" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.8.2" graphs: dependency: transitive description: @@ -921,10 +921,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_android: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5833e7d..42057b9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,10 +37,10 @@ dependencies: url_launcher: ^6.3.1 permission_handler: ^12.0.1 - go_router: ^17.0.0 - shared_preferences: ^2.5.3 + go_router: ^17.0.1 + shared_preferences: ^2.5.4 http_interceptor: ^2.0.0 - google_navigation_flutter: ^0.7.0 + google_navigation_flutter: ^0.8.2 dev_dependencies: flutter_test: @@ -48,8 +48,8 @@ dev_dependencies: flutter_lints: ^6.0.0 - build_runner: ^2.4.14 - json_serializable: ^6.9.2 + build_runner: ^2.10.5 + json_serializable: ^6.11.1 riverpod_generator: ^3.0.3 # For information on the generic Dart part of this file, see the From d2075d83b5bd62f56bf8b19bb24e877c424c4aad Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:23:27 -0500 Subject: [PATCH 02/12] auto-claude: subtask-1-2 - Update flutter_riverpod to 3.1.0 Updated flutter_riverpod dependency from ^3.0.3 to ^3.1.0. This is a minor version update with backward-compatible changes. Co-Authored-By: Claude --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 42057b9..ced4842 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: cupertino_icons: ^1.0.8 - flutter_riverpod: ^3.0.3 + flutter_riverpod: ^3.1.0 riverpod_annotation: ^3.0.3 animate_do: ^4.2.0 From 61dee9e51fd0abfcd2b87673af3c4bfa008b1f3d Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:25:14 -0500 Subject: [PATCH 03/12] auto-claude: subtask-1-3 - Run build_runner to regenerate code after package updates Updated riverpod_annotation from ^3.0.3 to ^4.0.0 and riverpod_generator from ^3.0.3 to ^4.0.0 to resolve version conflicts with flutter_riverpod ^3.1.0. Build runner executed successfully. Co-Authored-By: Claude --- pubspec.lock | 52 ++++++++++------------------------------------------ pubspec.yaml | 4 ++-- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index ab5892a..5dcac20 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,14 +25,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.11" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - sha256: dd574a0ab77de88b7d9c12bc4b626109a5ca9078216a79041a5c24c3a1bd103c - url: "https://pub.dev" - source: hosted - version: "0.13.7" animate_do: dependency: "direct main" description: @@ -209,22 +201,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - custom_lint_core: - dependency: transitive - description: - name: custom_lint_core - sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423" - url: "https://pub.dev" - source: hosted - version: "0.8.1" - custom_lint_visitor: - dependency: transitive - description: - name: custom_lint_visitor - sha256: "446d68322747ec1c36797090de776aa72228818d3d80685a91ff524d163fee6d" - url: "https://pub.dev" - source: hosted - version: "1.0.0+8.1.1" dart_style: dependency: transitive description: @@ -351,10 +327,10 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "9e2d6907f12cc7d23a846847615941bddee8709bf2bfd274acdf5e80bcf22fde" + sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.1.0" flutter_secure_storage: dependency: "direct main" description: @@ -889,34 +865,34 @@ packages: dependency: transitive description: name: riverpod - sha256: c406de02bff19d920b832bddfb8283548bfa05ce41c59afba57ce643e116aa59 + sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.1.0" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: a0f68adb078b790faa3c655110a017f9a7b7b079a57bbd40f540e80dce5fcd29 + sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0" url: "https://pub.dev" source: hosted - version: "1.0.0-dev.7" + version: "1.0.0-dev.8" riverpod_annotation: dependency: "direct main" description: name: riverpod_annotation - sha256: "7230014155777fc31ba3351bc2cb5a3b5717b11bfafe52b1553cb47d385f8897" + sha256: cc1474bc2df55ec3c1da1989d139dcef22cd5e2bd78da382e867a69a8eca2e46 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "4.0.0" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: "49894543a42cf7a9954fc4e7366b6d3cb2e6ec0fa07775f660afcdd92d097702" + sha256: e43b1537229cc8f487f09b0c20d15dba840acbadcf5fc6dad7ad5e8ab75950dc url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "4.0.0+1" shared_preferences: dependency: "direct main" description: @@ -1194,14 +1170,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.4" - uuid: - dependency: transitive - description: - name: uuid - sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 - url: "https://pub.dev" - source: hosted - version: "4.5.2" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index ced4842..5abf42a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: cupertino_icons: ^1.0.8 flutter_riverpod: ^3.1.0 - riverpod_annotation: ^3.0.3 + riverpod_annotation: ^4.0.0 animate_do: ^4.2.0 lottie: ^3.0.0 @@ -50,7 +50,7 @@ dev_dependencies: build_runner: ^2.10.5 json_serializable: ^6.11.1 - riverpod_generator: ^3.0.3 + riverpod_generator: ^4.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From f3a05099ab17d383acd6cd9878a761c86c007fe4 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:26:44 -0500 Subject: [PATCH 04/12] auto-claude: subtask-2-1 - Create LoadingDialog component with static show() --- lib/components/loading_dialog.dart | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 lib/components/loading_dialog.dart diff --git a/lib/components/loading_dialog.dart b/lib/components/loading_dialog.dart new file mode 100644 index 0000000..c1e7d94 --- /dev/null +++ b/lib/components/loading_dialog.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; + +/// A reusable loading dialog component with a spinner and message. +/// +/// Use the static [show] method to display the dialog and [hide] to dismiss it. +/// +/// Example: +/// ```dart +/// // Show loading dialog +/// LoadingDialog.show(context, message: 'Loading...'); +/// +/// // Perform async operation +/// await someAsyncOperation(); +/// +/// // Hide loading dialog +/// LoadingDialog.hide(context); +/// ``` +class LoadingDialog extends StatelessWidget { + final String message; + + const LoadingDialog({ + super.key, + required this.message, + }); + + /// Shows a loading dialog with the specified [message]. + /// + /// The dialog is non-dismissible by tapping outside. + /// Use [hide] to dismiss the dialog when the operation completes. + static Future show( + BuildContext context, { + required String message, + }) { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext dialogContext) { + return LoadingDialog(message: message); + }, + ); + } + + /// Hides the currently displayed loading dialog. + /// + /// Should be called after showing a dialog with [show]. + static void hide(BuildContext context) { + Navigator.of(context).pop(); + } + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + + return Center( + child: Card( + elevation: 4, + color: colorScheme.surface, + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator( + color: colorScheme.primary, + ), + const SizedBox(height: 16), + Text( + message, + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurface, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ); + } +} From bcc938fde173cab4ef488757fab0e0ab54811a05 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:28:31 -0500 Subject: [PATCH 05/12] auto-claude: subtask-2-2 - Create NotesDialog component for displaying delivery notes - Add reusable NotesDialog component that extracts and displays notes from delivery orders - Add static show() method for convenient dialog display with empty notes handling - Add localization strings for notes dialog (EN/FR): notesTitle, noNotesMessage, close - Follow existing dialog pattern from NavigationTermsAndConditionsDialog Co-Authored-By: Claude --- lib/components/notes_dialog.dart | 104 +++++++++++++++++++++++++++++ lib/l10n/app_en.arb | 10 ++- lib/l10n/app_fr.arb | 10 ++- lib/l10n/app_localizations.dart | 18 +++++ lib/l10n/app_localizations_en.dart | 11 +++ lib/l10n/app_localizations_fr.dart | 11 +++ 6 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 lib/components/notes_dialog.dart diff --git a/lib/components/notes_dialog.dart b/lib/components/notes_dialog.dart new file mode 100644 index 0000000..f603293 --- /dev/null +++ b/lib/components/notes_dialog.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:planb_logistic/l10n/app_localizations.dart'; +import '../models/delivery.dart'; + +/// A dialog component for displaying delivery notes. +/// +/// This dialog extracts and displays all non-empty notes from the +/// orders associated with a delivery. +class NotesDialog extends StatelessWidget { + /// The delivery whose notes should be displayed. + final Delivery delivery; + + const NotesDialog({ + super.key, + required this.delivery, + }); + + /// Extracts non-empty notes from the delivery's orders. + List _extractNotes() { + return delivery.orders + .where((order) => order.note != null && order.note!.isNotEmpty) + .map((order) => order.note!) + .toList(); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + final colorScheme = Theme.of(context).colorScheme; + final notes = _extractNotes(); + + return AlertDialog( + title: Text( + l10n.notesTitle(delivery.name), + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: colorScheme.onSurface, + ), + ), + 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(context).textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurface, + 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(context).pop(), + child: Text( + l10n.close, + style: TextStyle(color: colorScheme.onPrimary), + ), + ), + ), + ], + ); + } + + /// Shows the notes dialog for a delivery. + /// + /// Returns `true` if the dialog was shown (i.e., the delivery has notes), + /// `false` otherwise. + /// + /// If the delivery has no notes, this method returns `false` without + /// showing the dialog. The caller is responsible for handling this case, + /// typically by showing an info message. + static Future show(BuildContext context, Delivery delivery) async { + final notes = delivery.orders + .where((order) => order.note != null && order.note!.isNotEmpty) + .map((order) => order.note!) + .toList(); + + if (notes.isEmpty) { + return false; + } + + await showDialog( + context: context, + builder: (BuildContext dialogContext) { + return NotesDialog(delivery: delivery); + }, + ); + + return true; + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d63426d..94bbd3b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -109,5 +109,13 @@ "passwordRequired": "Password is required", "loginButton": "Login", "navigate": "Navigate", - "upload": "Upload" + "upload": "Upload", + "notesTitle": "Notes for {name}", + "@notesTitle": { + "placeholders": { + "name": {"type": "String"} + } + }, + "noNotesMessage": "No notes attached to this delivery", + "close": "Close" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 2bab17f..b6ea8c6 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -109,5 +109,13 @@ "passwordRequired": "Le mot de passe est requis", "loginButton": "Connexion", "navigate": "Naviguer", - "upload": "Téléverser" + "upload": "Téléverser", + "notesTitle": "Notes pour {name}", + "@notesTitle": { + "placeholders": { + "name": {"type": "String"} + } + }, + "noNotesMessage": "Aucune note associée à cette livraison", + "close": "Fermer" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 8c9de9c..1ffde8f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -559,6 +559,24 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Upload'** String get upload; + + /// No description provided for @notesTitle. + /// + /// In en, this message translates to: + /// **'Notes for {name}'** + String notesTitle(String name); + + /// No description provided for @noNotesMessage. + /// + /// In en, this message translates to: + /// **'No notes attached to this delivery'** + String get noNotesMessage; + + /// No description provided for @close. + /// + /// In en, this message translates to: + /// **'Close'** + String get close; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0399f33..a9d982b 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -256,4 +256,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get upload => 'Upload'; + + @override + String notesTitle(String name) { + return 'Notes for $name'; + } + + @override + String get noNotesMessage => 'No notes attached to this delivery'; + + @override + String get close => 'Close'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 6ebea89..854f1b3 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -256,4 +256,15 @@ class AppLocalizationsFr extends AppLocalizations { @override String get upload => 'Téléverser'; + + @override + String notesTitle(String name) { + return 'Notes pour $name'; + } + + @override + String get noNotesMessage => 'Aucune note associée à cette livraison'; + + @override + String get close => 'Fermer'; } From e5f267b4f7ab008e13cdc76c0c613a1a3987f513 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:30:49 -0500 Subject: [PATCH 06/12] auto-claude: subtask-2-3 - Create PhotoCaptureDialog component for photo confirmation Add PhotoCaptureDialog widget component that: - Shows captured photo preview with proper constraints - Displays confirmation message using delivery name - Provides Cancel and Upload action buttons - Uses theme-aware styling with colorScheme - Handles image loading errors gracefully - Includes proper i18n support (EN/FR) Added localization keys: - confirmPhoto - uploadPhotoConfirmation (with name placeholder) - uploadingPhoto - photoUploadSuccess - photoUploadFailed - cameraError - uploadError - serverError - retake Co-Authored-By: Claude --- lib/components/photo_capture_dialog.dart | 133 +++++++++++++++++++++++ lib/l10n/app_en.arb | 31 +++++- lib/l10n/app_fr.arb | 31 +++++- lib/l10n/app_localizations.dart | 54 +++++++++ lib/l10n/app_localizations_en.dart | 35 ++++++ lib/l10n/app_localizations_fr.dart | 35 ++++++ 6 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 lib/components/photo_capture_dialog.dart diff --git a/lib/components/photo_capture_dialog.dart b/lib/components/photo_capture_dialog.dart new file mode 100644 index 0000000..a867820 --- /dev/null +++ b/lib/components/photo_capture_dialog.dart @@ -0,0 +1,133 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:planb_logistic/l10n/app_localizations.dart'; + +/// A dialog component for confirming and displaying captured photos before upload. +/// +/// This dialog shows a preview of the captured photo and prompts the user +/// to confirm the upload or cancel/retake. +/// +/// Example: +/// ```dart +/// final confirmed = await PhotoCaptureDialog.show( +/// context, +/// imageFile: File(pickedFile.path), +/// deliveryName: delivery.name, +/// ); +/// +/// if (confirmed == true) { +/// // Proceed with upload +/// } +/// ``` +class PhotoCaptureDialog extends StatelessWidget { + /// The captured image file to display. + final File imageFile; + + /// The name of the delivery for the confirmation message. + final String deliveryName; + + const PhotoCaptureDialog({ + super.key, + required this.imageFile, + required this.deliveryName, + }); + + /// Shows the photo capture confirmation dialog. + /// + /// Returns `true` if the user confirms the upload, `false` if cancelled. + /// Returns `null` if the dialog is dismissed without selection. + static Future show( + BuildContext context, { + required File imageFile, + required String deliveryName, + }) { + return showDialog( + context: context, + builder: (BuildContext dialogContext) { + return PhotoCaptureDialog( + imageFile: imageFile, + deliveryName: deliveryName, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + final colorScheme = Theme.of(context).colorScheme; + final textTheme = Theme.of(context).textTheme; + final screenSize = MediaQuery.of(context).size; + + return AlertDialog( + title: Text( + l10n.confirmPhoto, + style: textTheme.headlineSmall?.copyWith( + color: colorScheme.onSurface, + ), + ), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: screenSize.height * 0.5, + maxWidth: screenSize.width * 0.8, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.file( + imageFile, + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + return Container( + height: 200, + width: double.infinity, + decoration: BoxDecoration( + color: colorScheme.errorContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Center( + child: Icon( + Icons.broken_image, + size: 48, + color: colorScheme.error, + ), + ), + ); + }, + ), + ), + ), + const SizedBox(height: 16), + Text( + l10n.uploadPhotoConfirmation(deliveryName), + style: textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurface, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text( + l10n.cancel, + style: TextStyle(color: colorScheme.error), + ), + ), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(true), + style: ElevatedButton.styleFrom( + backgroundColor: colorScheme.primary, + foregroundColor: colorScheme.onPrimary, + ), + child: Text(l10n.upload), + ), + ], + ); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 94bbd3b..b132f88 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -117,5 +117,34 @@ } }, "noNotesMessage": "No notes attached to this delivery", - "close": "Close" + "close": "Close", + "confirmPhoto": "Confirm Photo", + "uploadPhotoConfirmation": "Upload this photo for {name}?", + "@uploadPhotoConfirmation": { + "placeholders": { + "name": {"type": "String"} + } + }, + "uploadingPhoto": "Uploading photo...", + "photoUploadSuccess": "Photo uploaded successfully", + "photoUploadFailed": "Upload failed: {statusCode}", + "@photoUploadFailed": { + "placeholders": { + "statusCode": {"type": "int"} + } + }, + "cameraError": "Camera error: {message}", + "@cameraError": { + "placeholders": { + "message": {"type": "String"} + } + }, + "uploadError": "Upload error: {message}", + "@uploadError": { + "placeholders": { + "message": {"type": "String"} + } + }, + "serverError": "Server error - Please contact support", + "retake": "Retake" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index b6ea8c6..dbb44ca 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -117,5 +117,34 @@ } }, "noNotesMessage": "Aucune note associée à cette livraison", - "close": "Fermer" + "close": "Fermer", + "confirmPhoto": "Confirmer la photo", + "uploadPhotoConfirmation": "Telecharger cette photo pour {name}?", + "@uploadPhotoConfirmation": { + "placeholders": { + "name": {"type": "String"} + } + }, + "uploadingPhoto": "Telechargement de la photo...", + "photoUploadSuccess": "Photo telechargee avec succes", + "photoUploadFailed": "Echec du telechargement: {statusCode}", + "@photoUploadFailed": { + "placeholders": { + "statusCode": {"type": "int"} + } + }, + "cameraError": "Erreur de camera: {message}", + "@cameraError": { + "placeholders": { + "message": {"type": "String"} + } + }, + "uploadError": "Erreur de telechargement: {message}", + "@uploadError": { + "placeholders": { + "message": {"type": "String"} + } + }, + "serverError": "Erreur serveur - Veuillez contacter le support", + "retake": "Reprendre" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 1ffde8f..7479dbd 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -577,6 +577,60 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Close'** String get close; + + /// No description provided for @confirmPhoto. + /// + /// In en, this message translates to: + /// **'Confirm Photo'** + String get confirmPhoto; + + /// No description provided for @uploadPhotoConfirmation. + /// + /// In en, this message translates to: + /// **'Upload this photo for {name}?'** + String uploadPhotoConfirmation(String name); + + /// No description provided for @uploadingPhoto. + /// + /// In en, this message translates to: + /// **'Uploading photo...'** + String get uploadingPhoto; + + /// No description provided for @photoUploadSuccess. + /// + /// In en, this message translates to: + /// **'Photo uploaded successfully'** + String get photoUploadSuccess; + + /// No description provided for @photoUploadFailed. + /// + /// In en, this message translates to: + /// **'Upload failed: {statusCode}'** + String photoUploadFailed(int statusCode); + + /// No description provided for @cameraError. + /// + /// In en, this message translates to: + /// **'Camera error: {message}'** + String cameraError(String message); + + /// No description provided for @uploadError. + /// + /// In en, this message translates to: + /// **'Upload error: {message}'** + String uploadError(String message); + + /// No description provided for @serverError. + /// + /// In en, this message translates to: + /// **'Server error - Please contact support'** + String get serverError; + + /// No description provided for @retake. + /// + /// In en, this message translates to: + /// **'Retake'** + String get retake; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index a9d982b..e2a15f0 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -267,4 +267,39 @@ class AppLocalizationsEn extends AppLocalizations { @override String get close => 'Close'; + + @override + String get confirmPhoto => 'Confirm Photo'; + + @override + String uploadPhotoConfirmation(String name) { + return 'Upload this photo for $name?'; + } + + @override + String get uploadingPhoto => 'Uploading photo...'; + + @override + String get photoUploadSuccess => 'Photo uploaded successfully'; + + @override + String photoUploadFailed(int statusCode) { + return 'Upload failed: $statusCode'; + } + + @override + String cameraError(String message) { + return 'Camera error: $message'; + } + + @override + String uploadError(String message) { + return 'Upload error: $message'; + } + + @override + String get serverError => 'Server error - Please contact support'; + + @override + String get retake => 'Retake'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 854f1b3..6b12a3b 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -267,4 +267,39 @@ class AppLocalizationsFr extends AppLocalizations { @override String get close => 'Fermer'; + + @override + String get confirmPhoto => 'Confirmer la photo'; + + @override + String uploadPhotoConfirmation(String name) { + return 'Telecharger cette photo pour $name?'; + } + + @override + String get uploadingPhoto => 'Telechargement de la photo...'; + + @override + String get photoUploadSuccess => 'Photo telechargee avec succes'; + + @override + String photoUploadFailed(int statusCode) { + return 'Echec du telechargement: $statusCode'; + } + + @override + String cameraError(String message) { + return 'Erreur de camera: $message'; + } + + @override + String uploadError(String message) { + return 'Erreur de telechargement: $message'; + } + + @override + String get serverError => 'Erreur serveur - Veuillez contacter le support'; + + @override + String get retake => 'Reprendre'; } From fcf7e83f132d266129b8a839f3a5bede6135a603 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:32:52 -0500 Subject: [PATCH 07/12] auto-claude: subtask-3-1 - Extract UnifiedDeliveryListView to dedicated component Extracted UnifiedDeliveryListView from deliveries_page.dart to lib/components/unified_delivery_list.dart for better code organization and reusability. The component provides a unified delivery list experience supporting expanded and collapsed states for responsive sidebar layouts. Co-Authored-By: Claude --- lib/components/unified_delivery_list.dart | 61 +++++++++++++++++++++++ lib/pages/deliveries_page.dart | 55 +------------------- 2 files changed, 62 insertions(+), 54 deletions(-) create mode 100644 lib/components/unified_delivery_list.dart diff --git a/lib/components/unified_delivery_list.dart b/lib/components/unified_delivery_list.dart new file mode 100644 index 0000000..2a94f37 --- /dev/null +++ b/lib/components/unified_delivery_list.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import '../models/delivery.dart'; +import '../l10n/app_localizations.dart'; +import 'delivery_list_item.dart'; + +/// A unified list view for displaying deliveries with selection and action support. +/// +/// This component provides a consistent delivery list experience across the app, +/// supporting both expanded and collapsed states for responsive sidebar layouts. +class UnifiedDeliveryListView extends StatelessWidget { + final List deliveries; + final Delivery? selectedDelivery; + final ScrollController scrollController; + final ValueChanged onDeliverySelected; + final Function(Delivery, String) onItemAction; + final bool isCollapsed; + + const UnifiedDeliveryListView({ + super.key, + required this.deliveries, + this.selectedDelivery, + required this.scrollController, + required this.onDeliverySelected, + required this.onItemAction, + this.isCollapsed = false, + }); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context); + if (deliveries.isEmpty) { + return Center( + child: Text(l10n.noDeliveries), + ); + } + + return RefreshIndicator( + onRefresh: () async { + // Trigger refresh via provider + }, + child: ListView.builder( + controller: scrollController, + padding: const EdgeInsets.only(top: 4, bottom: 8), + physics: const AlwaysScrollableScrollPhysics(), + itemCount: deliveries.length, + itemBuilder: (context, index) { + final delivery = deliveries[index]; + return DeliveryListItem( + delivery: delivery, + isSelected: selectedDelivery?.id == delivery.id, + onTap: () => onDeliverySelected(delivery), + onCall: () => onItemAction(delivery, 'call'), + onAction: (action) => onItemAction(delivery, action), + animationIndex: index, + isCollapsed: isCollapsed, + ); + }, + ), + ); + } +} diff --git a/lib/pages/deliveries_page.dart b/lib/pages/deliveries_page.dart index cbd4d2c..1896ec5 100644 --- a/lib/pages/deliveries_page.dart +++ b/lib/pages/deliveries_page.dart @@ -12,7 +12,7 @@ import '../api/openapi_config.dart'; import '../models/delivery_commands.dart'; import '../components/map_sidebar_layout.dart'; import '../components/dark_mode_map.dart'; -import '../components/delivery_list_item.dart'; +import '../components/unified_delivery_list.dart'; import '../utils/toast_helper.dart'; class DeliveriesPage extends ConsumerStatefulWidget { @@ -508,59 +508,6 @@ class _DeliveriesPageState extends ConsumerState { } } -class UnifiedDeliveryListView extends StatelessWidget { - final List deliveries; - final Delivery? selectedDelivery; - final ScrollController scrollController; - final ValueChanged onDeliverySelected; - final Function(Delivery, String) onItemAction; - final bool isCollapsed; - - const UnifiedDeliveryListView({ - super.key, - required this.deliveries, - this.selectedDelivery, - required this.scrollController, - required this.onDeliverySelected, - required this.onItemAction, - this.isCollapsed = false, - }); - - @override - Widget build(BuildContext context) { - final l10n = AppLocalizations.of(context)!; - if (deliveries.isEmpty) { - return Center( - child: Text(l10n.noDeliveries), - ); - } - - return RefreshIndicator( - onRefresh: () async { - // Trigger refresh via provider - }, - child: ListView.builder( - controller: scrollController, - padding: const EdgeInsets.only(top: 4, bottom: 8), - physics: const AlwaysScrollableScrollPhysics(), - itemCount: deliveries.length, // Show all deliveries with scrolling - itemBuilder: (context, index) { - final delivery = deliveries[index]; - return DeliveryListItem( - delivery: delivery, - isSelected: selectedDelivery?.id == delivery.id, - onTap: () => onDeliverySelected(delivery), - onCall: () => onItemAction(delivery, 'call'), - onAction: (action) => onItemAction(delivery, action), - animationIndex: index, - isCollapsed: isCollapsed, - ); - }, - ), - ); - } -} - class DeliveryCard extends StatelessWidget { final Delivery delivery; final bool isSelected; From 5cb220f68c87442901df55e7b6a1fa05a6808155 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:36:49 -0500 Subject: [PATCH 08/12] auto-claude: subtask-3-2 - Extract DeliveryCard to dedicated component file Extracted DeliveryCard widget from deliveries_page.dart to its own component file at lib/components/delivery_card.dart. Also fixed unnecessary non-null assertion warnings by removing redundant '!' operators after AppLocalizations.of(context) calls. Co-Authored-By: Claude --- lib/components/delivery_card.dart | 183 ++++++++++++++++++++++++++++++ lib/pages/deliveries_page.dart | 175 ---------------------------- 2 files changed, 183 insertions(+), 175 deletions(-) create mode 100644 lib/components/delivery_card.dart diff --git a/lib/components/delivery_card.dart b/lib/components/delivery_card.dart new file mode 100644 index 0000000..4503214 --- /dev/null +++ b/lib/components/delivery_card.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import '../models/delivery.dart'; +import '../l10n/app_localizations.dart'; + +/// A card widget that displays delivery information with action buttons. +/// +/// This component shows delivery details including customer name, contact, +/// address, order information, and provides quick action buttons for +/// calling, navigating, and more options. +class DeliveryCard extends StatelessWidget { + final Delivery delivery; + final bool isSelected; + final VoidCallback onTap; + final Function(Delivery, String) onAction; + + const DeliveryCard({ + super.key, + required this.delivery, + this.isSelected = false, + required this.onTap, + required this.onAction, + }); + + @override + Widget build(BuildContext context) { + final contact = delivery.orders.isNotEmpty && delivery.orders.first.contact != null + ? delivery.orders.first.contact + : null; + final order = delivery.orders.isNotEmpty ? delivery.orders.first : null; + + return Card( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + color: isSelected + ? Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3) + : null, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + delivery.name, + style: Theme.of(context).textTheme.titleMedium, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (contact != null) + Text( + contact.fullName, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + if (delivery.delivered) + Chip( + label: Text(AppLocalizations.of(context).delivered), + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + ) + else if (order?.isNewCustomer ?? false) + Chip( + label: Text(AppLocalizations.of(context).newCustomer), + backgroundColor: const Color(0xFFFFFBEB), + ), + ], + ), + const SizedBox(height: 12), + if (delivery.deliveryAddress != null) + Text( + delivery.deliveryAddress!.formattedAddress ?? '', + style: Theme.of(context).textTheme.bodySmall, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (order != null) ...[ + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (order.totalItems != null) + Text( + AppLocalizations.of(context).items(order.totalItems!), + style: Theme.of(context).textTheme.bodySmall, + ), + Text( + AppLocalizations.of(context).moneyCurrency(order.totalAmount), + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ], + const SizedBox(height: 12), + Wrap( + spacing: 8, + children: [ + if (contact?.phoneNumber != null) + OutlinedButton.icon( + onPressed: () => onAction(delivery, 'call'), + icon: const Icon(Icons.phone), + label: Text(AppLocalizations.of(context).call), + ), + if (delivery.deliveryAddress != null) + OutlinedButton.icon( + onPressed: () { + onTap(); // Select the delivery + onAction(delivery, 'map'); + }, + icon: const Icon(Icons.map), + label: Text(AppLocalizations.of(context).navigate), + ), + OutlinedButton.icon( + onPressed: () => _showDeliveryActions(context), + icon: const Icon(Icons.more_vert), + label: Text(AppLocalizations.of(context).more), + ), + ], + ), + ], + ), + ), + ), + ); + } + + void _showDeliveryActions(BuildContext context) { + final l10n = AppLocalizations.of(context); + showModalBottomSheet( + context: context, + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (!delivery.delivered) + ListTile( + leading: const Icon(Icons.check_circle), + title: Text(l10n.markAsCompleted), + onTap: () { + Navigator.pop(context); + onAction(delivery, 'complete'); + }, + ) + else + ListTile( + leading: const Icon(Icons.undo), + title: Text(l10n.markAsUncompleted), + onTap: () { + Navigator.pop(context); + onAction(delivery, 'uncomplete'); + }, + ), + ListTile( + leading: const Icon(Icons.camera_alt), + title: Text(l10n.uploadPhoto), + onTap: () { + Navigator.pop(context); + onAction(delivery, 'photo'); + }, + ), + ListTile( + leading: const Icon(Icons.description), + title: Text(l10n.viewDetails), + onTap: () { + Navigator.pop(context); + onAction(delivery, 'details'); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/deliveries_page.dart b/lib/pages/deliveries_page.dart index 1896ec5..1ee0ef0 100644 --- a/lib/pages/deliveries_page.dart +++ b/lib/pages/deliveries_page.dart @@ -507,178 +507,3 @@ class _DeliveriesPageState extends ConsumerState { } } } - -class DeliveryCard extends StatelessWidget { - final Delivery delivery; - final bool isSelected; - final VoidCallback onTap; - final Function(Delivery, String) onAction; - - const DeliveryCard({ - super.key, - required this.delivery, - this.isSelected = false, - required this.onTap, - required this.onAction, - }); - - @override - Widget build(BuildContext context) { - final contact = delivery.orders.isNotEmpty && delivery.orders.first.contact != null - ? delivery.orders.first.contact - : null; - final order = delivery.orders.isNotEmpty ? delivery.orders.first : null; - - return Card( - margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - color: isSelected - ? Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3) - : null, - child: InkWell( - onTap: onTap, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - delivery.name, - style: Theme.of(context).textTheme.titleMedium, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - if (contact != null) - Text( - contact.fullName, - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ), - if (delivery.delivered) - Chip( - label: Text(AppLocalizations.of(context)!.delivered), - backgroundColor: Theme.of(context).colorScheme.primaryContainer, - ) - else if (order?.isNewCustomer ?? false) - Chip( - label: Text(AppLocalizations.of(context)!.newCustomer), - backgroundColor: const Color(0xFFFFFBEB), - ), - ], - ), - const SizedBox(height: 12), - if (delivery.deliveryAddress != null) - Text( - delivery.deliveryAddress!.formattedAddress ?? '', - style: Theme.of(context).textTheme.bodySmall, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - if (order != null) ...[ - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (order.totalItems != null) - Text( - AppLocalizations.of(context)!.items(order.totalItems!), - style: Theme.of(context).textTheme.bodySmall, - ), - Text( - AppLocalizations.of(context)!.moneyCurrency(order.totalAmount), - style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), - ], - ), - ], - const SizedBox(height: 12), - Wrap( - spacing: 8, - children: [ - if (contact?.phoneNumber != null) - OutlinedButton.icon( - onPressed: () => onAction(delivery, 'call'), - icon: const Icon(Icons.phone), - label: Text(AppLocalizations.of(context)!.call), - ), - if (delivery.deliveryAddress != null) - OutlinedButton.icon( - onPressed: () { - onTap(); // Select the delivery - onAction(delivery, 'map'); - }, - icon: const Icon(Icons.map), - label: Text(AppLocalizations.of(context)!.navigate), - ), - OutlinedButton.icon( - onPressed: () => _showDeliveryActions(context), - icon: const Icon(Icons.more_vert), - label: Text(AppLocalizations.of(context)!.more), - ), - ], - ), - ], - ), - ), - ), - ); - } - - void _showDeliveryActions(BuildContext context) { - final l10n = AppLocalizations.of(context)!; - showModalBottomSheet( - context: context, - builder: (context) => SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (!delivery.delivered) - ListTile( - leading: const Icon(Icons.check_circle), - title: Text(l10n.markAsCompleted), - onTap: () { - Navigator.pop(context); - onAction(delivery, 'complete'); - }, - ) - else - ListTile( - leading: const Icon(Icons.undo), - title: Text(l10n.markAsUncompleted), - onTap: () { - Navigator.pop(context); - onAction(delivery, 'uncomplete'); - }, - ), - ListTile( - leading: const Icon(Icons.camera_alt), - title: Text(l10n.uploadPhoto), - onTap: () { - Navigator.pop(context); - // TODO: Implement photo upload - }, - ), - ListTile( - leading: const Icon(Icons.description), - title: Text(l10n.viewDetails), - onTap: () { - Navigator.pop(context); - // TODO: Navigate to delivery details - }, - ), - ], - ), - ), - ); - } -} From c8c2ec0921449cd1c54afb3e0c246c9f8ab77c0e Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:40:20 -0500 Subject: [PATCH 09/12] auto-claude: subtask-4-1 - Update deliveries_page.dart to import and use extracted components - Import LoadingDialog and PhotoCaptureDialog components - Replace inline photo confirmation dialog with PhotoCaptureDialog.show() - Replace inline loading dialog with LoadingDialog.show() and LoadingDialog.hide() - Use localized uploadingPhoto string for loading message Co-Authored-By: Claude --- lib/pages/deliveries_page.dart | 65 ++++++---------------------------- 1 file changed, 10 insertions(+), 55 deletions(-) diff --git a/lib/pages/deliveries_page.dart b/lib/pages/deliveries_page.dart index 1ee0ef0..748fdda 100644 --- a/lib/pages/deliveries_page.dart +++ b/lib/pages/deliveries_page.dart @@ -13,6 +13,8 @@ import '../models/delivery_commands.dart'; import '../components/map_sidebar_layout.dart'; import '../components/dark_mode_map.dart'; import '../components/unified_delivery_list.dart'; +import '../components/loading_dialog.dart'; +import '../components/photo_capture_dialog.dart'; import '../utils/toast_helper.dart'; class DeliveriesPage extends ConsumerStatefulWidget { @@ -410,38 +412,10 @@ class _DeliveriesPageState extends ConsumerState { if (!context.mounted) return; - final bool? confirmed = await showDialog( - context: context, - builder: (BuildContext dialogContext) { - return AlertDialog( - title: const Text('Confirm Photo'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Image.file( - File(pickedFile!.path), - height: 300, - 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: Text(AppLocalizations.of(context)!.cancel), - ), - ElevatedButton( - onPressed: () => Navigator.of(dialogContext).pop(true), - child: Text(AppLocalizations.of(context)!.upload), - ), - ], - ); - }, + final bool? confirmed = await PhotoCaptureDialog.show( + context, + imageFile: File(pickedFile.path), + deliveryName: delivery.name, ); if (confirmed != true) { @@ -450,27 +424,8 @@ class _DeliveriesPageState extends ConsumerState { if (!context.mounted) return; - showDialog( - 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('Uploading photo...'), - ], - ), - ), - ), - ); - }, - ); + final localizations = AppLocalizations.of(context); + LoadingDialog.show(context, message: localizations.uploadingPhoto); try { final Uri uploadUrl = Uri.parse( @@ -485,7 +440,7 @@ class _DeliveriesPageState extends ConsumerState { final http.Response response = await http.Response.fromStream(streamedResponse); if (context.mounted) { - Navigator.of(context).pop(); + LoadingDialog.hide(context); } if (response.statusCode >= 200 && response.statusCode < 300) { @@ -501,7 +456,7 @@ class _DeliveriesPageState extends ConsumerState { } } catch (e) { if (context.mounted) { - Navigator.of(context).pop(); + LoadingDialog.hide(context); ToastHelper.showError(context, 'Upload error: $e'); } } From 697b724f02804fa4ca5eba2eac4cf26227ffc1be Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:45:39 -0500 Subject: [PATCH 10/12] 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 --- lib/l10n/app_en.arb | 5 +- lib/l10n/app_fr.arb | 5 +- lib/l10n/app_localizations.dart | 18 +++ lib/l10n/app_localizations_en.dart | 9 ++ lib/l10n/app_localizations_fr.dart | 9 ++ lib/pages/routes_page.dart | 215 ++++++----------------------- 6 files changed, 84 insertions(+), 177 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b132f88..ff0fa88 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -146,5 +146,8 @@ } }, "serverError": "Server error - Please contact support", - "retake": "Retake" + "retake": "Retake", + "completingDelivery": "Completing delivery...", + "markingAsUncompleted": "Marking as uncompleted...", + "deliveryMarkedUncompleted": "Delivery marked as uncompleted" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index dbb44ca..8f35436 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -146,5 +146,8 @@ } }, "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" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 7479dbd..ee43169 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -631,6 +631,24 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'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 diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index e2a15f0..621e89c 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -302,4 +302,13 @@ class AppLocalizationsEn extends AppLocalizations { @override 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'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 6b12a3b..1416596 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -302,4 +302,13 @@ class AppLocalizationsFr extends AppLocalizations { @override 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'; } diff --git a/lib/pages/routes_page.dart b/lib/pages/routes_page.dart index 20c1c24..b6fc49d 100644 --- a/lib/pages/routes_page.dart +++ b/lib/pages/routes_page.dart @@ -11,10 +11,12 @@ import '../providers/providers.dart'; import '../api/client.dart'; import '../utils/toast_helper.dart'; import '../api/openapi_config.dart'; -import '../utils/breakpoints.dart'; import '../utils/http_client_factory.dart'; import '../components/collapsible_routes_sidebar.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 'deliveries_page.dart'; import 'settings_page.dart'; @@ -81,13 +83,14 @@ class _RoutesPageState extends ConsumerState { Delivery delivery, int routeFragmentId, ) async { + // Capture l10n before async operations to avoid BuildContext across async gaps + final l10n = AppLocalizations.of(context); final authService = ref.read(authServiceProvider); // Ensure we have a valid token (automatically refreshes if needed) final token = await authService.ensureValidToken(); if (token == null) { if (mounted) { - final l10n = AppLocalizations.of(context)!; ToastHelper.showError(context, l10n.authenticationRequired); } return; @@ -102,27 +105,7 @@ class _RoutesPageState extends ConsumerState { switch (action) { case 'complete': if (mounted) { - showDialog( - 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...'), - ], - ), - ), - ), - ); - }, - ); + LoadingDialog.show(context, message: l10n.completingDelivery); } final result = await authClient.executeCommand( @@ -134,7 +117,7 @@ class _RoutesPageState extends ConsumerState { result.when( success: (_) async { if (mounted) { - Navigator.of(context).pop(); + LoadingDialog.hide(context); } if (mounted) { @@ -176,23 +159,21 @@ class _RoutesPageState extends ConsumerState { } if (mounted) { - final l10n = AppLocalizations.of(context)!; ToastHelper.showSuccess(context, l10n.deliverySuccessful); } } }, onError: (error) { if (mounted) { - Navigator.of(context).pop(); + LoadingDialog.hide(context); } debugPrint('Complete delivery failed - Type: ${error.type}, Message: ${error.message}'); debugPrint('Error details: ${error.details}'); if (mounted) { - final l10n = AppLocalizations.of(context)!; String errorMessage = l10n.error(error.message); if (error.statusCode == 500) { - errorMessage = 'Server error - Please contact support'; + errorMessage = l10n.serverError; } ToastHelper.showError(context, errorMessage); } @@ -202,37 +183,17 @@ class _RoutesPageState extends ConsumerState { case 'uncomplete': if (mounted) { - showDialog( - 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...'), - ], - ), - ), - ), - ); - }, - ); + LoadingDialog.show(context, message: l10n.markingAsUncompleted); } - final result = await authClient.executeCommand( + final uncompleteResult = await authClient.executeCommand( endpoint: 'markDeliveryAsUncompleted', command: MarkDeliveryAsUncompletedCommand(deliveryId: delivery.id), ); - result.when( + uncompleteResult.when( success: (_) async { if (mounted) { - Navigator.of(context).pop(); + LoadingDialog.hide(context); } if (mounted) { @@ -257,18 +218,16 @@ class _RoutesPageState extends ConsumerState { } if (mounted) { - final l10n = AppLocalizations.of(context)!; - ToastHelper.showSuccess(context, 'Delivery marked as uncompleted'); + ToastHelper.showSuccess(context, l10n.deliveryMarkedUncompleted); } } }, onError: (error) { if (mounted) { - Navigator.of(context).pop(); + LoadingDialog.hide(context); } if (mounted) { - final l10n = AppLocalizations.of(context)!; ToastHelper.showError(context, l10n.error(error.message)); } }, @@ -289,12 +248,12 @@ class _RoutesPageState extends ConsumerState { Delivery delivery, ) async { final authService = ref.read(authServiceProvider); + final l10n = AppLocalizations.of(context); // Ensure we have a valid token (automatically refreshes if needed) final token = await authService.ensureValidToken(); if (token == null) { if (mounted) { - final l10n = AppLocalizations.of(context)!; ToastHelper.showError(context, l10n.authenticationRequired); } return; @@ -309,7 +268,7 @@ class _RoutesPageState extends ConsumerState { ); } catch (e) { if (mounted) { - ToastHelper.showError(context, 'Camera error: $e'); + ToastHelper.showError(context, l10n.cameraError(e.toString())); } return; } @@ -320,45 +279,11 @@ class _RoutesPageState extends ConsumerState { if (!mounted) return; - final bool? confirmed = await showDialog( - context: context, - builder: (BuildContext dialogContext) { - return AlertDialog( - title: const Text('Confirm Photo'), - 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'), - ), - ], - ); - }, + // Show photo confirmation dialog + final bool? confirmed = await PhotoCaptureDialog.show( + context, + imageFile: File(pickedFile.path), + deliveryName: delivery.name, ); if (confirmed != true) { @@ -367,27 +292,8 @@ class _RoutesPageState extends ConsumerState { if (!mounted) return; - showDialog( - 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('Uploading photo...'), - ], - ), - ), - ), - ); - }, - ); + // Show uploading dialog + LoadingDialog.show(context, message: l10n.uploadingPhoto); try { final Uri uploadUrl = Uri.parse( @@ -408,33 +314,31 @@ class _RoutesPageState extends ConsumerState { client.close(); if (mounted) { - Navigator.of(context).pop(); + LoadingDialog.hide(context); } if (response.statusCode >= 200 && response.statusCode < 300) { if (mounted) { - ToastHelper.showSuccess(context, 'Photo uploaded successfully'); + ToastHelper.showSuccess(context, l10n.photoUploadSuccess); } - ref.refresh(allDeliveriesProvider); + ref.invalidate(allDeliveriesProvider); } else { debugPrint('Photo upload failed - Status: ${response.statusCode}'); debugPrint('Response body: ${response.body}'); if (mounted) { - String errorMessage = 'Upload failed'; + String errorMessage = l10n.photoUploadFailed(response.statusCode); if (response.statusCode == 500) { - errorMessage = 'Server error - Please contact support'; + errorMessage = l10n.serverError; } else if (response.statusCode == 401) { - errorMessage = 'Authentication required - Please log in again'; - } else { - errorMessage = 'Upload failed: ${response.statusCode}'; + errorMessage = l10n.authenticationRequired; } ToastHelper.showError(context, errorMessage); } } } catch (e) { if (mounted) { - Navigator.of(context).pop(); - ToastHelper.showError(context, 'Upload error: $e'); + LoadingDialog.hide(context); + ToastHelper.showError(context, l10n.uploadError(e.toString())); } } } @@ -462,53 +366,14 @@ class _RoutesPageState extends ConsumerState { } Future _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 (notes.isEmpty) { - ToastHelper.showInfo(context, 'No notes attached to this delivery'); - return; - } + final l10n = AppLocalizations.of(context); + final hasNotes = await NotesDialog.show(context, delivery); - await showDialog( - context: context, - 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'), - ), - ), - ], - ); - }, - ); + if (!hasNotes && mounted) { + ToastHelper.showInfo(context, l10n.noNotesMessage); + } } @override @@ -516,7 +381,7 @@ class _RoutesPageState extends ConsumerState { final routesData = ref.watch(deliveryRoutesProvider); final allDeliveriesData = ref.watch(allDeliveriesProvider); final userProfile = ref.watch(userProfileProvider); - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Scaffold( appBar: AppBar( @@ -535,8 +400,8 @@ class _RoutesPageState extends ConsumerState { onPressed: (routesData.isLoading || allDeliveriesData.isLoading) ? null : () { - ref.refresh(deliveryRoutesProvider); - ref.refresh(allDeliveriesProvider); + ref.invalidate(deliveryRoutesProvider); + ref.invalidate(allDeliveriesProvider); }, tooltip: 'Refresh', ), From c53f4a3b2f74d2894cf3d3d1c62cec0c46290b69 Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:47:26 -0500 Subject: [PATCH 11/12] auto-claude: subtask-5-1 - Remove orientation restriction to allow all orientations Removed SystemChrome.setPreferredOrientations call that was restricting the app to landscape-only mode. The app now supports all device orientations by using Flutter's default behavior. Also fixed a minor lint warning by replacing (_, __) with named parameters. Co-Authored-By: Claude --- .auto-claude-security.json | 211 +++++++++++++++++++++++++++++++++++++ .auto-claude-status | 25 +++++ .claude_settings.json | 39 +++++++ .gitignore | 3 + lib/main.dart | 7 +- 5 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 .auto-claude-security.json create mode 100644 .auto-claude-status create mode 100644 .claude_settings.json diff --git a/.auto-claude-security.json b/.auto-claude-security.json new file mode 100644 index 0000000..593a383 --- /dev/null +++ b/.auto-claude-security.json @@ -0,0 +1,211 @@ +{ + "base_commands": [ + ".", + "[", + "[[", + "ag", + "awk", + "basename", + "bash", + "bc", + "break", + "cat", + "cd", + "chmod", + "clear", + "cmp", + "column", + "comm", + "command", + "continue", + "cp", + "curl", + "cut", + "date", + "df", + "diff", + "dig", + "dirname", + "du", + "echo", + "egrep", + "env", + "eval", + "exec", + "exit", + "expand", + "export", + "expr", + "false", + "fd", + "fgrep", + "file", + "find", + "fmt", + "fold", + "gawk", + "gh", + "git", + "grep", + "gunzip", + "gzip", + "head", + "help", + "host", + "iconv", + "id", + "jobs", + "join", + "jq", + "kill", + "killall", + "less", + "let", + "ln", + "ls", + "lsof", + "man", + "mkdir", + "mktemp", + "more", + "mv", + "nl", + "paste", + "pgrep", + "ping", + "pkill", + "popd", + "printenv", + "printf", + "ps", + "pushd", + "pwd", + "read", + "readlink", + "realpath", + "reset", + "return", + "rev", + "rg", + "rm", + "rmdir", + "sed", + "seq", + "set", + "sh", + "shuf", + "sleep", + "sort", + "source", + "split", + "stat", + "tail", + "tar", + "tee", + "test", + "time", + "timeout", + "touch", + "tr", + "tree", + "true", + "type", + "uname", + "unexpand", + "uniq", + "unset", + "unzip", + "watch", + "wc", + "wget", + "whereis", + "which", + "whoami", + "xargs", + "yes", + "yq", + "zip", + "zsh" + ], + "stack_commands": [ + "ant", + "ar", + "clang", + "clang++", + "cmake", + "dart", + "dart2js", + "dartanalyzer", + "dartdoc", + "dartfmt", + "flutter", + "fvm", + "g++", + "gcc", + "gradle", + "gradlew", + "ipython", + "jar", + "java", + "javac", + "jupyter", + "kotlin", + "kotlinc", + "ld", + "make", + "maven", + "meson", + "mvn", + "ninja", + "nm", + "notebook", + "objdump", + "pdb", + "pip", + "pip3", + "pipx", + "pub", + "pudb", + "python", + "python3", + "strip", + "swift", + "swiftc", + "xcodebuild" + ], + "script_commands": [], + "custom_commands": [], + "detected_stack": { + "languages": [ + "python", + "java", + "kotlin", + "c", + "cpp", + "swift", + "dart" + ], + "package_managers": [ + "pub" + ], + "frameworks": [ + "flutter" + ], + "databases": [], + "infrastructure": [], + "cloud_providers": [], + "code_quality_tools": [], + "version_managers": [] + }, + "custom_scripts": { + "npm_scripts": [], + "make_targets": [], + "poetry_scripts": [], + "cargo_aliases": [], + "shell_scripts": [] + }, + "project_dir": "/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter", + "created_at": "2026-01-20T11:12:23.419489", + "project_hash": "7fd6fc12b9f5a8ae884a529baeb5487f", + "inherited_from": "/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter" +} \ No newline at end of file diff --git a/.auto-claude-status b/.auto-claude-status new file mode 100644 index 0000000..f0f66ad --- /dev/null +++ b/.auto-claude-status @@ -0,0 +1,25 @@ +{ + "active": true, + "spec": "001-normalize-code-update-packages-widgetify-component", + "state": "building", + "subtasks": { + "completed": 10, + "total": 14, + "in_progress": 1, + "failed": 0 + }, + "phase": { + "current": "Unlock Orientation", + "id": null, + "total": 1 + }, + "workers": { + "active": 0, + "max": 1 + }, + "session": { + "number": 11, + "started_at": "2026-01-20T11:20:56.182893" + }, + "last_update": "2026-01-20T11:46:27.066658" +} \ No newline at end of file diff --git a/.claude_settings.json b/.claude_settings.json new file mode 100644 index 0000000..328e8a0 --- /dev/null +++ b/.claude_settings.json @@ -0,0 +1,39 @@ +{ + "sandbox": { + "enabled": true, + "autoAllowBashIfSandboxed": true + }, + "permissions": { + "defaultMode": "acceptEdits", + "allow": [ + "Read(./**)", + "Write(./**)", + "Edit(./**)", + "Glob(./**)", + "Grep(./**)", + "Read(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/worktrees/tasks/001-normalize-code-update-packages-widgetify-component/**)", + "Write(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/worktrees/tasks/001-normalize-code-update-packages-widgetify-component/**)", + "Edit(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/worktrees/tasks/001-normalize-code-update-packages-widgetify-component/**)", + "Glob(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/worktrees/tasks/001-normalize-code-update-packages-widgetify-component/**)", + "Grep(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/worktrees/tasks/001-normalize-code-update-packages-widgetify-component/**)", + "Read(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/worktrees/tasks/001-normalize-code-update-packages-widgetify-component/.auto-claude/specs/001-normalize-code-update-packages-widgetify-component/**)", + "Write(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/worktrees/tasks/001-normalize-code-update-packages-widgetify-component/.auto-claude/specs/001-normalize-code-update-packages-widgetify-component/**)", + "Edit(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/worktrees/tasks/001-normalize-code-update-packages-widgetify-component/.auto-claude/specs/001-normalize-code-update-packages-widgetify-component/**)", + "Read(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/**)", + "Write(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/**)", + "Edit(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/**)", + "Glob(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/**)", + "Grep(/Users/mathias/Documents/workspaces/plan-b/ionic-planb-logistic-app-flutter/.auto-claude/**)", + "Bash(*)", + "WebFetch(*)", + "WebSearch(*)", + "mcp__context7__resolve-library-id(*)", + "mcp__context7__get-library-docs(*)", + "mcp__graphiti-memory__search_nodes(*)", + "mcp__graphiti-memory__search_facts(*)", + "mcp__graphiti-memory__add_episode(*)", + "mcp__graphiti-memory__get_episodes(*)", + "mcp__graphiti-memory__get_entity_edge(*)" + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3820a95..5224bfb 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Auto Claude data directory +.auto-claude/ diff --git a/lib/main.dart b/lib/main.dart index 4687be0..e4b63f1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,11 +11,6 @@ import 'pages/routes_page.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - await SystemChrome.setPreferredOrientations([ - DeviceOrientation.landscapeLeft, - DeviceOrientation.landscapeRight, - ]); - runApp( const ProviderScope( child: PlanBLogisticApp(), @@ -71,7 +66,7 @@ class PlanBLogisticApp extends ConsumerWidget { ), ), ), - error: (_, __) => MaterialApp( + error: (error, stackTrace) => MaterialApp( title: 'Plan B Logistics', theme: MaterialTheme(const TextTheme()).light(), darkTheme: MaterialTheme(const TextTheme()).dark(), From 6986a12b911fdfe490d8a288d08634ea96f56a4c Mon Sep 17 00:00:00 2001 From: Mathias Beaulieu-Duncan Date: Tue, 20 Jan 2026 11:54:12 -0500 Subject: [PATCH 12/12] auto-claude: subtask-6-1 - Run flutter analyze to ensure no errors or warnings Fixed all 39 analyzer issues: - Removed unused import (animation_system.dart in collapsible_routes_sidebar.dart) - Removed unused element (_buildActionButton in dark_mode_map.dart) - Fixed unnecessary non-null assertions on AppLocalizations.of(context) - Removed unnecessary type checks in providers.dart - Used super parameters for key in navigation_tc_dialog.dart and status_colors.dart - Replaced print statements with debugPrint in providers.dart and logging_interceptor.dart Co-Authored-By: Claude --- .auto-claude-status | 10 ++-- .../collapsible_routes_sidebar.dart | 3 +- lib/components/dark_mode_map.dart | 56 ------------------- lib/components/delivery_list_item.dart | 2 +- lib/components/navigation_tc_dialog.dart | 4 +- lib/components/route_list_item.dart | 2 +- lib/pages/deliveries_page.dart | 13 ++--- lib/pages/login_page.dart | 18 +++--- lib/pages/settings_page.dart | 2 +- lib/providers/providers.dart | 20 +++---- lib/theme/status_colors.dart | 8 +-- lib/utils/logging_interceptor.dart | 19 ++++--- 12 files changed, 48 insertions(+), 109 deletions(-) diff --git a/.auto-claude-status b/.auto-claude-status index f0f66ad..b749234 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,23 +3,23 @@ "spec": "001-normalize-code-update-packages-widgetify-component", "state": "building", "subtasks": { - "completed": 10, + "completed": 11, "total": 14, "in_progress": 1, "failed": 0 }, "phase": { - "current": "Unlock Orientation", + "current": "Cleanup and Verification", "id": null, - "total": 1 + "total": 3 }, "workers": { "active": 0, "max": 1 }, "session": { - "number": 11, + "number": 12, "started_at": "2026-01-20T11:20:56.182893" }, - "last_update": "2026-01-20T11:46:27.066658" + "last_update": "2026-01-20T11:47:55.069999" } \ No newline at end of file diff --git a/lib/components/collapsible_routes_sidebar.dart b/lib/components/collapsible_routes_sidebar.dart index a59d46b..b422c29 100644 --- a/lib/components/collapsible_routes_sidebar.dart +++ b/lib/components/collapsible_routes_sidebar.dart @@ -4,7 +4,6 @@ import '../l10n/app_localizations.dart'; import '../models/delivery_route.dart'; import '../theme/spacing_system.dart'; import '../theme/size_system.dart'; -import '../theme/animation_system.dart'; import '../theme/color_system.dart'; import '../utils/breakpoints.dart'; import '../providers/providers.dart'; @@ -73,7 +72,7 @@ class _CollapsibleRoutesSidebarState extends ConsumerState { ); } - Widget _buildActionButton({ - required String label, - required IconData icon, - required VoidCallback? onPressed, - required Color color, - }) { - final isDisabled = onPressed == null; - final buttonColor = isDisabled ? color.withValues(alpha: 0.5) : color; - - return Container( - margin: const EdgeInsets.only(bottom: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.3), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: Material( - color: buttonColor, - borderRadius: BorderRadius.circular(8), - child: InkWell( - onTap: onPressed, - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - icon, - color: Colors.white, - size: 18, - ), - const SizedBox(width: 6), - Text( - label, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500, - fontSize: 14, - ), - ), - ], - ), - ), - ), - ), - ); - } } diff --git a/lib/components/delivery_list_item.dart b/lib/components/delivery_list_item.dart index 55f4d4b..4099f9a 100644 --- a/lib/components/delivery_list_item.dart +++ b/lib/components/delivery_list_item.dart @@ -95,7 +95,7 @@ class _DeliveryListItemState extends State Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final statusColor = _getStatusColor(widget.delivery); - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); // Collapsed view: Show only the badge if (widget.isCollapsed) { diff --git a/lib/components/navigation_tc_dialog.dart b/lib/components/navigation_tc_dialog.dart index 9fab231..8ff504d 100644 --- a/lib/components/navigation_tc_dialog.dart +++ b/lib/components/navigation_tc_dialog.dart @@ -6,10 +6,10 @@ class NavigationTermsAndConditionsDialog extends StatelessWidget { final VoidCallback? onDecline; const NavigationTermsAndConditionsDialog({ - Key? key, + super.key, required this.onAccept, this.onDecline, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/components/route_list_item.dart b/lib/components/route_list_item.dart index cbc597b..7f870ce 100644 --- a/lib/components/route_list_item.dart +++ b/lib/components/route_list_item.dart @@ -80,7 +80,7 @@ class _RouteListItemState extends State Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final statusColor = _getStatusColor(widget.route); - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); // Collapsed view: Show only the badge if (widget.isCollapsed) { diff --git a/lib/pages/deliveries_page.dart b/lib/pages/deliveries_page.dart index 748fdda..ebcaacc 100644 --- a/lib/pages/deliveries_page.dart +++ b/lib/pages/deliveries_page.dart @@ -88,7 +88,7 @@ class _DeliveriesPageState extends ConsumerState { final deliveriesData = ref.watch(deliveriesProvider(widget.routeFragmentId)); final tokenAsync = ref.watch(authTokenProvider); final token = tokenAsync.hasValue ? tokenAsync.value : null; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); // When embedded in sidebar, show only the delivery list with back button // This is a responsive sidebar that collapses like routes @@ -300,7 +300,7 @@ class _DeliveriesPageState extends ConsumerState { } if (token == null) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); ToastHelper.showError(context, l10n.authenticationRequired); return; } @@ -322,7 +322,7 @@ class _DeliveriesPageState extends ConsumerState { ); result.when( success: (_) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); // ignore: unused_result ref.refresh(deliveriesProvider(widget.routeFragmentId)); // ignore: unused_result @@ -330,7 +330,7 @@ class _DeliveriesPageState extends ConsumerState { ToastHelper.showSuccess(context, l10n.deliverySuccessful); }, onError: (error) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); ToastHelper.showError(context, l10n.error(error.message)); }, ); @@ -343,7 +343,6 @@ class _DeliveriesPageState extends ConsumerState { ); result.when( success: (_) { - final l10n = AppLocalizations.of(context)!; // ignore: unused_result ref.refresh(deliveriesProvider(widget.routeFragmentId)); // ignore: unused_result @@ -351,7 +350,7 @@ class _DeliveriesPageState extends ConsumerState { ToastHelper.showSuccess(context, 'Delivery marked as uncompleted'); }, onError: (error) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); ToastHelper.showError(context, l10n.error(error.message)); }, ); @@ -386,7 +385,7 @@ class _DeliveriesPageState extends ConsumerState { String? token, ) async { if (token == null) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); ToastHelper.showError(context, l10n.authenticationRequired); return; } diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 579f7bf..54ec189 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -79,7 +79,7 @@ class _LoginPageState extends ConsumerState { ), const SizedBox(height: 24), Text( - AppLocalizations.of(context)!.appTitle, + AppLocalizations.of(context).appTitle, textAlign: TextAlign.center, style: Theme.of(context).textTheme.displayMedium?.copyWith( color: Theme.of(context).colorScheme.primary, @@ -88,7 +88,7 @@ class _LoginPageState extends ConsumerState { ), const SizedBox(height: 8), Text( - AppLocalizations.of(context)!.appDescription, + AppLocalizations.of(context).appDescription, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, @@ -98,8 +98,8 @@ class _LoginPageState extends ConsumerState { TextFormField( controller: _usernameController, decoration: InputDecoration( - labelText: AppLocalizations.of(context)!.username, - hintText: AppLocalizations.of(context)!.usernameHint, + labelText: AppLocalizations.of(context).username, + hintText: AppLocalizations.of(context).usernameHint, prefixIcon: const Icon(Icons.person), border: const OutlineInputBorder(), ), @@ -107,7 +107,7 @@ class _LoginPageState extends ConsumerState { enabled: !_isLoading, validator: (value) { if (value == null || value.trim().isEmpty) { - return AppLocalizations.of(context)!.usernameRequired; + return AppLocalizations.of(context).usernameRequired; } return null; }, @@ -116,8 +116,8 @@ class _LoginPageState extends ConsumerState { TextFormField( controller: _passwordController, decoration: InputDecoration( - labelText: AppLocalizations.of(context)!.password, - hintText: AppLocalizations.of(context)!.passwordHint, + labelText: AppLocalizations.of(context).password, + hintText: AppLocalizations.of(context).passwordHint, prefixIcon: const Icon(Icons.lock), border: const OutlineInputBorder(), suffixIcon: IconButton( @@ -137,7 +137,7 @@ class _LoginPageState extends ConsumerState { onFieldSubmitted: (_) => _handleLogin(), validator: (value) { if (value == null || value.isEmpty) { - return AppLocalizations.of(context)!.passwordRequired; + return AppLocalizations.of(context).passwordRequired; } return null; }, @@ -159,7 +159,7 @@ class _LoginPageState extends ConsumerState { ), ), ) - : Text(AppLocalizations.of(context)!.loginButton), + : Text(AppLocalizations.of(context).loginButton), ), ], ), diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index cb4486e..ae7be55 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -11,7 +11,7 @@ class SettingsPage extends ConsumerWidget { final userProfile = ref.watch(userProfileProvider); final languageAsync = ref.watch(languageProvider); final themeMode = ref.watch(themeModeProvider); - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); final language = languageAsync.maybeWhen( data: (value) => value, diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 17cf033..b4b0947 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -57,11 +57,9 @@ final deliveryRoutesProvider = FutureProvider>((ref) async { query: _EmptyQuery(), fromJson: (json) { // API returns data wrapped in object with "data" field - if (json is Map) { - final data = json['data']; - if (data is List) { - return data.map((r) => DeliveryRoute.fromJson(r as Map)).toList(); - } + final data = json['data']; + if (data is List) { + return data.map((r) => DeliveryRoute.fromJson(r as Map)).toList(); } return []; }, @@ -88,11 +86,9 @@ final deliveriesProvider = FutureProvider.family, int>((ref, rout query: _DeliveriesQuery(routeFragmentId: routeFragmentId), fromJson: (json) { // API returns data wrapped in object with "data" field - if (json is Map) { - final data = json['data']; - if (data is List) { - return data.map((d) => Delivery.fromJson(d as Map)).toList(); - } + final data = json['data']; + if (data is List) { + return data.map((d) => Delivery.fromJson(d as Map)).toList(); } return []; }, @@ -100,9 +96,9 @@ final deliveriesProvider = FutureProvider.family, int>((ref, rout // Log error if API call failed result.whenError((error) { - print('ERROR fetching deliveries for route $routeFragmentId: ${error.message}'); + debugPrint('ERROR fetching deliveries for route $routeFragmentId: ${error.message}'); if (error.originalException != null) { - print('Original exception: ${error.originalException}'); + debugPrint('Original exception: ${error.originalException}'); } }); diff --git a/lib/theme/status_colors.dart b/lib/theme/status_colors.dart index 906e549..fb2e88c 100644 --- a/lib/theme/status_colors.dart +++ b/lib/theme/status_colors.dart @@ -194,12 +194,12 @@ class StatusBadgeWidget extends StatelessWidget { final double fontSize; const StatusBadgeWidget({ - Key? key, + super.key, required this.status, this.showIcon = true, this.showLabel = true, this.fontSize = 12, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -242,11 +242,11 @@ class StatusAccentBar extends StatelessWidget { final double height; const StatusAccentBar({ - Key? key, + super.key, required this.status, this.width = 4, this.height = 60, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/lib/utils/logging_interceptor.dart b/lib/utils/logging_interceptor.dart index 5bc3b11..ebc26e5 100644 --- a/lib/utils/logging_interceptor.dart +++ b/lib/utils/logging_interceptor.dart @@ -1,26 +1,27 @@ +import 'package:flutter/foundation.dart'; import 'package:http_interceptor/http_interceptor.dart'; class LoggingInterceptor implements InterceptorContract { @override Future interceptRequest({required BaseRequest request}) async { - print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - print('📤 REQUEST: ${request.method} ${request.url}'); - print('Headers: ${request.headers}'); + debugPrint('----------------------------------------------------'); + debugPrint('REQUEST: ${request.method} ${request.url}'); + debugPrint('Headers: ${request.headers}'); if (request is Request) { - print('Body: ${request.body}'); + debugPrint('Body: ${request.body}'); } - print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + debugPrint('----------------------------------------------------'); return request; } @override Future interceptResponse({required BaseResponse response}) async { - print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - print('📥 RESPONSE: ${response.statusCode} ${response.request?.url}'); + debugPrint('----------------------------------------------------'); + debugPrint('RESPONSE: ${response.statusCode} ${response.request?.url}'); if (response is Response) { - print('Body: ${response.body}'); + debugPrint('Body: ${response.body}'); } - print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + debugPrint('----------------------------------------------------'); return response; }