/* * @Author : WuWei * @LastEditors : WuWei * @Date : 2022-02-24 17:05:18 * @LastEditTime : 2023-11-02 14:20:44 * @Description : Do not edit */ import 'dart:async'; import 'dart:convert' as convert; import 'dart:io'; import 'dart:ui' as ui; import 'package:crypto/crypto.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; /// The dart:io implementation of [image_provider.NetworkImage]. @immutable class WisLocalCacheNetworkImage extends ImageProvider implements NetworkImage { /// Creates an object that fetches the image at the given URL. /// /// The arguments [url] and [scale] must not be null. const WisLocalCacheNetworkImage( this.url, { this.scale = 1.0, this.headers, // 缓存标识 this.isLocalCache = true, }); @override final String url; @override final double scale; @override final Map? headers; final bool isLocalCache; @override Future obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this); } ImageStreamCompleter load(NetworkImage key) { // Ownership of this controller is handed off to [_loadAsync]; it is that // method's responsibility to close the controller's stream when the image // has been loaded or an error is thrown. final StreamController chunkEvents = StreamController(); return MultiFrameImageStreamCompleter( codec: _loadAsync(key, chunkEvents), chunkEvents: chunkEvents.stream, scale: key.scale, debugLabel: key.url, informationCollector: () { return [ DiagnosticsProperty('Image provider', this), DiagnosticsProperty('Image key', key), ]; }, ); } // Do not access this field directly; use [_httpClient] instead. // We set `autoUncompress` to false to ensure that we can trust the value of // the `Content-Length` HTTP header. We automatically uncompress the content // in our call to [consolidateHttpClientResponseBytes]. static final HttpClient _sharedHttpClient = HttpClient() ..autoUncompress = false; static HttpClient get _httpClient { HttpClient client = _sharedHttpClient; assert(() { if (debugNetworkImageHttpClientProvider != null) client = debugNetworkImageHttpClientProvider!(); return true; }()); return client; } Future _loadAsync( NetworkImage key, StreamController chunkEvents, ) async { try { assert(key == this); /// 如果本地缓存过图片,直接返回图片 if (isLocalCache == true) { final Uint8List? bytes = await _getImageFromLocal(key.url); if (bytes != null && bytes.lengthInBytes != 0) { var data = await ui.ImmutableBuffer.fromUint8List(bytes); return await PaintingBinding.instance .instantiateImageCodecWithSize(data); } } final Uri resolved = Uri.base.resolve(key.url); final HttpClientRequest request = await _httpClient.getUrl(resolved); headers?.forEach((String name, String value) { request.headers.add(name, value); }); final HttpClientResponse response = await request.close(); if (response.statusCode != HttpStatus.ok) { // The network may be only temporarily unavailable, or the file will be // added on the server later. Avoid having future calls to resolve // fail to check the network again. throw NetworkImageLoadException( statusCode: response.statusCode, uri: resolved); } final Uint8List bytes = await consolidateHttpClientResponseBytes( response, onBytesReceived: (int cumulative, int? total) { chunkEvents.add(ImageChunkEvent( cumulativeBytesLoaded: cumulative, expectedTotalBytes: total, )); }, ); /// 网络请求结束后,将图片缓存到本地 if (isLocalCache == true && bytes.lengthInBytes != 0) { _saveImageToLocal(bytes, key.url); } if (bytes.lengthInBytes == 0) throw Exception('NetworkImage is an empty file: $resolved'); var data = await ui.ImmutableBuffer.fromUint8List(bytes); return await PaintingBinding.instance.instantiateImageCodecWithSize(data); } catch (e) { // Depending on where the exception was thrown, the image cache may not // have had a chance to track the key in the cache at all. // Schedule a microtask to give the cache a chance to add the key. scheduleMicrotask(() { PaintingBinding.instance.imageCache.evict(key); }); rethrow; } finally { chunkEvents.close(); } } /// 图片路径通过MD5处理,然后缓存到本地 void _saveImageToLocal(Uint8List mUInt8List, String name) async { String path = await _getCachePathString(name); var file = File(path); bool exist = await file.exists(); if (!exist) { File(path).writeAsBytesSync(mUInt8List); } } /// 从本地拿图片 Future _getImageFromLocal(String name) async { String path = await _getCachePathString(name); var file = File(path); bool exist = await file.exists(); if (exist) { final Uint8List bytes = await file.readAsBytes(); return bytes; } return null; } /// 获取图片的缓存路径并创建 Future _getCachePathString(String name) async { // 获取图片的名称 String filePathFileName = md5.convert(convert.utf8.encode(name)).toString(); String extensionName = name.split('/').last.split('.').last; // print('图片url:$name'); // print('filePathFileName:$filePathFileName'); // print('extensionName:$extensionName'); // 生成、获取结果存储路径 final tempDic = await getTemporaryDirectory(); Directory directory = Directory(tempDic.path + '/CacheImage/'); bool isFoldExist = await directory.exists(); if (!isFoldExist) { await directory.create(); } return directory.path + filePathFileName + '.$extensionName'; } @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) return false; return other is NetworkImage && other.url == url && other.scale == scale; } @override int get hashCode => Object.hash(url, scale); @override String toString() => '${objectRuntimeType(this, 'NetworkImage')}("$url", scale: $scale)'; }