mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	* enable DCM in CI * chore: up version * chore: up version --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
		
			
				
	
	
		
			243 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			7.0 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() {
 | 
						|
      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(),
 | 
						|
            ),
 | 
						|
          ),
 | 
						|
      ],
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 |