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 <noreply@anthropic.com>
This commit is contained in:
Mathias Beaulieu-Duncan 2026-01-20 11:30:49 -05:00
parent bcc938fde1
commit e5f267b4f7
6 changed files with 317 additions and 2 deletions

View File

@ -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<bool?> show(
BuildContext context, {
required File imageFile,
required String deliveryName,
}) {
return showDialog<bool>(
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),
),
],
);
}
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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

View File

@ -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';
}

View File

@ -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';
}