CashedNetworkImageWidget.dart 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import 'dart:ui' as ui;
  2. import 'dart:ui';
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter_bloc/flutter_bloc.dart';
  6. import 'package:flutter_cache_manager/flutter_cache_manager.dart';
  7. import 'package:network_image_cached/src/http_request.dart';
  8. import 'bloc/cashed_image_bloc.dart'
  9. show
  10. CashedImageBloc,
  11. CashedImageGetErrorState,
  12. CashedImageGetState,
  13. CashedImageState,
  14. GetStartImageEvent;
  15. typedef ImageWidgetBuilder =
  16. Widget Function(BuildContext context, ImageProvider imageProvider);
  17. class CachedNetworkImageWidget extends StatefulWidget {
  18. final String imageUrl;
  19. final int count;
  20. final double? width;
  21. final double? height;
  22. final Widget? placeholder;
  23. final Widget? errorWidget;
  24. final bool cached;
  25. final Map<String, String>? httpHeaders;
  26. final String? cacheKey;
  27. final Widget? loadwidget;
  28. final ImageWidgetBuilder imageBuilder;
  29. const CachedNetworkImageWidget({
  30. super.key,
  31. required this.imageUrl,
  32. required this.cacheKey,
  33. this.count = 10,
  34. this.height,
  35. this.width,
  36. this.errorWidget,
  37. this.placeholder,
  38. this.cached = true,
  39. this.loadwidget = const CircularProgressIndicator(),
  40. this.httpHeaders,
  41. required this.imageBuilder,
  42. });
  43. @override
  44. CachedNetworkImageWidgetState createState() =>
  45. CachedNetworkImageWidgetState();
  46. }
  47. class CachedNetworkImageWidgetState extends State<CachedNetworkImageWidget>
  48. with AutomaticKeepAliveClientMixin {
  49. late final CashedImageBloc bloc;
  50. @override
  51. void initState() {
  52. if (!mounted) {
  53. bloc = CashedImageBloc()
  54. ..add(
  55. GetStartImageEvent(
  56. httpHeaders: widget.httpHeaders,
  57. url: widget.imageUrl,
  58. cached: widget.cached,
  59. count: widget.count,
  60. cachkey: widget.cacheKey,
  61. ),
  62. );
  63. }
  64. super.initState();
  65. }
  66. @override
  67. void dispose() {
  68. bloc.close();
  69. super.dispose();
  70. }
  71. @override
  72. bool get wantKeepAlive => true;
  73. @override
  74. Widget build(BuildContext context) {
  75. super.build(context);
  76. return BlocProvider(
  77. create: (context) => bloc,
  78. child: BlocBuilder<CashedImageBloc, CashedImageState>(
  79. builder: (context, state) {
  80. if (state is CashedImageGetErrorState) {
  81. return SizedBox(
  82. width: widget.width,
  83. height: widget.height,
  84. child: widget.errorWidget ?? Center(child: Icon(Icons.error)),
  85. );
  86. }
  87. if (state is CashedImageGetState) {
  88. ImageProvider imageProvider = MemoryImage(state.bytes);
  89. return widget.imageBuilder(context, imageProvider!);
  90. }
  91. return SizedBox(
  92. width: widget.width,
  93. height: widget.height,
  94. child:
  95. widget.placeholder ??
  96. Center(child: widget.loadwidget ?? CircularProgressIndicator()),
  97. );
  98. },
  99. ),
  100. );
  101. }
  102. }
  103. class CachedNetworkImageProvider
  104. extends ImageProvider<CachedNetworkImageProvider> {
  105. final String imageUrl;
  106. final int count;
  107. final BoxFit fit;
  108. final double? width;
  109. final double? height;
  110. final Widget? placeholder;
  111. final Widget? errorWidget;
  112. final bool cached;
  113. final String? cacheKey;
  114. final Widget? loadwidget;
  115. final Map<String, String>? httpHeaders;
  116. const CachedNetworkImageProvider({
  117. required this.imageUrl,
  118. required this.cacheKey,
  119. required this.fit,
  120. this.count = 10,
  121. this.height,
  122. this.width,
  123. this.errorWidget,
  124. this.placeholder,
  125. this.cached = true,
  126. this.loadwidget = const CircularProgressIndicator(),
  127. this.httpHeaders,
  128. });
  129. @override
  130. ImageStreamCompleter loadImage(
  131. CachedNetworkImageProvider key,
  132. ImageDecoderCallback decode,
  133. ) {
  134. // 1. Создаём Future, который загрузит и декодирует изображение
  135. // 2. Возвращаем ImageStreamCompleter, который управляет потоком загрузки
  136. return MultiFrameImageStreamCompleter(
  137. codec: _loadAndDecodeImage(decode),
  138. scale: 1.0,
  139. informationCollector: () => <DiagnosticsNode>[
  140. DiagnosticsProperty<CachedNetworkImageProvider>(
  141. 'Image provider',
  142. key,
  143. showName: false,
  144. ),
  145. ],
  146. );
  147. }
  148. // Вспомогательный метод: загружает байты и декодирует в ui.Image
  149. Future<ui.Codec> _loadAndDecodeImage(ImageDecoderCallback decode) async {
  150. try {
  151. // 1. Отправляем HTTP‑запрос
  152. HttpGetImage httpGetImage = HttpGetImage(
  153. url: imageUrl,
  154. count: count,
  155. httpHeaders: httpHeaders,
  156. );
  157. // 2. Декодируем байты в ui.Image
  158. DefaultCacheManager defaultCacheManager = DefaultCacheManager();
  159. if (cached) {
  160. FileInfo? fileInfo;
  161. if (cacheKey != null) {
  162. fileInfo = await defaultCacheManager.getFileFromCache(cacheKey!);
  163. } else {
  164. fileInfo = await defaultCacheManager.getFileFromCache(imageUrl);
  165. }
  166. if (fileInfo != null) {
  167. final decodedImage = await decode(
  168. await ImmutableBuffer.fromUint8List(
  169. await fileInfo.file.readAsBytes(),
  170. ),
  171. );
  172. return decodedImage;
  173. }
  174. }
  175. Uint8List img;
  176. try {
  177. img = await httpGetImage.getDataObjectIsolate();
  178. if (cached) {
  179. if (cacheKey != null) {
  180. defaultCacheManager.putFile(cacheKey!, img);
  181. } else {
  182. defaultCacheManager.putFile(imageUrl, img);
  183. }
  184. }
  185. } catch (e) {
  186. throw Exception('Failed to load image: $e');
  187. }
  188. final decodedImage = await decode(
  189. await ImmutableBuffer.fromUint8List(img),
  190. );
  191. return decodedImage;
  192. } catch (e) {
  193. // Если ошибка — бросаем исключение (будет обработано в Image widget)
  194. throw Exception('Failed to load image: $e');
  195. }
  196. }
  197. @override
  198. Future<CachedNetworkImageProvider> obtainKey(
  199. ImageConfiguration configuration,
  200. ) {
  201. return SynchronousFuture<CachedNetworkImageProvider>(this);
  202. }
  203. }