thumbnail_widget.dart 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:dio/dio.dart';
  3. import 'package:flutter/material.dart';
  4. import 'dart:io' as io;
  5. import 'package:photos/core/cache/thumbnail_cache.dart';
  6. import 'package:photos/core/configuration.dart';
  7. import 'package:photos/file_repository.dart';
  8. import 'package:photos/models/file.dart';
  9. import 'package:logging/logging.dart';
  10. import 'package:photos/core/constants.dart';
  11. import 'package:photos/models/file_type.dart';
  12. import 'package:photos/ui/loading_widget.dart';
  13. import 'package:photos/utils/crypto_util.dart';
  14. import 'package:photos/utils/file_util.dart';
  15. class ThumbnailWidget extends StatefulWidget {
  16. final File file;
  17. final BoxFit fit;
  18. const ThumbnailWidget(
  19. this.file, {
  20. Key key,
  21. this.fit = BoxFit.cover,
  22. }) : super(key: key);
  23. @override
  24. _ThumbnailWidgetState createState() => _ThumbnailWidgetState();
  25. }
  26. class _ThumbnailWidgetState extends State<ThumbnailWidget> {
  27. static final _logger = Logger("ThumbnailWidget");
  28. static final Widget loadingWidget = Container(
  29. alignment: Alignment.center,
  30. color: Colors.grey[800],
  31. );
  32. bool _hasLoadedThumbnail = false;
  33. bool _encounteredErrorLoadingThumbnail = false;
  34. ImageProvider _imageProvider;
  35. @override
  36. Widget build(BuildContext context) {
  37. if (widget.file.localID == null) {
  38. return _getNetworkImage();
  39. }
  40. _loadLocalImage(context);
  41. var content;
  42. if (_imageProvider != null) {
  43. final image = Image(
  44. image: _imageProvider,
  45. fit: widget.fit,
  46. );
  47. if (widget.file.fileType == FileType.video) {
  48. content = Stack(
  49. children: [
  50. image,
  51. Icon(Icons.play_circle_outline),
  52. ],
  53. fit: StackFit.expand,
  54. );
  55. } else {
  56. content = image;
  57. }
  58. }
  59. return Stack(
  60. children: [
  61. loadingWidget,
  62. AnimatedOpacity(
  63. opacity: content == null ? 0 : 1.0,
  64. duration: Duration(milliseconds: 400),
  65. child: content,
  66. ),
  67. ],
  68. fit: StackFit.expand,
  69. );
  70. }
  71. void _loadLocalImage(BuildContext context) {
  72. if (!_hasLoadedThumbnail && !_encounteredErrorLoadingThumbnail) {
  73. final cachedSmallThumbnail =
  74. ThumbnailLruCache.get(widget.file, THUMBNAIL_SMALL_SIZE);
  75. if (cachedSmallThumbnail != null) {
  76. _imageProvider = Image.memory(cachedSmallThumbnail).image;
  77. _hasLoadedThumbnail = true;
  78. } else {
  79. widget.file.getAsset().then((asset) async {
  80. if (asset == null) {
  81. await deleteFiles([widget.file]);
  82. await FileRepository.instance.reloadFiles();
  83. return;
  84. }
  85. asset
  86. .thumbDataWithSize(THUMBNAIL_SMALL_SIZE, THUMBNAIL_SMALL_SIZE)
  87. .then((data) {
  88. if (data != null && mounted) {
  89. final imageProvider = Image.memory(data).image;
  90. precacheImage(imageProvider, context).then((value) {
  91. if (mounted) {
  92. setState(() {
  93. _imageProvider = imageProvider;
  94. _hasLoadedThumbnail = true;
  95. });
  96. }
  97. });
  98. }
  99. ThumbnailLruCache.put(widget.file, THUMBNAIL_SMALL_SIZE, data);
  100. });
  101. }).catchError((e) {
  102. _logger.warning("Could not load image: ", e);
  103. _encounteredErrorLoadingThumbnail = true;
  104. });
  105. }
  106. }
  107. }
  108. Widget _getNetworkImage() {
  109. if (!widget.file.isEncrypted) {
  110. return CachedNetworkImage(
  111. imageUrl: widget.file.getThumbnailUrl(),
  112. placeholder: (context, url) => loadWidget,
  113. errorWidget: (context, url, error) => Icon(Icons.error),
  114. fit: BoxFit.cover,
  115. );
  116. } else {
  117. final thumbnailPath = Configuration.instance.getThumbnailsDirectory() +
  118. widget.file.generatedID.toString() +
  119. ".jpg";
  120. final thumbnailFile = io.File(thumbnailPath);
  121. if (thumbnailFile.existsSync()) {
  122. return Image.file(
  123. thumbnailFile,
  124. fit: widget.fit,
  125. );
  126. } else {
  127. final temporaryPath = Configuration.instance.getTempDirectory() +
  128. widget.file.generatedID.toString() +
  129. "_thumbnail.aes";
  130. final decryptedFileFuture = Dio()
  131. .download(widget.file.getThumbnailUrl(), temporaryPath)
  132. .then((_) async {
  133. await CryptoUtil.decryptFileToFile(
  134. temporaryPath, thumbnailPath, Configuration.instance.getKey());
  135. io.File(temporaryPath).deleteSync();
  136. return io.File(thumbnailPath);
  137. });
  138. return FutureBuilder<io.File>(
  139. future: decryptedFileFuture,
  140. builder: (context, snapshot) {
  141. if (snapshot.hasData) {
  142. // TODO: Cache data
  143. return Image.file(
  144. snapshot.data,
  145. fit: widget.fit,
  146. );
  147. } else if (snapshot.hasError) {
  148. _logger.warning(snapshot.error);
  149. return Text(snapshot.error.toString());
  150. } else {
  151. return loadingWidget;
  152. }
  153. },
  154. );
  155. }
  156. }
  157. }
  158. @override
  159. void didUpdateWidget(ThumbnailWidget oldWidget) {
  160. super.didUpdateWidget(oldWidget);
  161. if (widget.file.generatedID != oldWidget.file.generatedID) {
  162. setState(() {
  163. _hasLoadedThumbnail = false;
  164. _imageProvider = null;
  165. });
  166. }
  167. }
  168. }