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

342 lines
11 KiB
Dart
Raw Normal View History

import 'package:flutter/foundation.dart';
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';
2023-01-11 04:39:33 +00:00
import 'package:photos/core/cache/thumbnail_in_memory_cache.dart';
import 'package:photos/core/configuration.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';
import 'package:photos/models/collection.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-12-16 04:48:15 +00:00
import 'package:photos/services/collections_service.dart';
2022-11-17 05:32:24 +00:00
import 'package:photos/services/favorites_service.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;
2022-11-17 05:32:24 +00:00
final bool showFavForAlbumOnly;
final bool shouldShowLivePhotoOverlay;
final Duration? diskLoadDeferDuration;
final Duration? serverLoadDeferDuration;
final int thumbnailSize;
final bool shouldShowOwnerAvatar;
2021-03-25 20:05:39 +00:00
ThumbnailWidget(
this.file, {
Key? key,
this.fit = BoxFit.cover,
this.shouldShowSyncStatus = true,
this.shouldShowLivePhotoOverlay = false,
2022-03-21 11:35:01 +00:00
this.shouldShowArchiveStatus = false,
2022-11-17 05:32:24 +00:00
this.showFavForAlbumOnly = false,
this.shouldShowOwnerAvatar = false,
this.diskLoadDeferDuration,
this.serverLoadDeferDuration,
this.thumbnailSize = thumbnailSmallSize,
}) : 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;
ImageProvider? _imageProvider;
int? optimizedImageHeight;
int? optimizedImageWidth;
2020-04-25 10:28:22 +00:00
@override
void initState() {
super.initState();
assignOptimizedImageDimensions();
}
@override
void dispose() {
super.dispose();
2022-07-04 06:02:17 +00:00
Future.delayed(const Duration(milliseconds: 10), () {
// Cancel request only if the widget has been unmounted
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) {
2021-05-07 12:11:58 +00:00
_reset();
}
}
2023-05-15 10:14:55 +00:00
///Assigned dimension will be the size of a grid item. The size will be
///assigned to the side which is smaller in dimension.
void assignOptimizedImageDimensions() {
if (widget.file!.width == 0 || widget.file!.height == 0) {
return;
}
if (widget.file!.width < widget.file!.height) {
optimizedImageWidth = widget.thumbnailSize;
} else {
optimizedImageHeight = widget.thumbnailSize;
}
}
2020-03-28 18:18:27 +00:00
@override
Widget build(BuildContext context) {
if (widget.file!.isRemoteFile) {
2020-08-13 21:33:31 +00:00
_loadNetworkImage();
} else {
_loadLocalImage(context);
2020-05-25 14:35:06 +00:00
}
Widget? image;
2020-08-13 21:33:31 +00:00
if (_imageProvider != null) {
image = Image(
image: optimizedImageHeight != null || optimizedImageWidth != null
? ResizeImage(
_imageProvider!,
width: optimizedImageWidth,
height: optimizedImageHeight,
)
: _imageProvider!,
2020-08-13 21:33:31 +00:00
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
Widget? content;
if (image != null) {
2022-08-29 14:43:31 +00:00
final List<Widget> contentChildren = [image];
2022-11-17 05:32:24 +00:00
if (FavoritesService.instance.isFavoriteCache(
widget.file!,
2022-11-17 05:32:24 +00:00
checkOnlyAlbum: widget.showFavForAlbumOnly,
)) {
contentChildren.add(const FavoriteOverlayIcon());
}
if (widget.file!.fileType == FileType.video) {
2022-07-03 05:37:04 +00:00
contentChildren.add(const VideoOverlayIcon());
2023-05-03 07:33:45 +00:00
} else if (widget.shouldShowLivePhotoOverlay &&
(widget.file!.fileType == FileType.livePhoto ||
((widget.file!.pubMagicMetadata?.mvi ?? 0) > 0))) {
2022-07-03 05:37:04 +00:00
contentChildren.add(const LivePhotoOverlayIcon());
2020-06-20 10:03:45 +00:00
}
if (widget.shouldShowOwnerAvatar) {
if (widget.file!.ownerID != null &&
widget.file!.ownerID != Configuration.instance.getUserID()) {
final owner = CollectionsService.instance
.getFileOwner(widget.file!.ownerID!, widget.file!.collectionID);
// hide this icon if the current thumbnail is being showed as album
// cover
contentChildren.add(
OwnerAvatarOverlayIcon(owner),
);
} else if (widget.file!.pubMagicMetadata!.uploaderName != null) {
contentChildren.add(
// Use -1 as userID for enforcing black avatar color
OwnerAvatarOverlayIcon(
User(
id: -1,
email: '',
name: widget.file!.pubMagicMetadata!.uploaderName,
),
),
);
}
}
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-08-29 14:43:31 +00:00
final List<Widget> viewChildren = [
2022-07-03 05:37:04 +00:00
const ThumbnailPlaceHolder(),
content ?? const SizedBox(),
2022-03-21 11:35:01 +00:00
];
if (widget.shouldShowSyncStatus && widget.file!.uploadedFileID == null) {
2022-07-03 05:37:04 +00:00
viewChildren.add(const UnSyncedIcon());
}
if (kDebugMode &&
widget.shouldShowSyncStatus &&
widget.file!.uploadedFileID != null) {
if (widget.file!.localID != null) {
viewChildren.add(const DeviceIcon());
} else {
viewChildren.add(const CloudOnlyIcon());
}
}
2022-07-03 05:37:04 +00:00
if (widget.file is TrashFile) {
viewChildren.add(TrashedFileOverlayText(widget.file as TrashFile));
2022-07-03 05:37:04 +00:00
}
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 =
2023-01-11 04:39:33 +00:00
ThumbnailInMemoryLruCache.get(widget.file!, thumbnailSmallSize);
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!,
size: widget.thumbnailSize,
).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 as TrashFile);
} 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(
2022-12-31 16:24:32 +00:00
[widget.file!],
2022-06-11 08:23:52 +00:00
type: EventType.deletedFromDevice,
2022-11-11 13:09:22 +00:00
source: "thumbFileDeleted",
2022-06-11 08:23:52 +00:00
),
);
}
2021-04-26 07:29:01 +00:00
}
return;
}
2022-12-30 15:42:03 +00:00
if (mounted) {
final imageProvider = Image.memory(thumbData).image;
_cacheAndRender(imageProvider);
}
2023-01-11 04:39:33 +00:00
ThumbnailInMemoryLruCache.put(
2023-01-12 11:09:33 +00:00
widget.file!,
thumbData,
thumbnailSmallSize,
);
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;
2023-01-11 04:39:33 +00:00
final cachedThumbnail = ThumbnailInMemoryLruCache.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
}