hwhub/lib/services/storage_service.dart
Lukas Müllner 91f25e0cef feat(v1.1): complete UI overhaul with branded theme, garage, and image uploads
- Custom Material 3 theme with Hot Wheels branding (orange/red gradient, navy accents)
- Locally bundled Poppins font (Regular, Medium, SemiBold, Bold)
- Redesigned login screen with full-bleed background image and frosted glass form
- Bottom navigation shell: My Garage / Scan / Profile tabs
- My Garage screen with grid view, search, stats, detail bottom sheet
- Edit and delete car details from the detail sheet
- Skip button for quick-add with minimal info
- Camera photo upload to Supabase Storage (UUID-based unguessable paths)
- Change/add photo from car detail view
- Profile screen with change password, about dialog, sign out
- Scan tab with camera scanner and manual entry
- Styled scanner screen with crosshair overlay and gradient buttons
- Custom app icon with transparent background and adaptive icon support
- Native splash screen with brand colors
- Auto-refresh garage on tab switch
2026-02-24 07:46:06 +01:00

72 lines
2.2 KiB
Dart

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:uuid/uuid.dart';
import '../main.dart';
/// Handles uploading / deleting car images in Supabase Storage.
///
/// Bucket: `car-images` (public, but URLs are unguessable)
/// Path: `cars/{uuid}.jpg` — random UUID per image.
///
/// Shared garage — any authenticated user can upload / replace / delete.
class StorageService {
StorageService._();
static const _bucket = 'car-images';
static const _uuid = Uuid();
/// Upload a photo from [file].
///
/// If [oldImageUrl] is provided the previous file is deleted first.
/// Returns the public URL on success, or `null` on failure.
static Future<String?> uploadCarImage({
required File file,
String? oldImageUrl,
}) async {
try {
// Clean up old image if re-uploading.
if (oldImageUrl != null) {
await _deleteByUrl(oldImageUrl);
}
final path = 'cars/${_uuid.v4()}.jpg';
await supabase.storage.from(_bucket).upload(
path,
file,
fileOptions: const FileOptions(
contentType: 'image/jpeg',
),
);
// Return the public URL.
final url = supabase.storage.from(_bucket).getPublicUrl(path);
return url;
} catch (e) {
debugPrint('StorageService.uploadCarImage error: $e');
return null;
}
}
/// Delete the image at the given public [imageUrl].
static Future<void> deleteCarImage(String? imageUrl) async {
if (imageUrl == null || imageUrl.isEmpty) return;
await _deleteByUrl(imageUrl);
}
/// Extract the storage path from a public URL and remove the file.
static Future<void> _deleteByUrl(String imageUrl) async {
try {
// Public URLs look like:
// .../storage/v1/object/public/car-images/cars/<uuid>.jpg
final marker = '/object/public/$_bucket/';
final idx = imageUrl.indexOf(marker);
if (idx == -1) return;
final path = imageUrl.substring(idx + marker.length);
await supabase.storage.from(_bucket).remove([path]);
} catch (e) {
debugPrint('StorageService._deleteByUrl error: $e');
}
}
}