Files
mobile-app/lib/core/utils/image_url_resolver.dart

69 lines
2.1 KiB
Dart

import 'package:real_estate_mobile/core/network/api_client.dart';
/// Resolves S3 keys to presigned download URLs via the backend API.
/// Caches results in memory to avoid repeated API calls.
class ImageUrlResolver {
ImageUrlResolver._();
static final ImageUrlResolver instance = ImageUrlResolver._();
final Map<String, String> _cache = {};
final Map<String, Future<String?>> _pending = {};
/// Returns a direct image URL.
/// - If [imageUrl] is already an http(s) URL, returns it as-is.
/// - If it's an S3 key, fetches a presigned download URL from the backend.
Future<String?> resolve(String? imageUrl) async {
if (imageUrl == null || imageUrl.isEmpty) return null;
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
return imageUrl;
}
// Check cache
if (_cache.containsKey(imageUrl)) return _cache[imageUrl];
// Deduplicate in-flight requests for the same key
if (_pending.containsKey(imageUrl)) return _pending[imageUrl];
final future = _fetchPresignedUrl(imageUrl);
_pending[imageUrl] = future;
try {
final result = await future;
if (result != null) _cache[imageUrl] = result;
return result;
} finally {
_pending.remove(imageUrl);
}
}
Future<String?> _fetchPresignedUrl(String key) async {
try {
final response = await ApiClient.instance.dio.get(
'/upload/presigned-download-url',
queryParameters: {'key': key},
);
final data = response.data;
// Backend wraps: { success: true, data: { url: "..." } }
if (data is Map<String, dynamic>) {
final payload = data['data'];
if (payload is Map<String, dynamic>) {
return payload['url'] as String?;
}
// In case response isn't wrapped (direct controller return)
return data['url'] as String?;
}
return null;
} catch (_) {
return null;
}
}
void clearCache() => _cache.clear();
/// Evict a single key from the in-memory cache so the next resolve
/// fetches a fresh presigned URL.
void evict(String? key) {
if (key != null) _cache.remove(key);
}
}