mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
183 lines
5.5 KiB
Dart
183 lines
5.5 KiB
Dart
// Original code from https://github.com/ueman/image_provider/blob/ed6ff43e1ba69a8ab5a3701d70974cae9cd68a34/dio_image_provider/lib/dio_image_provider.dart
|
|
// Modified by lifegpc <[email protected]>
|
|
import 'dart:async';
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:logging/logging.dart';
|
|
|
|
import '../globals.dart';
|
|
|
|
final _log = Logger("DioImageProvider");
|
|
|
|
/// Fetches the given URL from the network, associating it with the given scale.
|
|
///
|
|
/// The image will be cached regardless of cache headers from the server.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Image.network].
|
|
/// * https://pub.dev/packages/http_image_provider
|
|
@immutable
|
|
class DioImage extends ImageProvider<DioImage> {
|
|
static Dio defaultDio = Dio();
|
|
|
|
/// Creates an object that fetches the image at the given URL.
|
|
///
|
|
/// The arguments [url] and [scale] must not be null.
|
|
/// [dio] will be the default [Dio] if not set.
|
|
DioImage.string(String url,
|
|
{this.scale = 1.0, this.headers, this.onData, Dio? dio, this.key})
|
|
: dio = dio ?? defaultDio,
|
|
url = Uri.parse(url);
|
|
|
|
/// Creates an object that fetches the image at the given URL.
|
|
///
|
|
/// The arguments [url] and [scale] must not be null.
|
|
/// [dio] will be the default [Dio] if not set.
|
|
DioImage(this.url,
|
|
{this.scale = 1.0, this.headers, this.onData, this.key, Dio? dio})
|
|
: dio = dio ?? defaultDio;
|
|
|
|
/// The URL from which the image will be fetched.
|
|
final Uri url;
|
|
|
|
/// The scale to place in the [ImageInfo] object of the image.
|
|
final double scale;
|
|
|
|
/// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
|
|
///
|
|
/// When running flutter on the web, headers are not used.
|
|
final Map<String, String>? headers;
|
|
|
|
/// [dio] will be the default [Dio] if not set.
|
|
final Dio dio;
|
|
|
|
final void Function(Uint8List, Headers, String)? onData;
|
|
|
|
final Key? key;
|
|
|
|
@override
|
|
Future<DioImage> obtainKey(ImageConfiguration configuration) {
|
|
return SynchronousFuture<DioImage>(this);
|
|
}
|
|
|
|
@override
|
|
ImageStreamCompleter loadImage(DioImage key, ImageDecoderCallback decode) {
|
|
// 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 chunkEvents = StreamController<ImageChunkEvent>();
|
|
|
|
return MultiFrameImageStreamCompleter(
|
|
codec: _loadAsync(key, chunkEvents, decode),
|
|
chunkEvents: chunkEvents.stream,
|
|
scale: key.scale,
|
|
debugLabel: key.url.toString(),
|
|
informationCollector: () => <DiagnosticsNode>[
|
|
DiagnosticsProperty<ImageProvider>('Image provider', this),
|
|
DiagnosticsProperty<DioImage>('Image key', key),
|
|
],
|
|
);
|
|
}
|
|
|
|
Future<ui.Codec> _loadAsync(
|
|
DioImage key,
|
|
StreamController<ImageChunkEvent> chunkEvents,
|
|
ImageDecoderCallback decode,
|
|
) async {
|
|
try {
|
|
assert(key == this);
|
|
|
|
if (isImageCacheEnabled) {
|
|
try {
|
|
final cache = await imageCaches.getCache(url.toString());
|
|
if (cache != null) {
|
|
if (onData != null) {
|
|
onData!(cache!.$1, Headers.fromMap(cache!.$2),
|
|
cache!.$3 ?? url.toString());
|
|
}
|
|
final buffer = await ui.ImmutableBuffer.fromUint8List(cache!.$1);
|
|
return decode(buffer);
|
|
}
|
|
} catch (e, stack) {
|
|
_log.warning("Failed to get cache for ${url.toString()}: $e\n$stack");
|
|
}
|
|
}
|
|
|
|
final response = await dio.getUri<dynamic>(
|
|
url,
|
|
options: Options(headers: headers, responseType: ResponseType.bytes),
|
|
onReceiveProgress: (count, total) {
|
|
chunkEvents.add(ImageChunkEvent(
|
|
cumulativeBytesLoaded: count,
|
|
expectedTotalBytes: total >= 0 ? total : null,
|
|
));
|
|
},
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
throw NetworkImageLoadException(
|
|
uri: url,
|
|
statusCode: response.statusCode!,
|
|
);
|
|
}
|
|
|
|
final bytes = Uint8List.fromList(response.data as List<int>);
|
|
|
|
if (bytes.lengthInBytes == 0) {
|
|
throw NetworkImageLoadException(
|
|
uri: url,
|
|
statusCode: response.statusCode!,
|
|
);
|
|
}
|
|
|
|
if (onData != null) {
|
|
onData!(bytes, response.headers, response.realUri.toString());
|
|
}
|
|
|
|
if (isImageCacheEnabled) {
|
|
try {
|
|
await imageCaches.putCache(url.toString(), bytes,
|
|
response.headers.map, response.realUri.toString());
|
|
} catch (e, stack) {
|
|
_log.warning("Failed to put cache for ${url.toString()}: $e\n$stack");
|
|
}
|
|
}
|
|
|
|
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
|
|
return decode(buffer);
|
|
} 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 {
|
|
unawaited(chunkEvents.close());
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) {
|
|
if (other.runtimeType != runtimeType) {
|
|
return false;
|
|
}
|
|
return other is DioImage &&
|
|
other.url == url &&
|
|
other.scale == scale &&
|
|
other.key == key;
|
|
}
|
|
|
|
@override
|
|
int get hashCode => Object.hash(url, scale, key);
|
|
|
|
@override
|
|
String toString() =>
|
|
'${objectRuntimeType(this, 'DioImage')}("$url", scale: $scale)';
|
|
}
|