import 'dart:ui' as ui; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:network_image_cached/src/http_request.dart'; import 'bloc/cashed_image_bloc.dart' show CashedImageBloc, CashedImageGetErrorState, CashedImageGetState, CashedImageState, GetStartImageEvent; typedef ImageWidgetBuilder = Widget Function(BuildContext context, ImageProvider imageProvider); class CachedNetworkImageWidget extends StatefulWidget { final String imageUrl; final int count; final double? width; final double? height; final Widget? placeholder; final Widget? errorWidget; final bool cached; final Map? httpHeaders; final String? cacheKey; final Widget? loadwidget; final ImageWidgetBuilder imageBuilder; const CachedNetworkImageWidget({ super.key, required this.imageUrl, required this.cacheKey, this.count = 10, this.height, this.width, this.errorWidget, this.placeholder, this.cached = true, this.loadwidget = const CircularProgressIndicator(), this.httpHeaders, required this.imageBuilder, }); @override CachedNetworkImageWidgetState createState() => CachedNetworkImageWidgetState(); } class CachedNetworkImageWidgetState extends State with AutomaticKeepAliveClientMixin { late final CashedImageBloc bloc; @override void initState() { bloc = CashedImageBloc() ..add( GetStartImageEvent( httpHeaders: widget.httpHeaders, url: widget.imageUrl, cached: widget.cached, count: widget.count, cachkey: widget.cacheKey, ), ); super.initState(); } @override void dispose() { bloc.close(); super.dispose(); } @override bool get wantKeepAlive => true; @override Widget build(BuildContext context) { super.build(context); return BlocProvider( create: (context) => bloc, child: BlocBuilder( buildWhen: (prev, curr) => !mounted, builder: (context, state) { if (state is CashedImageGetErrorState) { return SizedBox( width: widget.width, height: widget.height, child: widget.errorWidget ?? Center(child: Icon(Icons.error)), ); } if (state is CashedImageGetState) { ImageProvider imageProvider = MemoryImage(state.bytes); return widget.imageBuilder(context, imageProvider); } return SizedBox( width: widget.width, height: widget.height, child: widget.placeholder ?? Center(child: widget.loadwidget ?? CircularProgressIndicator()), ); }, ), ); } } class CachedNetworkImageProvider extends ImageProvider { final String imageUrl; final int count; final BoxFit fit; final double? width; final double? height; final Widget? placeholder; final Widget? errorWidget; final bool cached; final String? cacheKey; final Widget? loadwidget; final Map? httpHeaders; const CachedNetworkImageProvider({ required this.imageUrl, required this.cacheKey, required this.fit, this.count = 10, this.height, this.width, this.errorWidget, this.placeholder, this.cached = true, this.loadwidget = const CircularProgressIndicator(), this.httpHeaders, }); @override ImageStreamCompleter loadImage( CachedNetworkImageProvider key, ImageDecoderCallback decode, ) { // 1. Создаём Future, который загрузит и декодирует изображение // 2. Возвращаем ImageStreamCompleter, который управляет потоком загрузки return MultiFrameImageStreamCompleter( codec: _loadAndDecodeImage(decode), scale: 1.0, informationCollector: () => [ DiagnosticsProperty( 'Image provider', key, showName: false, ), ], ); } // Вспомогательный метод: загружает байты и декодирует в ui.Image Future _loadAndDecodeImage(ImageDecoderCallback decode) async { try { // 1. Отправляем HTTP‑запрос HttpGetImage httpGetImage = HttpGetImage( url: imageUrl, count: count, httpHeaders: httpHeaders, ); // 2. Декодируем байты в ui.Image DefaultCacheManager defaultCacheManager = DefaultCacheManager(); if (cached) { FileInfo? fileInfo; if (cacheKey != null) { fileInfo = await defaultCacheManager.getFileFromCache(cacheKey!); } else { fileInfo = await defaultCacheManager.getFileFromCache(imageUrl); } if (fileInfo != null) { final decodedImage = await decode( await ImmutableBuffer.fromUint8List( await fileInfo.file.readAsBytes(), ), ); return decodedImage; } } Uint8List img; try { img = await httpGetImage.getDataObjectIsolate(); if (cached) { if (cacheKey != null) { defaultCacheManager.putFile(cacheKey!, img); } else { defaultCacheManager.putFile(imageUrl, img); } } } catch (e) { throw Exception('Failed to load image: $e'); } final decodedImage = await decode( await ImmutableBuffer.fromUint8List(img), ); return decodedImage; } catch (e) { // Если ошибка — бросаем исключение (будет обработано в Image widget) throw Exception('Failed to load image: $e'); } } @override Future obtainKey( ImageConfiguration configuration, ) { return SynchronousFuture(this); } }