ente/lib/ui/viewer/file/thumbnail_widget.dart

266 lines
8 KiB
Dart
Raw Normal View History

2020-03-28 18:18:27 +00:00
import 'package:flutter/material.dart';
2021-10-30 05:19:05 +00:00
import 'package:logging/logging.dart';
2020-05-04 20:44:34 +00:00
import 'package:photos/core/cache/thumbnail_cache.dart';
2021-07-21 20:47:43 +00:00
import 'package:photos/core/constants.dart';
2021-05-03 15:18:56 +00:00
import 'package:photos/core/errors.dart';
2021-04-21 13:09:18 +00:00
import 'package:photos/core/event_bus.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/db/trash_db.dart';
import 'package:photos/events/files_updated_event.dart';
2021-04-21 13:09:18 +00:00
import 'package:photos/events/local_photos_updated_event.dart';
2020-06-19 23:03:26 +00:00
import 'package:photos/models/file.dart';
2020-06-20 10:03:45 +00:00
import 'package:photos/models/file_type.dart';
import 'package:photos/models/trash_file.dart';
2022-07-02 17:30:04 +00:00
import 'package:photos/ui/viewer/file/file_icons_widget.dart';
import 'package:photos/utils/file_util.dart';
2021-05-02 20:52:56 +00:00
import 'package:photos/utils/thumbnail_util.dart';
2020-03-28 18:18:27 +00:00
2020-04-25 09:12:13 +00:00
class ThumbnailWidget extends StatefulWidget {
final File file;
final BoxFit fit;
final bool shouldShowSyncStatus;
2022-03-21 11:35:01 +00:00
final bool shouldShowArchiveStatus;
final bool shouldShowLivePhotoOverlay;
final Duration diskLoadDeferDuration;
final Duration serverLoadDeferDuration;
2021-03-25 20:05:39 +00:00
ThumbnailWidget(
this.file, {
2020-03-28 18:18:27 +00:00
Key key,
this.fit = BoxFit.cover,
this.shouldShowSyncStatus = true,
this.shouldShowLivePhotoOverlay = false,
2022-03-21 11:35:01 +00:00
this.shouldShowArchiveStatus = false,
this.diskLoadDeferDuration,
this.serverLoadDeferDuration,
2021-05-08 18:05:51 +00:00
}) : super(key: key ?? Key(file.tag()));
2020-03-28 18:18:27 +00:00
@override
2022-07-03 09:45:00 +00:00
State<ThumbnailWidget> createState() => _ThumbnailWidgetState();
2020-03-28 18:18:27 +00:00
}
2020-04-25 09:12:13 +00:00
class _ThumbnailWidgetState extends State<ThumbnailWidget> {
2020-05-12 16:47:02 +00:00
static final _logger = Logger("ThumbnailWidget");
2020-05-04 14:08:26 +00:00
bool _hasLoadedThumbnail = false;
bool _isLoadingLocalThumbnail = false;
bool _errorLoadingLocalThumbnail = false;
bool _isLoadingRemoteThumbnail = false;
bool _errorLoadingRemoteThumbnail = false;
2020-04-25 10:28:22 +00:00
ImageProvider _imageProvider;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
Future.delayed(Duration(milliseconds: 10), () {
// Cancel request only if the widget has been unmounted
2021-07-19 05:37:34 +00:00
if (!mounted && widget.file.isRemoteFile() && !_hasLoadedThumbnail) {
removePendingGetThumbnailRequestIfAny(widget.file);
}
});
}
2021-05-07 12:11:58 +00:00
@override
void didUpdateWidget(ThumbnailWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.file.generatedID != oldWidget.file.generatedID) {
_reset();
}
}
2020-03-28 18:18:27 +00:00
@override
Widget build(BuildContext context) {
2021-07-19 05:37:34 +00:00
if (widget.file.isRemoteFile()) {
2020-08-13 21:33:31 +00:00
_loadNetworkImage();
} else {
_loadLocalImage(context);
2020-05-25 14:35:06 +00:00
}
2021-08-05 19:25:15 +00:00
Widget image;
2020-08-13 21:33:31 +00:00
if (_imageProvider != null) {
image = Image(
image: _imageProvider,
fit: widget.fit,
);
}
2022-07-03 05:37:04 +00:00
// todo: [2ndJuly22] pref-review if the content Widget which depends on
// thumbnail fetch logic should be part of separate stateFull widget.
// If yes, parent thumbnail widget can be stateless
2021-08-05 19:25:15 +00:00
Widget content;
if (image != null) {
2022-07-03 05:37:04 +00:00
List<Widget> contentChildren = [image];
if (widget.file.fileType == FileType.video) {
2022-07-03 05:37:04 +00:00
contentChildren.add(const VideoOverlayIcon());
} else if (widget.file.fileType == FileType.livePhoto &&
widget.shouldShowLivePhotoOverlay) {
2022-07-03 05:37:04 +00:00
contentChildren.add(const LivePhotoOverlayIcon());
2020-06-20 10:03:45 +00:00
}
2022-07-03 05:37:04 +00:00
content = contentChildren.length == 1
? contentChildren.first
: Stack(
fit: StackFit.expand,
2022-07-03 09:45:00 +00:00
children: contentChildren,
2022-07-03 05:37:04 +00:00
);
2020-05-25 14:35:06 +00:00
}
2022-07-03 05:37:04 +00:00
List<Widget> viewChildren = [
const ThumbnailPlaceHolder(),
2022-03-21 11:35:01 +00:00
AnimatedOpacity(
opacity: content == null ? 0 : 1.0,
duration: Duration(milliseconds: 200),
child: content,
2022-07-03 05:37:04 +00:00
)
2022-03-21 11:35:01 +00:00
];
2022-07-03 05:37:04 +00:00
if (widget.shouldShowSyncStatus && widget.file.uploadedFileID == null) {
viewChildren.add(const UnSyncedIcon());
}
if (widget.file is TrashFile) {
viewChildren.add(TrashedFileOverlayText(widget.file));
}
2022-07-02 17:30:04 +00:00
// todo: Move this icon overlay to the collection widget.
2022-03-21 11:35:01 +00:00
if (widget.shouldShowArchiveStatus) {
2022-07-03 05:37:04 +00:00
viewChildren.add(const ArchiveOverlayIcon());
2022-03-21 11:35:01 +00:00
}
2022-07-03 05:37:04 +00:00
2020-06-23 18:50:10 +00:00
return Stack(
fit: StackFit.expand,
2022-07-03 09:45:00 +00:00
children: viewChildren,
2020-06-23 18:50:10 +00:00
);
2020-05-25 14:35:06 +00:00
}
void _loadLocalImage(BuildContext context) {
2020-08-13 21:33:31 +00:00
if (!_hasLoadedThumbnail &&
!_errorLoadingLocalThumbnail &&
!_isLoadingLocalThumbnail) {
_isLoadingLocalThumbnail = true;
2020-04-25 10:28:22 +00:00
final cachedSmallThumbnail =
2021-07-21 20:47:43 +00:00
ThumbnailLruCache.get(widget.file, kThumbnailSmallSize);
2020-04-25 10:28:22 +00:00
if (cachedSmallThumbnail != null) {
_imageProvider = Image.memory(cachedSmallThumbnail).image;
_hasLoadedThumbnail = true;
2020-05-04 14:08:26 +00:00
} else {
if (widget.diskLoadDeferDuration != null) {
Future.delayed(widget.diskLoadDeferDuration, () {
2021-04-26 07:29:01 +00:00
if (mounted) {
_getThumbnailFromDisk();
}
2020-06-13 16:44:16 +00:00
});
2021-04-26 07:29:01 +00:00
} else {
_getThumbnailFromDisk();
}
2020-04-25 10:28:22 +00:00
}
}
2020-05-25 14:35:06 +00:00
}
2020-03-28 18:18:27 +00:00
2021-04-26 07:29:01 +00:00
Future _getThumbnailFromDisk() async {
getThumbnailFromLocal(widget.file).then((thumbData) async {
if (thumbData == null) {
if (widget.file.uploadedFileID != null) {
_logger.fine("Removing localID reference for " + widget.file.tag());
widget.file.localID = null;
if (widget.file is TrashFile) {
TrashDB.instance.update(widget.file);
} else {
FilesDB.instance.update(widget.file);
}
2021-04-26 07:29:01 +00:00
_loadNetworkImage();
} else {
if (await doesLocalFileExist(widget.file) == false) {
_logger.info("Deleting file " + widget.file.tag());
FilesDB.instance.deleteLocalFile(widget.file);
2022-06-11 08:23:52 +00:00
Bus.instance.fire(
LocalPhotosUpdatedEvent(
[widget.file],
type: EventType.deletedFromDevice,
),
);
}
2021-04-26 07:29:01 +00:00
}
return;
}
if (thumbData != null && mounted) {
final imageProvider = Image.memory(thumbData).image;
_cacheAndRender(imageProvider);
}
ThumbnailLruCache.put(widget.file, thumbData, kThumbnailSmallSize);
2021-04-26 07:29:01 +00:00
}).catchError((e) {
_logger.warning("Could not load image: ", e);
_errorLoadingLocalThumbnail = true;
2021-04-26 07:29:01 +00:00
});
}
2020-08-13 21:33:31 +00:00
void _loadNetworkImage() {
if (!_hasLoadedThumbnail &&
!_errorLoadingRemoteThumbnail &&
!_isLoadingRemoteThumbnail) {
_isLoadingRemoteThumbnail = true;
final cachedThumbnail = ThumbnailLruCache.get(widget.file);
if (cachedThumbnail != null) {
_imageProvider = Image.memory(cachedThumbnail).image;
_hasLoadedThumbnail = true;
return;
}
if (widget.serverLoadDeferDuration != null) {
Future.delayed(widget.serverLoadDeferDuration, () {
2021-04-26 07:29:01 +00:00
if (mounted) {
_getThumbnailFromServer();
}
});
} else {
_getThumbnailFromServer();
}
}
2020-03-28 18:18:27 +00:00
}
2020-04-27 13:02:29 +00:00
2021-05-03 15:18:56 +00:00
void _getThumbnailFromServer() async {
try {
final thumbnail = await getThumbnailFromServer(widget.file);
if (mounted) {
final imageProvider = Image.memory(thumbnail).image;
2021-05-04 18:27:38 +00:00
_cacheAndRender(imageProvider);
}
2021-05-03 15:18:56 +00:00
} catch (e) {
if (e is RequestCancelledError) {
if (mounted) {
2021-05-04 18:27:38 +00:00
_logger.info(
2022-06-11 08:23:52 +00:00
"Thumbnail request was aborted although it is in view, will retry",
);
2021-05-03 15:18:56 +00:00
_reset();
2021-05-07 12:11:58 +00:00
setState(() {});
2021-05-03 15:18:56 +00:00
}
} else {
_logger.severe("Could not load image " + widget.file.toString(), e);
_errorLoadingRemoteThumbnail = true;
2021-05-03 15:18:56 +00:00
}
}
}
2021-05-04 18:27:38 +00:00
void _cacheAndRender(ImageProvider<Object> imageProvider) {
if (imageCache.currentSizeBytes > 256 * 1024 * 1024) {
_logger.info("Clearing image cache");
imageCache.clear();
imageCache.clearLiveImages();
}
precacheImage(imageProvider, context).then((value) {
if (mounted) {
setState(() {
_imageProvider = imageProvider;
_hasLoadedThumbnail = true;
});
}
});
}
2021-05-03 15:18:56 +00:00
void _reset() {
2021-05-07 12:11:58 +00:00
_hasLoadedThumbnail = false;
_isLoadingLocalThumbnail = false;
_isLoadingRemoteThumbnail = false;
_errorLoadingLocalThumbnail = false;
_errorLoadingRemoteThumbnail = false;
2021-05-07 12:11:58 +00:00
_imageProvider = null;
2021-05-03 15:18:56 +00:00
}
2020-03-28 18:18:27 +00:00
}