Skip to content

Commit

Permalink
feat: NetworkImage can be cached locally.
Browse files Browse the repository at this point in the history
  • Loading branch information
mathrunet committed Oct 28, 2023
1 parent 4bd4e9e commit fc00f2f
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation

import path_provider_foundation

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}
126 changes: 107 additions & 19 deletions packages/katana_theme/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ packages:
dependency: transitive
description:
name: build_resolvers
sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20"
sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.4.1"
build_runner:
dependency: "direct dev"
description:
Expand All @@ -93,10 +93,10 @@ packages:
dependency: transitive
description:
name: build_runner_core
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185
url: "https://pub.dev"
source: hosted
version: "7.2.10"
version: "7.2.11"
built_collection:
dependency: transitive
description:
Expand All @@ -109,10 +109,10 @@ packages:
dependency: transitive
description:
name: built_value
sha256: ff627b645b28fb8bdb69e645f910c2458fd6b65f6585c3a53e0626024897dedf
sha256: a8de5955205b4d1dbbbc267daddf2178bd737e4bab8987c04a500478c9651e74
url: "https://pub.dev"
source: hosted
version: "8.6.2"
version: "8.6.3"
characters:
dependency: transitive
description:
Expand Down Expand Up @@ -141,10 +141,10 @@ packages:
dependency: transitive
description:
name: code_builder
sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677"
url: "https://pub.dev"
source: hosted
version: "4.5.0"
version: "4.7.0"
collection:
dependency: transitive
description:
Expand Down Expand Up @@ -173,10 +173,10 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.6"
dart_style:
dependency: transitive
description:
Expand All @@ -189,10 +189,10 @@ packages:
dependency: transitive
description:
name: encrypt
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
url: "https://pub.dev"
source: hosted
version: "5.0.1"
version: "5.0.3"
fake_async:
dependency: transitive
description:
Expand All @@ -201,6 +201,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
file:
dependency: transitive
description:
Expand All @@ -226,10 +234,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
url: "https://pub.dev"
source: hosted
version: "2.0.2"
version: "2.0.3"
flutter_svg:
dependency: "direct main"
description:
Expand Down Expand Up @@ -337,28 +345,28 @@ packages:
path: "../../katana"
relative: true
source: path
version: "2.4.2"
version: "2.4.3"
katana_theme:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "2.0.24"
version: "2.0.29"
katana_theme_annotation:
dependency: "direct overridden"
description:
path: "../../katana_theme_annotation"
relative: true
source: path
version: "2.0.17"
version: "2.0.20"
katana_theme_builder:
dependency: "direct dev"
description:
path: "../../katana_theme_builder"
relative: true
source: path
version: "2.0.21"
version: "2.0.24"
lints:
dependency: transitive
description:
Expand Down Expand Up @@ -439,6 +447,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
path_provider:
dependency: transitive
description:
name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
petitparser:
dependency: transitive
description:
Expand All @@ -447,6 +503,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.4.0"
platform:
dependency: transitive
description:
name: platform
sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d
url: "https://pub.dev"
source: hosted
version: "2.1.6"
pointycastle:
dependency: transitive
description:
Expand Down Expand Up @@ -644,6 +716,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.0"
win32:
dependency: transitive
description:
name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
url: "https://pub.dev"
source: hosted
version: "5.0.9"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
xml:
dependency: transitive
description:
Expand All @@ -662,4 +750,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=2.11.0-0.1.pre"
flutter: ">=3.7.0"
114 changes: 112 additions & 2 deletions packages/katana_theme/lib/src/others/asset.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,20 @@ class _MemoizedNetworkImage extends network_image.NetworkImage {
headers: headers,
);

static final HttpClient _sharedHttpClient = HttpClient()
..autoUncompress = false;

static HttpClient get _httpClient {
HttpClient? client;
assert(() {
if (debugNetworkImageHttpClientProvider != null) {
client = debugNetworkImageHttpClientProvider!();
}
return true;
}());
return client ?? _sharedHttpClient;
}

@override
// ignore: deprecated_member_use
ImageStreamCompleter load(NetworkImage key, DecoderCallback decode) {
Expand All @@ -196,13 +210,109 @@ class _MemoizedNetworkImage extends network_image.NetworkImage {
ImageStreamCompleter loadImage(
NetworkImage key, ImageDecoderCallback decode) {
if (key.url.isEmpty) {
return super.loadImage(key, decode);
return _loadImage(key, decode);
}
final cache = _ImageMemoryCache._getCache(key.url);
if (cache != null) {
return cache;
}
return _ImageMemoryCache._setCache(key.url, super.loadImage(key, decode));
return _ImageMemoryCache._setCache(key.url, _loadImage(key, decode));
}

ImageStreamCompleter _loadImage(
NetworkImage key, ImageDecoderCallback decode) {
final StreamController<ImageChunkEvent> chunkEvents =
StreamController<ImageChunkEvent>();

return MultiFrameImageStreamCompleter(
codec: _loadNetworkAsync(key, chunkEvents, decode: decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
debugLabel: key.url,
informationCollector: () => <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>("Image provider", this),
DiagnosticsProperty<NetworkImage>("Image key", key),
],
);
}

Future<ui.Codec> _loadNetworkAsync(
NetworkImage key,
StreamController<ImageChunkEvent> chunkEvents, {
required ImageDecoderCallback decode,
}) async {
try {
assert(key == this);
final file = await _getLocalFile(key);
if (file.existsSync()) {
return _loadFileAsync(key, file: file, decode: decode);
}

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) {
await response.drain<List<int>>(<int>[]);
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 (bytes.lengthInBytes == 0) {
throw Exception("NetworkImage is an empty file: $resolved");
}
await file.writeAsBytes(bytes);

final ui.ImmutableBuffer buffer =
await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
} catch (e) {
scheduleMicrotask(() {
PaintingBinding.instance.imageCache.evict(key);
});
rethrow;
} finally {
chunkEvents.close();
}
}

Future<ui.Codec> _loadFileAsync(
NetworkImage key, {
required File file,
required ImageDecoderCallback decode,
}) async {
final int lengthInBytes = await file.length();
if (lengthInBytes == 0) {
PaintingBinding.instance.imageCache.evict(key);
throw StateError("$file is empty and cannot be loaded as an image.");
}
if (file.runtimeType == File) {
return decode(await ui.ImmutableBuffer.fromFilePath(file.path));
}
return decode(
await ui.ImmutableBuffer.fromUint8List(await file.readAsBytes()));
}

Future<File> _getLocalFile(NetworkImage key) async {
final cacheDir = await getTemporaryDirectory();
final fileName = key.url.last();
final ext = fileName.contains(".") ? fileName.last(separator: ".") : null;
return File(
"${cacheDir.path}/${fileName.toSHA1()}${ext != null ? ".$ext" : ""}",
);
}
}

Expand Down
Loading

0 comments on commit fc00f2f

Please sign in to comment.