mirror of
https://github.com/kodjodevf/mangayomi.git
synced 2026-05-23 15:52:16 +00:00
Refs #609 (high RAM with stutters). Manga / anime covers from sources are typically 720x1080 or larger (~3 MB decoded RGBA per cover). The library grid, library list and generic browse / search card widgets render those covers at roughly 150x220 logical pixels, but every cover decoded to its full source resolution and that decoded bitmap landed in Flutter's `imageCache`. With 30-50 covers in flight during a normal scroll, the default 100 MB cache filled and the engine started evicting + re-decoding aggressively — exactly the symptom in #609 (stutters + high RAM). Mangayomi already had `ExtendedResizeImage` available via the `extended_image_library` package and used it in one place (`cachedCompressedNetworkImage`, called only from the History screen). This commit generalises that pattern. Add a `coverProvider()` helper in `lib/utils/cached_network.dart` that wraps `CustomExtendedNetworkImageProvider` in `ExtendedResizeImage` with a 200 KB encoded budget — sharp at typical thumbnail size on high-DPR screens, ~3.6x smaller decoded than a full-resolution cover. Pass through the same `cache` / `cacheMaxAge` knobs the underlying provider exposes so existing disk-cache behaviour is preserved. Swap the three high-traffic thumbnail call sites to use it: * `lib/modules/library/widgets/library_gridview_widget.dart` * `lib/modules/library/widgets/library_listview_widget.dart` * `lib/modules/widgets/manga_image_card_widget.dart` (both `MangaImageCardWidget` and `MangaImageCardListTileWidget`, used by browse and search results) Deliberately not changed: * The manga / anime detail page hero cover — large display, full resolution is appropriate. * Reader pages — already memory-managed by `ChapterPreloadManager` and need full resolution for actual reading. * `cachedNetworkImage()` and other lower-traffic thumbnail surfaces (tracker results, calendar, recommendation grid). Easy to extend in a follow-up if anyone asks; kept narrow here so review is manageable. Verified * `flutter analyze` clean on every touched file * `flutter build macos --release` succeeds * Smoke-tested on macOS with the local-all-fixes build: library grid, library list and browse card all render identical-looking covers at typical thumbnail sizes; no visible quality regression at the displayed scale
107 lines
3.5 KiB
Dart
107 lines
3.5 KiB
Dart
import 'package:extended_image/extended_image.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart';
|
|
|
|
/// Default upper bound on the encoded byte size of a thumbnail-sized cover
|
|
/// after `ExtendedResizeImage` resamples. 200 KB roughly maps to a 400x600
|
|
/// JPEG / WebP — sharp enough at typical grid / list display sizes on
|
|
/// high-DPR screens, while keeping the *decoded* RAM footprint small (the
|
|
/// resampled image goes into Flutter's `imageCache` decoded, where every
|
|
/// saved KB matters).
|
|
const int _coverMaxBytes = 200 << 10;
|
|
|
|
/// Returns an `ImageProvider` for a manga / anime cover URL that decodes at
|
|
/// thumbnail resolution rather than the source resolution.
|
|
///
|
|
/// Source covers are commonly 720x1080 or larger (~3 MB decoded RGBA per
|
|
/// cover). When used directly in a library grid or list, every visible
|
|
/// thumbnail fills `imageCache` with a 3 MB blob even though it renders at
|
|
/// ~150x220 logical pixels. Wrapping the provider in `ExtendedResizeImage`
|
|
/// instructs the decoder to resample to a much smaller bitmap before it
|
|
/// hits the cache, so the same in-memory budget holds far more thumbnails
|
|
/// and scrolling stops thrashing.
|
|
///
|
|
/// Use this for thumbnail call sites (library grid / list, browse search
|
|
/// cards, tracker results, calendar, etc.). Do *not* use it for large hero
|
|
/// covers (manga detail page) or for reader pages, which need full
|
|
/// resolution.
|
|
ImageProvider coverProvider(
|
|
String url, {
|
|
Map<String, String>? headers,
|
|
int maxBytes = _coverMaxBytes,
|
|
bool cache = true,
|
|
Duration? cacheMaxAge,
|
|
}) {
|
|
return ExtendedResizeImage(
|
|
CustomExtendedNetworkImageProvider(
|
|
url,
|
|
headers: headers,
|
|
cache: cache,
|
|
cacheMaxAge: cacheMaxAge,
|
|
),
|
|
maxBytes: maxBytes,
|
|
);
|
|
}
|
|
|
|
Widget cachedNetworkImage({
|
|
Map<String, String>? headers,
|
|
required String imageUrl,
|
|
required double? width,
|
|
required double? height,
|
|
required BoxFit? fit,
|
|
AlignmentGeometry? alignment,
|
|
bool useCustomNetworkImage = true,
|
|
Widget errorWidget = const Icon(Icons.error, size: 50),
|
|
}) {
|
|
return ExtendedImage(
|
|
image: useCustomNetworkImage
|
|
? CustomExtendedNetworkImageProvider(imageUrl, headers: headers)
|
|
: ExtendedNetworkImageProvider(imageUrl, headers: headers),
|
|
width: width,
|
|
height: height,
|
|
fit: fit,
|
|
filterQuality: FilterQuality.medium,
|
|
mode: ExtendedImageMode.none,
|
|
handleLoadingProgress: true,
|
|
loadStateChanged: (state) {
|
|
if (state.extendedImageLoadState == LoadState.failed) {
|
|
return errorWidget;
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget cachedCompressedNetworkImage({
|
|
Map<String, String>? headers,
|
|
required String imageUrl,
|
|
required double? width,
|
|
required double? height,
|
|
required BoxFit? fit,
|
|
AlignmentGeometry? alignment,
|
|
bool useCustomNetworkImage = true,
|
|
Widget errorWidget = const Icon(Icons.error, size: 50),
|
|
int maxBytes = 5 << 10,
|
|
}) {
|
|
return ExtendedImage(
|
|
image: ExtendedResizeImage(
|
|
useCustomNetworkImage
|
|
? CustomExtendedNetworkImageProvider(imageUrl, headers: headers)
|
|
: ExtendedNetworkImageProvider(imageUrl, headers: headers),
|
|
maxBytes: maxBytes,
|
|
),
|
|
width: width,
|
|
height: height,
|
|
fit: fit,
|
|
filterQuality: FilterQuality.medium,
|
|
mode: ExtendedImageMode.none,
|
|
handleLoadingProgress: true,
|
|
clearMemoryCacheWhenDispose: true,
|
|
loadStateChanged: (state) {
|
|
if (state.extendedImageLoadState == LoadState.failed) {
|
|
return errorWidget;
|
|
}
|
|
return null;
|
|
},
|
|
);
|
|
}
|