123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- import 'package:flutter/material.dart';
- import 'package:logging/logging.dart';
- import 'package:photos/core/cache/thumbnail_cache.dart';
- import 'package:photos/core/constants.dart';
- import 'package:photos/core/errors.dart';
- 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';
- import 'package:photos/events/local_photos_updated_event.dart';
- import 'package:photos/models/file.dart';
- import 'package:photos/models/file_type.dart';
- import 'package:photos/models/trash_file.dart';
- import 'package:photos/ui/common_elements.dart';
- import 'package:photos/utils/date_time_util.dart';
- import 'package:photos/utils/file_util.dart';
- import 'package:photos/utils/thumbnail_util.dart';
- class ThumbnailWidget extends StatefulWidget {
- final File file;
- final BoxFit fit;
- final bool shouldShowSyncStatus;
- final bool shouldShowArchiveStatus;
- final bool shouldShowLivePhotoOverlay;
- final Duration diskLoadDeferDuration;
- final Duration serverLoadDeferDuration;
- ThumbnailWidget(
- this.file, {
- Key key,
- this.fit = BoxFit.cover,
- this.shouldShowSyncStatus = true,
- this.shouldShowLivePhotoOverlay = false,
- this.shouldShowArchiveStatus = false,
- this.diskLoadDeferDuration,
- this.serverLoadDeferDuration,
- }) : super(key: key ?? Key(file.tag()));
- @override
- _ThumbnailWidgetState createState() => _ThumbnailWidgetState();
- }
- Widget getFileInfoContainer(BuildContext context, File file) {
- if (file is TrashFile) {
- return Container(
- decoration: BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.bottomCenter,
- end: Alignment.topCenter,
- colors: [Colors.black.withOpacity(0.33), Colors.transparent],
- ),
- ),
- child: Text(
- daysLeft(file.deleteBy),
- style: Theme.of(context)
- .textTheme
- .subtitle2
- .copyWith(color: Colors.white), //same for both themes
- ),
- alignment: Alignment.bottomCenter,
- padding: EdgeInsets.only(bottom: 5),
- );
- }
- return emptyContainer;
- }
- class _ThumbnailWidgetState extends State<ThumbnailWidget> {
- static final _logger = Logger("ThumbnailWidget");
- static final kVideoIconOverlay = SizedBox(
- height: 64,
- child: Icon(
- Icons.play_circle_outline,
- size: 40,
- color: Colors.white70,
- ),
- );
- static final kLiveVideoIconOverlay = Align(
- alignment: Alignment.topRight,
- child: Padding(
- padding: const EdgeInsets.only(right: 8, top: 4),
- child: Icon(
- Icons.wb_sunny_outlined,
- size: 14,
- color: Colors.white.withOpacity(0.9),
- ),
- ),
- );
- static final kArchiveIconOverlay = Align(
- alignment: Alignment.bottomRight,
- child: Padding(
- padding: const EdgeInsets.only(right: 8, bottom: 8),
- child: Icon(
- Icons.visibility_off,
- size: 24,
- color: Colors.white.withOpacity(0.9),
- ),
- ),
- );
- static final kUnsyncedIconOverlay = Container(
- decoration: BoxDecoration(
- gradient: LinearGradient(
- begin: Alignment.topCenter,
- end: Alignment.bottomCenter,
- colors: [
- Colors.transparent,
- Colors.black.withOpacity(0.6),
- ],
- stops: const [0.75, 1],
- ),
- ),
- child: Align(
- alignment: Alignment.bottomRight,
- child: Padding(
- padding: const EdgeInsets.only(right: 8, bottom: 4),
- child: Icon(
- Icons.cloud_off_outlined,
- size: 18,
- color: Colors.white.withOpacity(0.9),
- ),
- ),
- ),
- );
- static final Widget lightLoadWidget = Container(
- alignment: Alignment.center,
- color: Color.fromRGBO(240, 240, 240, 1),
- );
- static final Widget darkLoadWidget = Container(
- alignment: Alignment.center,
- color: Color.fromRGBO(20, 20, 20, 1),
- );
- bool _hasLoadedThumbnail = false;
- bool _isLoadingLocalThumbnail = false;
- bool _errorLoadingLocalThumbnail = false;
- bool _isLoadingRemoteThumbnail = false;
- bool _errorLoadingRemoteThumbnail = false;
- 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
- if (!mounted && widget.file.isRemoteFile() && !_hasLoadedThumbnail) {
- removePendingGetThumbnailRequestIfAny(widget.file);
- }
- });
- }
- @override
- void didUpdateWidget(ThumbnailWidget oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (widget.file.generatedID != oldWidget.file.generatedID) {
- _reset();
- }
- }
- @override
- Widget build(BuildContext context) {
- if (widget.file.isRemoteFile()) {
- _loadNetworkImage();
- } else {
- _loadLocalImage(context);
- }
- Widget image;
- if (_imageProvider != null) {
- image = Image(
- image: _imageProvider,
- fit: widget.fit,
- );
- }
- Widget content;
- if (image != null) {
- if (widget.file.fileType == FileType.video) {
- content = Stack(
- children: [
- image,
- kVideoIconOverlay,
- ],
- fit: StackFit.expand,
- );
- } else if (widget.file.fileType == FileType.livePhoto &&
- widget.shouldShowLivePhotoOverlay) {
- content = Stack(
- children: [
- image,
- kLiveVideoIconOverlay,
- ],
- fit: StackFit.expand,
- );
- } else {
- content = image;
- }
- }
- List<Widget> viewChildrens = [
- _getLoadingWidget(),
- AnimatedOpacity(
- opacity: content == null ? 0 : 1.0,
- duration: Duration(milliseconds: 200),
- child: content,
- ),
- widget.shouldShowSyncStatus && widget.file.uploadedFileID == null
- ? kUnsyncedIconOverlay
- : getFileInfoContainer(context, widget.file),
- ];
- if (widget.shouldShowArchiveStatus) {
- viewChildrens.add(kArchiveIconOverlay);
- }
- return Stack(
- children: viewChildrens,
- fit: StackFit.expand,
- );
- }
- void _loadLocalImage(BuildContext context) {
- if (!_hasLoadedThumbnail &&
- !_errorLoadingLocalThumbnail &&
- !_isLoadingLocalThumbnail) {
- _isLoadingLocalThumbnail = true;
- final cachedSmallThumbnail =
- ThumbnailLruCache.get(widget.file, kThumbnailSmallSize);
- if (cachedSmallThumbnail != null) {
- _imageProvider = Image.memory(cachedSmallThumbnail).image;
- _hasLoadedThumbnail = true;
- } else {
- if (widget.diskLoadDeferDuration != null) {
- Future.delayed(widget.diskLoadDeferDuration, () {
- if (mounted) {
- _getThumbnailFromDisk();
- }
- });
- } else {
- _getThumbnailFromDisk();
- }
- }
- }
- }
- 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);
- }
- _loadNetworkImage();
- } else {
- if (await doesLocalFileExist(widget.file) == false) {
- _logger.info("Deleting file " + widget.file.tag());
- FilesDB.instance.deleteLocalFile(widget.file);
- Bus.instance.fire(
- LocalPhotosUpdatedEvent(
- [widget.file],
- type: EventType.deletedFromDevice,
- ),
- );
- }
- }
- return;
- }
- if (thumbData != null && mounted) {
- final imageProvider = Image.memory(thumbData).image;
- _cacheAndRender(imageProvider);
- }
- ThumbnailLruCache.put(widget.file, thumbData, kThumbnailSmallSize);
- }).catchError((e) {
- _logger.warning("Could not load image: ", e);
- _errorLoadingLocalThumbnail = true;
- });
- }
- 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, () {
- if (mounted) {
- _getThumbnailFromServer();
- }
- });
- } else {
- _getThumbnailFromServer();
- }
- }
- }
- void _getThumbnailFromServer() async {
- try {
- final thumbnail = await getThumbnailFromServer(widget.file);
- if (mounted) {
- final imageProvider = Image.memory(thumbnail).image;
- _cacheAndRender(imageProvider);
- }
- } catch (e) {
- if (e is RequestCancelledError) {
- if (mounted) {
- _logger.info(
- "Thumbnail request was aborted although it is in view, will retry",
- );
- _reset();
- setState(() {});
- }
- } else {
- _logger.severe("Could not load image " + widget.file.toString(), e);
- _errorLoadingRemoteThumbnail = true;
- }
- }
- }
- 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;
- });
- }
- });
- }
- void _reset() {
- _hasLoadedThumbnail = false;
- _isLoadingLocalThumbnail = false;
- _isLoadingRemoteThumbnail = false;
- _errorLoadingLocalThumbnail = false;
- _errorLoadingRemoteThumbnail = false;
- _imageProvider = null;
- }
- Widget _getLoadingWidget() {
- return Theme.of(context).brightness == Brightness.light
- ? lightLoadWidget
- : darkLoadWidget;
- }
- }
|