multipage: thumbnail request cancellation

This commit is contained in:
Thibault Deckers 2021-02-04 17:45:46 +09:00
parent ff517925f6
commit 8fa3f18aef
7 changed files with 35 additions and 16 deletions

View file

@ -172,6 +172,15 @@ class AvesEntry {
addressChangeNotifier.dispose(); addressChangeNotifier.dispose();
} }
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
return other is AvesEntry && other.uri == uri && other.pageId == pageId && other._dateModifiedSecs == _dateModifiedSecs;
}
@override
int get hashCode => hashValues(uri, pageId, _dateModifiedSecs);
@override @override
String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, path=$path, pageId=$pageId}'; String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, path=$path, pageId=$pageId}';

View file

@ -1,11 +1,13 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
class MultiPageInfo { class MultiPageInfo {
final String uri;
final List<SinglePageInfo> pages; final List<SinglePageInfo> pages;
int get pageCount => pages.length; int get pageCount => pages.length;
MultiPageInfo({ MultiPageInfo({
@required this.uri,
this.pages, this.pages,
}) { }) {
if (pages.isNotEmpty) { if (pages.isNotEmpty) {
@ -18,8 +20,11 @@ class MultiPageInfo {
} }
} }
factory MultiPageInfo.fromPageMaps(List<Map> pageMaps) { factory MultiPageInfo.fromPageMaps(String uri, List<Map> pageMaps) {
return MultiPageInfo(pages: pageMaps.map((page) => SinglePageInfo.fromMap(page)).toList()); return MultiPageInfo(
uri: uri,
pages: pageMaps.map((page) => SinglePageInfo.fromMap(page)).toList(),
);
} }
SinglePageInfo get defaultPage => pages.firstWhere((page) => page.isDefault, orElse: () => null); SinglePageInfo get defaultPage => pages.firstWhere((page) => page.isDefault, orElse: () => null);
@ -29,7 +34,7 @@ class MultiPageInfo {
SinglePageInfo getById(int pageId) => pages.firstWhere((page) => page.pageId == pageId, orElse: () => null); SinglePageInfo getById(int pageId) => pages.firstWhere((page) => page.pageId == pageId, orElse: () => null);
@override @override
String toString() => '$runtimeType#${shortHash(this)}{pages=$pages}'; String toString() => '$runtimeType#${shortHash(this)}{uri=$uri, pages=$pages}';
} }
class SinglePageInfo implements Comparable<SinglePageInfo> { class SinglePageInfo implements Comparable<SinglePageInfo> {

View file

@ -89,7 +89,7 @@ class MetadataService {
'uri': entry.uri, 'uri': entry.uri,
}); });
final pageMaps = (result as List).cast<Map>(); final pageMaps = (result as List).cast<Map>();
return MultiPageInfo.fromPageMaps(pageMaps); return MultiPageInfo.fromPageMaps(entry.uri, pageMaps);
} on PlatformException catch (e) { } on PlatformException catch (e) {
debugPrint('getMultiPageInfo failed with code=${e.code}, exception=${e.message}, details=${e.details}'); debugPrint('getMultiPageInfo failed with code=${e.code}, exception=${e.message}, details=${e.details}');
} }

View file

@ -43,7 +43,10 @@ class InteractiveThumbnail extends StatelessWidget {
entry: entry, entry: entry,
extent: tileExtent, extent: tileExtent,
collection: collection, collection: collection,
isScrollingNotifier: isScrollingNotifier, // when the user is scrolling faster than we can retrieve the thumbnails,
// the retrieval task queue can pile up for thumbnails that got disposed
// in this case we pause the image retrieval task to get it out of the queue
cancellableNotifier: isScrollingNotifier,
), ),
), ),
); );

View file

@ -9,7 +9,7 @@ class DecoratedThumbnail extends StatelessWidget {
final AvesEntry entry; final AvesEntry entry;
final double extent; final double extent;
final CollectionLens collection; final CollectionLens collection;
final ValueNotifier<bool> isScrollingNotifier; final ValueNotifier<bool> cancellableNotifier;
final bool selectable, highlightable; final bool selectable, highlightable;
static final Color borderColor = Colors.grey.shade700; static final Color borderColor = Colors.grey.shade700;
@ -20,7 +20,7 @@ class DecoratedThumbnail extends StatelessWidget {
@required this.entry, @required this.entry,
@required this.extent, @required this.extent,
this.collection, this.collection,
this.isScrollingNotifier, this.cancellableNotifier,
this.selectable = true, this.selectable = true,
this.highlightable = true, this.highlightable = true,
}) : super(key: key); }) : super(key: key);
@ -40,7 +40,7 @@ class DecoratedThumbnail extends StatelessWidget {
: RasterImageThumbnail( : RasterImageThumbnail(
entry: entry, entry: entry,
extent: extent, extent: extent,
isScrollingNotifier: isScrollingNotifier, cancellableNotifier: cancellableNotifier,
heroTag: heroTag, heroTag: heroTag,
); );

View file

@ -9,14 +9,14 @@ import 'package:flutter/material.dart';
class RasterImageThumbnail extends StatefulWidget { class RasterImageThumbnail extends StatefulWidget {
final AvesEntry entry; final AvesEntry entry;
final double extent; final double extent;
final ValueNotifier<bool> isScrollingNotifier; final ValueNotifier<bool> cancellableNotifier;
final Object heroTag; final Object heroTag;
const RasterImageThumbnail({ const RasterImageThumbnail({
Key key, Key key,
@required this.entry, @required this.entry,
@required this.extent, @required this.extent,
this.isScrollingNotifier, this.cancellableNotifier,
this.heroTag, this.heroTag,
}) : super(key: key); }) : super(key: key);
@ -70,11 +70,7 @@ class _RasterImageThumbnailState extends State<RasterImageThumbnail> {
} }
void _pauseProvider() { void _pauseProvider() {
final isScrolling = widget.isScrollingNotifier?.value ?? false; if (widget.cancellableNotifier?.value ?? false) {
// when the user is scrolling faster than we can retrieve the thumbnails,
// the retrieval task queue can pile up for thumbnails that got disposed
// in this case we pause the image retrieval task to get it out of the queue
if (isScrolling) {
_fastThumbnailProvider?.pause(); _fastThumbnailProvider?.pause();
_sizedThumbnailProvider?.pause(); _sizedThumbnailProvider?.pause();
} }

View file

@ -27,6 +27,7 @@ class MultiPageOverlay extends StatefulWidget {
} }
class _MultiPageOverlayState extends State<MultiPageOverlay> { class _MultiPageOverlayState extends State<MultiPageOverlay> {
final _cancellableNotifier = ValueNotifier(true);
ScrollController _scrollController; ScrollController _scrollController;
bool _syncScroll = true; bool _syncScroll = true;
@ -90,7 +91,8 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
future: controller.info, future: controller.info,
builder: (context, snapshot) { builder: (context, snapshot) {
final multiPageInfo = snapshot.data; final multiPageInfo = snapshot.data;
if ((multiPageInfo?.pageCount ?? 0) <= 1) return SizedBox.shrink(); if ((multiPageInfo?.pageCount ?? 0) <= 1) return SizedBox();
if (multiPageInfo.uri != mainEntry.uri) return SizedBox();
return Container( return Container(
height: extent + separatorWidth * 2, height: extent + separatorWidth * 2,
child: Stack( child: Stack(
@ -125,6 +127,10 @@ class _MultiPageOverlayState extends State<MultiPageOverlay> {
child: DecoratedThumbnail( child: DecoratedThumbnail(
entry: pageEntry, entry: pageEntry,
extent: extent, extent: extent,
// the retrieval task queue can pile up for thumbnails of heavy pages
// (e.g. thumbnails of 15MP HEIF images inside 100MB+ HEIC containers)
// so we cancel these requests when possible
cancellableNotifier: _cancellableNotifier,
selectable: false, selectable: false,
highlightable: false, highlightable: false,
), ),