immich_image.dart 4.4 KB

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