feat(images): surface actionable upload errors for private photo pipeline

- Change uploadCarImage to throw explicit errors instead of returning null
- Handle upload exceptions in garage detail flow with clearer user feedback
- Guide users to retry with a smaller/clearer image when upload constraints fail
This commit is contained in:
Lukas Müllner 2026-03-04 11:31:37 +01:00
parent aab5c7d039
commit d287d175b5
2 changed files with 30 additions and 31 deletions

View file

@ -678,14 +678,18 @@ class GarageScreenState extends State<GarageScreen> {
showGlobalSnackBar('Uploading photo…'); showGlobalSnackBar('Uploading photo…');
final oldPath = car['user_image_url'] as String?; final oldPath = car['user_image_url'] as String?;
final newPath = await StorageService.uploadCarImage( String newPath;
file: File(xFile.path), try {
entryId: car['id'] as int, newPath = await StorageService.uploadCarImage(
oldPath: oldPath, file: File(xFile.path),
); entryId: car['id'] as int,
oldPath: oldPath,
if (newPath == null) { );
showGlobalSnackBar('Failed to upload photo.', isError: true); } catch (e) {
showGlobalSnackBar(
'Failed to upload photo. Please try a smaller/clearer image. ($e)',
isError: true,
);
return; return;
} }

View file

@ -18,34 +18,29 @@ class StorageService {
/// Upload a car image for a specific hotwheels entry. /// Upload a car image for a specific hotwheels entry.
/// Returns the storage path on success (e.g. `uid/123.jpg`). /// Returns the storage path on success (e.g. `uid/123.jpg`).
static Future<String?> uploadCarImage({ static Future<String> uploadCarImage({
required File file, required File file,
required int entryId, required int entryId,
String? oldPath, String? oldPath,
}) async { }) async {
try { if (oldPath != null && oldPath.isNotEmpty) {
if (oldPath != null && oldPath.isNotEmpty) { await deleteCarImage(oldPath);
await deleteCarImage(oldPath);
}
final userId = supabase.auth.currentUser!.id;
final path = '$userId/$entryId.jpg';
final compressed = await _compressImage(file);
await supabase.storage.from(_bucket).uploadBinary(
path,
compressed,
fileOptions: const FileOptions(
upsert: true,
contentType: 'image/jpeg',
),
);
return path;
} catch (e) {
debugPrint('StorageService.uploadCarImage error: $e');
return null;
} }
final userId = supabase.auth.currentUser!.id;
final path = '$userId/$entryId.jpg';
final compressed = await _compressImage(file);
await supabase.storage.from(_bucket).uploadBinary(
path,
compressed,
fileOptions: const FileOptions(
upsert: true,
contentType: 'image/jpeg',
),
);
return path;
} }
/// Generates a temporary signed URL for a private image path. /// Generates a temporary signed URL for a private image path.