mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -04:00 
			
		
		
		
	* update dependencies * update flutter version reference * update flutter version reference * update AndroidManifest with flutter_web_auth_2 * chore: lock file flutter version * fix: ios build
		
			
				
	
	
		
			243 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:flutter/material.dart';
 | |
| import 'package:hooks_riverpod/hooks_riverpod.dart';
 | |
| import 'package:immich_mobile/constants/constants.dart';
 | |
| import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | |
| import 'package:immich_mobile/entities/asset.entity.dart';
 | |
| import 'package:immich_mobile/extensions/theme_extensions.dart';
 | |
| import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
 | |
| import 'package:immich_mobile/utils/storage_indicator.dart';
 | |
| 
 | |
| class ThumbnailImage extends ConsumerWidget {
 | |
|   /// The asset to show the thumbnail image for
 | |
|   final Asset asset;
 | |
| 
 | |
|   /// Whether to show the storage indicator icont over the image or not
 | |
|   final bool showStorageIndicator;
 | |
| 
 | |
|   /// Whether to show the show stack icon over the image or not
 | |
|   final bool showStack;
 | |
| 
 | |
|   /// Whether to show the checkmark indicating that this image is selected
 | |
|   final bool isSelected;
 | |
| 
 | |
|   /// Can override [isSelected] and never show the selection indicator
 | |
|   final bool multiselectEnabled;
 | |
| 
 | |
|   /// If we are allowed to deselect this image
 | |
|   final bool canDeselect;
 | |
| 
 | |
|   /// The offset index to apply to this hero tag for animation
 | |
|   final int heroOffset;
 | |
| 
 | |
|   const ThumbnailImage({
 | |
|     super.key,
 | |
|     required this.asset,
 | |
|     this.showStorageIndicator = true,
 | |
|     this.showStack = false,
 | |
|     this.isSelected = false,
 | |
|     this.multiselectEnabled = false,
 | |
|     this.heroOffset = 0,
 | |
|     this.canDeselect = true,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context, WidgetRef ref) {
 | |
|     final assetContainerColor = context.isDarkTheme
 | |
|         ? context.primaryColor.darken(amount: 0.6)
 | |
|         : context.primaryColor.lighten(amount: 0.8);
 | |
|     // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
 | |
|     final isFromDto = asset.id == noDbId;
 | |
| 
 | |
|     Widget buildSelectionIcon(Asset asset) {
 | |
|       if (isSelected) {
 | |
|         return Container(
 | |
|           decoration: BoxDecoration(
 | |
|             shape: BoxShape.circle,
 | |
|             color: assetContainerColor,
 | |
|           ),
 | |
|           child: Icon(
 | |
|             Icons.check_circle_rounded,
 | |
|             color: context.primaryColor,
 | |
|           ),
 | |
|         );
 | |
|       } else {
 | |
|         return const Icon(
 | |
|           Icons.circle_outlined,
 | |
|           color: Colors.white,
 | |
|         );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     Widget buildVideoIcon() {
 | |
|       final minutes = asset.duration.inMinutes;
 | |
|       final durationString = asset.duration.toString();
 | |
|       return Positioned(
 | |
|         top: 5,
 | |
|         right: 8,
 | |
|         child: Row(
 | |
|           children: [
 | |
|             Text(
 | |
|               minutes > 59
 | |
|                   ? durationString.substring(0, 7) // h:mm:ss
 | |
|                   : minutes > 0
 | |
|                       ? durationString.substring(2, 7) // mm:ss
 | |
|                       : durationString.substring(3, 7), // m:ss
 | |
|               style: const TextStyle(
 | |
|                 color: Colors.white,
 | |
|                 fontSize: 10,
 | |
|                 fontWeight: FontWeight.bold,
 | |
|               ),
 | |
|             ),
 | |
|             const SizedBox(
 | |
|               width: 3,
 | |
|             ),
 | |
|             const Icon(
 | |
|               Icons.play_circle_fill_rounded,
 | |
|               color: Colors.white,
 | |
|               size: 18,
 | |
|             ),
 | |
|           ],
 | |
|         ),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     Widget buildStackIcon() {
 | |
|       return Positioned(
 | |
|         top: !asset.isImage ? 28 : 5,
 | |
|         right: 8,
 | |
|         child: Row(
 | |
|           children: [
 | |
|             if (asset.stackCount > 1)
 | |
|               Text(
 | |
|                 "${asset.stackCount}",
 | |
|                 style: const TextStyle(
 | |
|                   color: Colors.white,
 | |
|                   fontSize: 10,
 | |
|                   fontWeight: FontWeight.bold,
 | |
|                 ),
 | |
|               ),
 | |
|             if (asset.stackCount > 1)
 | |
|               const SizedBox(
 | |
|                 width: 3,
 | |
|               ),
 | |
|             const Icon(
 | |
|               Icons.burst_mode_rounded,
 | |
|               color: Colors.white,
 | |
|               size: 18,
 | |
|             ),
 | |
|           ],
 | |
|         ),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     Widget buildImage() {
 | |
|       final image = SizedBox.expand(
 | |
|         child: Hero(
 | |
|           tag: isFromDto
 | |
|               ? '${asset.remoteId}-$heroOffset'
 | |
|               : asset.id + heroOffset,
 | |
|           child: Stack(
 | |
|             children: [
 | |
|               SizedBox.expand(
 | |
|                 child: ImmichThumbnail(
 | |
|                   asset: asset,
 | |
|                   height: 250,
 | |
|                   width: 250,
 | |
|                 ),
 | |
|               ),
 | |
|               Container(
 | |
|                 decoration: const BoxDecoration(
 | |
|                   gradient: LinearGradient(
 | |
|                     colors: [
 | |
|                       Color.fromRGBO(0, 0, 0, 0.1),
 | |
|                       Colors.transparent,
 | |
|                       Colors.transparent,
 | |
|                       Color.fromRGBO(0, 0, 0, 0.1),
 | |
|                     ],
 | |
|                     begin: Alignment.topCenter,
 | |
|                     end: Alignment.bottomCenter,
 | |
|                     stops: [0, 0.3, 0.6, 1],
 | |
|                   ),
 | |
|                 ),
 | |
|               ),
 | |
|             ],
 | |
|           ),
 | |
|         ),
 | |
|       );
 | |
|       if (!multiselectEnabled || !isSelected) {
 | |
|         return image;
 | |
|       }
 | |
|       return Container(
 | |
|         decoration: BoxDecoration(
 | |
|           color: canDeselect ? assetContainerColor : Colors.grey,
 | |
|         ),
 | |
|         child: ClipRRect(
 | |
|           borderRadius: const BorderRadius.all(
 | |
|             Radius.circular(15.0),
 | |
|           ),
 | |
|           child: image,
 | |
|         ),
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     return Stack(
 | |
|       children: [
 | |
|         AnimatedContainer(
 | |
|           duration: const Duration(milliseconds: 300),
 | |
|           curve: Curves.decelerate,
 | |
|           decoration: BoxDecoration(
 | |
|             border: multiselectEnabled && isSelected
 | |
|                 ? Border.all(
 | |
|                     color: canDeselect ? assetContainerColor : Colors.grey,
 | |
|                     width: 8,
 | |
|                   )
 | |
|                 : const Border(),
 | |
|           ),
 | |
|           child: Stack(
 | |
|             children: [
 | |
|               buildImage(),
 | |
|               if (showStorageIndicator)
 | |
|                 Positioned(
 | |
|                   right: 8,
 | |
|                   bottom: 5,
 | |
|                   child: Icon(
 | |
|                     storageIcon(asset),
 | |
|                     color: Colors.white.withValues(alpha: .8),
 | |
|                     size: 16,
 | |
|                     shadows: [
 | |
|                       Shadow(
 | |
|                         blurRadius: 5.0,
 | |
|                         color: Colors.black.withValues(alpha: 0.6),
 | |
|                         offset: const Offset(0.0, 0.0),
 | |
|                       ),
 | |
|                     ],
 | |
|                   ),
 | |
|                 ),
 | |
|               if (asset.isFavorite)
 | |
|                 const Positioned(
 | |
|                   left: 8,
 | |
|                   bottom: 5,
 | |
|                   child: Icon(
 | |
|                     Icons.favorite,
 | |
|                     color: Colors.white,
 | |
|                     size: 16,
 | |
|                   ),
 | |
|                 ),
 | |
|               if (!asset.isImage) buildVideoIcon(),
 | |
|               if (asset.stackCount > 0) buildStackIcon(),
 | |
|             ],
 | |
|           ),
 | |
|         ),
 | |
|         if (multiselectEnabled)
 | |
|           Padding(
 | |
|             padding: const EdgeInsets.all(3.0),
 | |
|             child: Align(
 | |
|               alignment: Alignment.topLeft,
 | |
|               child: buildSelectionIcon(asset),
 | |
|             ),
 | |
|           ),
 | |
|       ],
 | |
|     );
 | |
|   }
 | |
| }
 |