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

@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:flutter_math_fork/flutter_math.dart';
import 'package:markdown/markdown.dart' as md;
@@ -16,6 +17,8 @@ import 'package:conduit/l10n/app_localizations.dart';
import '../../theme/color_tokens.dart';
import '../../theme/theme_extensions.dart';
import 'code_block_header.dart';
import 'package:conduit/core/network/self_signed_image_cache_manager.dart';
import 'package:conduit/core/network/image_header_utils.dart';
typedef MarkdownLinkTapCallback = void Function(String url, String title);
@@ -413,8 +416,15 @@ class _ImageBuilder extends MarkdownElementBuilder {
BuildContext context,
ConduitThemeExtension theme,
) {
// Read headers and optional self-signed cache manager from Riverpod
final container = ProviderScope.containerOf(context, listen: false);
final headers = buildImageHeadersFromContainer(container);
final cacheManager = container.read(selfSignedImageCacheManagerProvider);
return CachedNetworkImage(
imageUrl: url,
cacheManager: cacheManager,
httpHeaders: headers,
placeholder: (context, _) => Container(
height: 200,
decoration: BoxDecoration(

View File

@@ -3,14 +3,17 @@ import 'dart:typed_data';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../services/brand_service.dart';
import '../theme/theme_extensions.dart';
import 'package:conduit/core/network/self_signed_image_cache_manager.dart';
import 'package:conduit/core/network/image_header_utils.dart';
typedef AvatarWidgetBuilder =
Widget Function(BuildContext context, double size);
class AvatarImage extends StatelessWidget {
class AvatarImage extends ConsumerWidget {
final double size;
final String? imageUrl;
final BorderRadius? borderRadius;
@@ -29,7 +32,7 @@ class AvatarImage extends StatelessWidget {
BorderRadius get _radius => borderRadius ?? BorderRadius.circular(size / 2);
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final url = imageUrl?.trim();
if (url == null || url.isEmpty) {
return fallbackBuilder(context, size);
@@ -53,6 +56,11 @@ class AvatarImage extends StatelessWidget {
return fallbackBuilder(context, size);
}
// Build auth/custom headers when loading from network
final headers = buildImageHeadersFromWidgetRef(ref);
final cacheManager = ref.read(selfSignedImageCacheManagerProvider);
return ClipRRect(
borderRadius: _radius,
child: CachedNetworkImage(
@@ -60,6 +68,8 @@ class AvatarImage extends StatelessWidget {
width: size,
height: size,
fit: BoxFit.cover,
cacheManager: cacheManager,
httpHeaders: headers,
placeholder: (context, _) =>
(placeholderBuilder ?? _defaultPlaceholder)(context, size),
errorWidget: (context, url, error) => fallbackBuilder(context, size),