feat(ui): support authenticated image loading with cache manager

Add Riverpod-aware image header and cache manager support for network
images used in avatar and markdown widgets. Convert AvatarImage to a
ConsumerWidget and read a self-signed cache manager and HTTP headers
from Riverpod so CachedNetworkImage can send auth/custom headers and use
the provided cache manager. In Markdown image builder, obtain headers
and cache manager from a ProviderContainer (via ProviderScope.containerOf)
to enable the same authenticated loading in non-consumer contexts.

Introduce image_header_utils.dart to centralize building Authorization
and custom headers from auth/api providers, with helpers for Ref,
WidgetRef, and ProviderContainer. Add dependency adjustments in
pubspec.lock for flutter_cache_manager and http marked as direct main.

These changes ensure protected images (self-signed or auth-required)
load correctly across the app and reuse the configured cache manager.
This commit is contained in:
cogwheel0
2025-10-23 17:36:31 +05:30
parent 060d9721af
commit 0df4b4f050
7 changed files with 149 additions and 40 deletions

View File

@@ -13,6 +13,8 @@ import 'package:conduit/l10n/app_localizations.dart';
import '../../../core/providers/app_providers.dart';
import '../../auth/providers/unified_auth_providers.dart';
import '../../../core/utils/debug_logger.dart';
import '../../../core/network/self_signed_image_cache_manager.dart';
import '../../../core/network/image_header_utils.dart';
// Simple global cache to prevent reloading
final _globalImageCache = <String, String>{};
@@ -414,29 +416,15 @@ class _EnhancedImageAttachmentState
Widget _buildNetworkImage() {
// Get authentication headers if available
final api = ref.read(apiServiceProvider);
final authToken = ref.read(authTokenProvider3);
final headers = <String, String>{};
// Add auth token from unified auth provider
if (authToken != null && authToken.isNotEmpty) {
headers['Authorization'] = 'Bearer $authToken';
} else if (api?.serverConfig.apiKey != null &&
api!.serverConfig.apiKey!.isNotEmpty) {
// Fallback to API key from server config
headers['Authorization'] = 'Bearer ${api.serverConfig.apiKey}';
}
// Add any custom headers from server config
if (api != null && api.serverConfig.customHeaders.isNotEmpty) {
headers.addAll(api.serverConfig.customHeaders);
}
final headers = buildImageHeadersFromWidgetRef(ref);
final cacheManager = ref.read(selfSignedImageCacheManagerProvider);
final imageWidget = CachedNetworkImage(
key: ValueKey('image_${widget.attachmentId}'),
imageUrl: _cachedImageData!,
fit: BoxFit.cover,
httpHeaders: headers.isNotEmpty ? headers : null,
cacheManager: cacheManager,
httpHeaders: headers,
fadeInDuration: widget.disableAnimation
? Duration.zero
: const Duration(milliseconds: 200),
@@ -559,28 +547,14 @@ class FullScreenImageViewer extends ConsumerWidget {
if (imageData.startsWith('http')) {
// Get authentication headers if available
final api = ref.read(apiServiceProvider);
final authToken = ref.read(authTokenProvider3);
final headers = <String, String>{};
// Add auth token from unified auth provider
if (authToken != null && authToken.isNotEmpty) {
headers['Authorization'] = 'Bearer $authToken';
} else if (api?.serverConfig.apiKey != null &&
api!.serverConfig.apiKey!.isNotEmpty) {
// Fallback to API key from server config
headers['Authorization'] = 'Bearer ${api.serverConfig.apiKey}';
}
// Add any custom headers from server config
if (api != null && api.serverConfig.customHeaders.isNotEmpty) {
headers.addAll(api.serverConfig.customHeaders);
}
final headers = buildImageHeadersFromWidgetRef(ref);
final cacheManager = ref.read(selfSignedImageCacheManagerProvider);
imageWidget = CachedNetworkImage(
imageUrl: imageData,
fit: BoxFit.contain,
httpHeaders: headers.isNotEmpty ? headers : null,
cacheManager: cacheManager,
httpHeaders: headers,
placeholder: (context, url) => Center(
child: CircularProgressIndicator(
color: context.conduitTheme.buttonPrimary,