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:
parent
aab5c7d039
commit
d287d175b5
2 changed files with 30 additions and 31 deletions
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue