|
|
@@ -0,0 +1,224 @@
|
|
|
+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<String, String>? 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<CachedNetworkImageWidget>
|
|
|
+ with AutomaticKeepAliveClientMixin {
|
|
|
+ late final CashedImageBloc bloc;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ bloc = CashedImageBloc();
|
|
|
+
|
|
|
+ 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
|
|
|
+ ..add(
|
|
|
+ GetStartImageEvent(
|
|
|
+ httpHeaders: widget.httpHeaders,
|
|
|
+ url: widget.imageUrl,
|
|
|
+ cached: widget.cached,
|
|
|
+ count: widget.count,
|
|
|
+ cachkey: widget.cacheKey,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: BlocBuilder<CashedImageBloc, CashedImageState>(
|
|
|
+ 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<CachedNetworkImageProvider> {
|
|
|
+ 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<String, String>? 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: () => <DiagnosticsNode>[
|
|
|
+ DiagnosticsProperty<CachedNetworkImageProvider>(
|
|
|
+ 'Image provider',
|
|
|
+ key,
|
|
|
+ showName: false,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Вспомогательный метод: загружает байты и декодирует в ui.Image
|
|
|
+ Future<ui.Codec> _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<CachedNetworkImageProvider> obtainKey(
|
|
|
+ ImageConfiguration configuration,
|
|
|
+ ) {
|
|
|
+ return SynchronousFuture<CachedNetworkImageProvider>(this);
|
|
|
+ }
|
|
|
+}
|