immich_image.dart 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. import 'package:flutter_thumbhash/flutter_thumbhash.dart';
  5. import 'package:image_fade/image_fade.dart';
  6. import 'package:immich_mobile/shared/models/asset.dart';
  7. import 'package:immich_mobile/shared/models/store.dart';
  8. import 'package:immich_mobile/utils/image_url_builder.dart';
  9. import 'package:photo_manager/photo_manager.dart';
  10. import 'package:openapi/api.dart' as api;
  11. import 'package:flutter_cache_manager/flutter_cache_manager.dart';
  12. /// Renders an Asset using local data if available, else remote data
  13. class ImmichImage extends StatelessWidget {
  14. const ImmichImage(
  15. this.asset, {
  16. this.width,
  17. this.height,
  18. this.fit = BoxFit.cover,
  19. this.useGrayBoxPlaceholder = false,
  20. this.type = api.ThumbnailFormat.WEBP,
  21. super.key,
  22. });
  23. final Asset? asset;
  24. final bool useGrayBoxPlaceholder;
  25. final double? width;
  26. final double? height;
  27. final BoxFit fit;
  28. final api.ThumbnailFormat type;
  29. @override
  30. Widget build(BuildContext context) {
  31. if (this.asset == null) {
  32. return Container(
  33. decoration: const BoxDecoration(
  34. color: Colors.grey,
  35. ),
  36. child: SizedBox(
  37. width: width,
  38. height: height,
  39. child: const Center(
  40. child: Icon(Icons.no_photography),
  41. ),
  42. ),
  43. );
  44. }
  45. final Asset asset = this.asset!;
  46. if (asset.isLocal) {
  47. return Image(
  48. image: AssetEntityImageProvider(
  49. asset.local!,
  50. isOriginal: false,
  51. thumbnailSize: const ThumbnailSize.square(250), // like server thumbs
  52. ),
  53. width: width,
  54. height: height,
  55. fit: fit,
  56. frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
  57. if (wasSynchronouslyLoaded || frame != null) {
  58. return child;
  59. }
  60. return (useGrayBoxPlaceholder
  61. ? const SizedBox.square(
  62. dimension: 250,
  63. child: DecoratedBox(
  64. decoration: BoxDecoration(color: Colors.grey),
  65. ),
  66. )
  67. : Transform.scale(
  68. scale: 0.2,
  69. child: const CircularProgressIndicator(),
  70. ));
  71. },
  72. errorBuilder: (context, error, stackTrace) {
  73. if (error is PlatformException &&
  74. error.code == "The asset not found!") {
  75. debugPrint(
  76. "Asset ${asset.localId} does not exist anymore on device!",
  77. );
  78. } else {
  79. debugPrint(
  80. "Error getting thumb for assetId=${asset.localId}: $error",
  81. );
  82. }
  83. return Icon(
  84. Icons.image_not_supported_outlined,
  85. color: Theme.of(context).primaryColor,
  86. );
  87. },
  88. );
  89. }
  90. final String? token = Store.get(StoreKey.accessToken);
  91. final String thumbnailRequestUrl = getThumbnailUrl(asset, type: type);
  92. Widget placeholderWidget;
  93. if (asset.thumbhash != null) {
  94. placeholderWidget = FittedBox(
  95. fit: BoxFit.fill,
  96. child: Image(
  97. image: ThumbHash.fromIntList(asset.thumbhash!).toImage(),
  98. ),
  99. );
  100. } else if (useGrayBoxPlaceholder) {
  101. placeholderWidget = const DecoratedBox(
  102. decoration: BoxDecoration(color: Colors.grey),
  103. );
  104. } else {
  105. placeholderWidget = Transform.scale(
  106. scale: 0.2,
  107. child: const CircularProgressIndicator(),
  108. );
  109. }
  110. return ImageFade(
  111. width: width,
  112. height: height,
  113. image: CachedNetworkImageProvider(
  114. thumbnailRequestUrl,
  115. headers: {"Authorization": "Bearer $token"},
  116. cacheKey: getThumbnailCacheKey(asset, type: type),
  117. ),
  118. fit: fit,
  119. placeholder: placeholderWidget,
  120. errorBuilder: (context, error) {
  121. if (error is HttpExceptionWithStatus &&
  122. error.statusCode >= 400 &&
  123. error.statusCode < 500) {
  124. debugPrint(
  125. "Evicting thumbnail '$thumbnailRequestUrl' from cache: $error");
  126. CachedNetworkImage.evictFromCache(thumbnailRequestUrl);
  127. }
  128. return Icon(
  129. Icons.image_not_supported_outlined,
  130. color: Theme.of(context).primaryColor,
  131. );
  132. },
  133. );
  134. }
  135. }