diff --git a/lib/modules/library/widgets/library_entry_utils.dart b/lib/modules/library/widgets/library_entry_utils.dart index 8c0183c5..02322198 100644 --- a/lib/modules/library/widgets/library_entry_utils.dart +++ b/lib/modules/library/widgets/library_entry_utils.dart @@ -6,19 +6,21 @@ import 'package:mangayomi/modules/library/providers/library_filter_provider.dart import 'package:mangayomi/modules/library/providers/library_state_provider.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/modules/manga/detail/providers/state_providers.dart'; -import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart'; import 'package:mangayomi/modules/widgets/manga_image_card_widget.dart'; +import 'package:mangayomi/utils/cached_network.dart'; import 'package:mangayomi/utils/constant.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; import 'package:mangayomi/utils/headers.dart'; /// Resolves the correct [ImageProvider] for a manga entry, preferring a custom -/// local cover over the remote URL. +/// local cover over the remote URL. Remote covers are wrapped in +/// [coverProvider] so they decode at thumbnail resolution rather than the +/// source resolution — see refs #609. ImageProvider resolveCoverImage(Manga entry, WidgetRef ref) { if (entry.customCoverImage != null) { return MemoryImage(entry.customCoverImage as Uint8List); } - return CustomExtendedNetworkImageProvider( + return coverProvider( toImgUrl(entry.customCoverFromTracker ?? entry.imageUrl ?? ''), headers: (entry.isLocalArchive ?? false) ? null diff --git a/lib/modules/widgets/manga_image_card_widget.dart b/lib/modules/widgets/manga_image_card_widget.dart index 6128e9dd..f673408f 100644 --- a/lib/modules/widgets/manga_image_card_widget.dart +++ b/lib/modules/widgets/manga_image_card_widget.dart @@ -9,7 +9,7 @@ import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/models/source.dart'; import 'package:mangayomi/modules/manga/detail/manga_detail_main.dart'; -import 'package:mangayomi/modules/widgets/custom_extended_image_provider.dart'; +import 'package:mangayomi/utils/cached_network.dart'; import 'package:mangayomi/router/router.dart'; import 'package:mangayomi/utils/extensions/build_context_extensions.dart'; import 'package:mangayomi/utils/constant.dart'; @@ -46,7 +46,7 @@ class MangaImageCardWidget extends ConsumerWidget { image: hasData && libraryManga!.customCoverImage != null ? MemoryImage(libraryManga!.customCoverImage as Uint8List) as ImageProvider - : CustomExtendedNetworkImageProvider( + : coverProvider( toImgUrl( hasData ? libraryManga!.customCoverFromTracker ?? @@ -61,7 +61,6 @@ class MangaImageCardWidget extends ConsumerWidget { sourceId: source.id, ), ), - cache: true, cacheMaxAge: const Duration(days: 7), ), onTap: () => _toMangaRdD(ref, context, false), @@ -138,7 +137,7 @@ class MangaImageCardListTileWidget extends ConsumerWidget { final image = hasData && libraryManga!.customCoverImage != null ? MemoryImage(libraryManga!.customCoverImage as Uint8List) as ImageProvider - : CustomExtendedNetworkImageProvider( + : coverProvider( toImgUrl( hasData ? libraryManga!.customCoverFromTracker ?? diff --git a/lib/utils/cached_network.dart b/lib/utils/cached_network.dart index c518fffb..43229435 100644 --- a/lib/utils/cached_network.dart +++ b/lib/utils/cached_network.dart @@ -2,6 +2,47 @@ 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? headers, + int maxBytes = _coverMaxBytes, + bool cache = true, + Duration? cacheMaxAge, +}) { + return ExtendedResizeImage( + CustomExtendedNetworkImageProvider( + url, + headers: headers, + cache: cache, + cacheMaxAge: cacheMaxAge, + ), + maxBytes: maxBytes, + ); +} + Widget cachedNetworkImage({ Map? headers, required String imageUrl,