feat: Implement S3 image handling with a new resolver and widget, update agent display, and configure storage base URL.
This commit is contained in:
63
lib/core/utils/image_url_resolver.dart
Normal file
63
lib/core/utils/image_url_resolver.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import 'package:dio/dio.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;
|
||||
} on DioException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void clearCache() => _cache.clear();
|
||||
}
|
||||
Reference in New Issue
Block a user