CashedNetworkImageWidget.dart 6.3 KB

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