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. bloc = CashedImageBloc()
  53. ..add(
  54. GetStartImageEvent(
  55. httpHeaders: widget.httpHeaders,
  56. url: widget.imageUrl,
  57. cached: widget.cached,
  58. count: widget.count,
  59. cachkey: widget.cacheKey,
  60. ),
  61. );
  62. super.initState();
  63. }
  64. @override
  65. void dispose() {
  66. bloc.close();
  67. super.dispose();
  68. }
  69. @override
  70. bool get wantKeepAlive => true;
  71. @override
  72. Widget build(BuildContext context) {
  73. super.build(context);
  74. return BlocProvider(
  75. create: (context) => bloc,
  76. child: BlocBuilder<CashedImageBloc, CashedImageState>(
  77. buildWhen: (prev, curr) => !mounted,
  78. builder: (context, state) {
  79. if (state is CashedImageGetErrorState) {
  80. return SizedBox(
  81. width: widget.width,
  82. height: widget.height,
  83. child: widget.errorWidget ?? Center(child: Icon(Icons.error)),
  84. );
  85. }
  86. if (state is CashedImageGetState) {
  87. ImageProvider imageProvider = MemoryImage(state.bytes);
  88. return widget.imageBuilder(context, imageProvider);
  89. }
  90. return SizedBox(
  91. width: widget.width,
  92. height: widget.height,
  93. child:
  94. widget.placeholder ??
  95. Center(child: widget.loadwidget ?? CircularProgressIndicator()),
  96. );
  97. },
  98. ),
  99. );
  100. }
  101. }
  102. class CachedNetworkImageProvider
  103. extends ImageProvider<CachedNetworkImageProvider> {
  104. final String imageUrl;
  105. final int count;
  106. final BoxFit fit;
  107. final double? width;
  108. final double? height;
  109. final Widget? placeholder;
  110. final Widget? errorWidget;
  111. final bool cached;
  112. final String? cacheKey;
  113. final Widget? loadwidget;
  114. final Map<String, String>? httpHeaders;
  115. const CachedNetworkImageProvider({
  116. required this.imageUrl,
  117. required this.cacheKey,
  118. required this.fit,
  119. this.count = 10,
  120. this.height,
  121. this.width,
  122. this.errorWidget,
  123. this.placeholder,
  124. this.cached = true,
  125. this.loadwidget = const CircularProgressIndicator(),
  126. this.httpHeaders,
  127. });
  128. @override
  129. ImageStreamCompleter loadImage(
  130. CachedNetworkImageProvider key,
  131. ImageDecoderCallback decode,
  132. ) {
  133. // 1. Создаём Future, который загрузит и декодирует изображение
  134. // 2. Возвращаем ImageStreamCompleter, который управляет потоком загрузки
  135. return MultiFrameImageStreamCompleter(
  136. codec: _loadAndDecodeImage(decode),
  137. scale: 1.0,
  138. informationCollector: () => <DiagnosticsNode>[
  139. DiagnosticsProperty<CachedNetworkImageProvider>(
  140. 'Image provider',
  141. key,
  142. showName: false,
  143. ),
  144. ],
  145. );
  146. }
  147. // Вспомогательный метод: загружает байты и декодирует в ui.Image
  148. Future<ui.Codec> _loadAndDecodeImage(ImageDecoderCallback decode) async {
  149. try {
  150. // 1. Отправляем HTTP‑запрос
  151. HttpGetImage httpGetImage = HttpGetImage(
  152. url: imageUrl,
  153. count: count,
  154. httpHeaders: httpHeaders,
  155. );
  156. // 2. Декодируем байты в ui.Image
  157. DefaultCacheManager defaultCacheManager = DefaultCacheManager();
  158. if (cached) {
  159. FileInfo? fileInfo;
  160. if (cacheKey != null) {
  161. fileInfo = await defaultCacheManager.getFileFromCache(cacheKey!);
  162. } else {
  163. fileInfo = await defaultCacheManager.getFileFromCache(imageUrl);
  164. }
  165. if (fileInfo != null) {
  166. final decodedImage = await decode(
  167. await ImmutableBuffer.fromUint8List(
  168. await fileInfo.file.readAsBytes(),
  169. ),
  170. );
  171. return decodedImage;
  172. }
  173. }
  174. Uint8List img;
  175. try {
  176. img = await httpGetImage.getDataObjectIsolate();
  177. if (cached) {
  178. if (cacheKey != null) {
  179. defaultCacheManager.putFile(cacheKey!, img);
  180. } else {
  181. defaultCacheManager.putFile(imageUrl, img);
  182. }
  183. }
  184. } catch (e) {
  185. throw Exception('Failed to load image: $e');
  186. }
  187. final decodedImage = await decode(
  188. await ImmutableBuffer.fromUint8List(img),
  189. );
  190. return decodedImage;
  191. } catch (e) {
  192. // Если ошибка — бросаем исключение (будет обработано в Image widget)
  193. throw Exception('Failed to load image: $e');
  194. }
  195. }
  196. @override
  197. Future<CachedNetworkImageProvider> obtainKey(
  198. ImageConfiguration configuration,
  199. ) {
  200. return SynchronousFuture<CachedNetworkImageProvider>(this);
  201. }
  202. }