mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05:00 
			
		
		
		
	feat(mobile): Memories activity is now full screen, better image fit, adds progress indicator (#6793)
* Made memories full screen * Uses linear bar and fits the card better for memories * Fixes pageview close button moving with pages * Uses hooks instead of stateful components * Adds ticks to the progress indicator * Rounds the edges of the progress bar * Fixes trailing comma analyze error * Adds padding and hero to memories * Fixes an issue with initial index set and adds hero / proper padding * Fixes aspect ratio calculation * Color --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
		
							parent
							
								
									f6b4024a21
								
							
						
					
					
						commit
						c29976cd6f
					
				@ -12,18 +12,14 @@ import 'package:openapi/api.dart';
 | 
				
			|||||||
class MemoryCard extends StatelessWidget {
 | 
					class MemoryCard extends StatelessWidget {
 | 
				
			||||||
  final Asset asset;
 | 
					  final Asset asset;
 | 
				
			||||||
  final void Function() onTap;
 | 
					  final void Function() onTap;
 | 
				
			||||||
  final void Function() onClose;
 | 
					 | 
				
			||||||
  final String title;
 | 
					  final String title;
 | 
				
			||||||
  final String? rightCornerText;
 | 
					 | 
				
			||||||
  final bool showTitle;
 | 
					  final bool showTitle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const MemoryCard({
 | 
					  const MemoryCard({
 | 
				
			||||||
    required this.asset,
 | 
					    required this.asset,
 | 
				
			||||||
    required this.onTap,
 | 
					    required this.onTap,
 | 
				
			||||||
    required this.onClose,
 | 
					 | 
				
			||||||
    required this.title,
 | 
					    required this.title,
 | 
				
			||||||
    required this.showTitle,
 | 
					    required this.showTitle,
 | 
				
			||||||
    this.rightCornerText,
 | 
					 | 
				
			||||||
    super.key,
 | 
					    super.key,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,34 +61,34 @@ class MemoryCard extends StatelessWidget {
 | 
				
			|||||||
          ),
 | 
					          ),
 | 
				
			||||||
          GestureDetector(
 | 
					          GestureDetector(
 | 
				
			||||||
            onTap: onTap,
 | 
					            onTap: onTap,
 | 
				
			||||||
            child: ImmichImage(
 | 
					            child: LayoutBuilder(
 | 
				
			||||||
              asset,
 | 
					              builder: (context, constraints) {
 | 
				
			||||||
              fit: BoxFit.fitWidth,
 | 
					                // Determine the fit using the aspect ratio
 | 
				
			||||||
              height: double.infinity,
 | 
					                BoxFit fit = BoxFit.fitWidth;
 | 
				
			||||||
              width: double.infinity,
 | 
					                if (asset.width != null && asset.height != null) {
 | 
				
			||||||
              type: ThumbnailFormat.JPEG,
 | 
					                  final aspectRatio = asset.width! / asset.height!;
 | 
				
			||||||
              preferredLocalAssetSize: 2048,
 | 
					                  final phoneAspectRatio =
 | 
				
			||||||
            ),
 | 
					                      constraints.maxWidth / constraints.maxHeight;
 | 
				
			||||||
          ),
 | 
					                  // Look for a 25% difference in either direction
 | 
				
			||||||
          Positioned(
 | 
					                  if (phoneAspectRatio * .75 < aspectRatio &&
 | 
				
			||||||
            top: 2.0,
 | 
					                      phoneAspectRatio * 1.25 > aspectRatio) {
 | 
				
			||||||
            left: 2.0,
 | 
					                    // Cover to look nice if we have nearly the same aspect ratio
 | 
				
			||||||
            child: IconButton(
 | 
					                    fit = BoxFit.cover;
 | 
				
			||||||
              onPressed: onClose,
 | 
					                  }
 | 
				
			||||||
              icon: const Icon(Icons.close_rounded),
 | 
					                }
 | 
				
			||||||
              color: Colors.grey[400],
 | 
					
 | 
				
			||||||
            ),
 | 
					                return Hero(
 | 
				
			||||||
          ),
 | 
					                  tag: 'memory-${asset.id}',
 | 
				
			||||||
          Positioned(
 | 
					                  child: ImmichImage(
 | 
				
			||||||
            right: 18.0,
 | 
					                    asset,
 | 
				
			||||||
            top: 18.0,
 | 
					                    fit: fit,
 | 
				
			||||||
            child: Text(
 | 
					                    height: double.infinity,
 | 
				
			||||||
              rightCornerText ?? "",
 | 
					                    width: double.infinity,
 | 
				
			||||||
              style: TextStyle(
 | 
					                    type: ThumbnailFormat.JPEG,
 | 
				
			||||||
                color: Colors.grey[200],
 | 
					                    preferredLocalAssetSize: 2048,
 | 
				
			||||||
                fontSize: 12.0,
 | 
					                  ),
 | 
				
			||||||
                fontWeight: FontWeight.bold,
 | 
					                );
 | 
				
			||||||
              ),
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          if (showTitle)
 | 
					          if (showTitle)
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
 | 
				
			|||||||
  late final _animationController = AnimationController(
 | 
					  late final _animationController = AnimationController(
 | 
				
			||||||
    vsync: this,
 | 
					    vsync: this,
 | 
				
			||||||
    duration: const Duration(
 | 
					    duration: const Duration(
 | 
				
			||||||
      seconds: 3,
 | 
					      seconds: 2,
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
  )..repeat(
 | 
					  )..repeat(
 | 
				
			||||||
      reverse: true,
 | 
					      reverse: true,
 | 
				
			||||||
@ -29,7 +29,7 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
 | 
				
			|||||||
    super.initState();
 | 
					    super.initState();
 | 
				
			||||||
    _animation = CurvedAnimation(
 | 
					    _animation = CurvedAnimation(
 | 
				
			||||||
      parent: _animationController,
 | 
					      parent: _animationController,
 | 
				
			||||||
      curve: Curves.easeInOut,
 | 
					      curve: Curves.easeIn,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -41,74 +41,82 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return Column(
 | 
					    return SafeArea(
 | 
				
			||||||
      crossAxisAlignment: CrossAxisAlignment.center,
 | 
					      child: Stack(
 | 
				
			||||||
      mainAxisAlignment: MainAxisAlignment.center,
 | 
					        children: [
 | 
				
			||||||
      children: [
 | 
					          Positioned.fill(
 | 
				
			||||||
        Expanded(
 | 
					            child: Column(
 | 
				
			||||||
          child: Column(
 | 
					              mainAxisAlignment: MainAxisAlignment.center,
 | 
				
			||||||
            mainAxisAlignment: MainAxisAlignment.center,
 | 
					              children: [
 | 
				
			||||||
            children: [
 | 
					                const Icon(
 | 
				
			||||||
              const Icon(
 | 
					                  Icons.check_circle_outline_sharp,
 | 
				
			||||||
                Icons.check_circle_outline_sharp,
 | 
					                  color: immichDarkThemePrimaryColor,
 | 
				
			||||||
                color: immichDarkThemePrimaryColor,
 | 
					                  size: 64.0,
 | 
				
			||||||
                size: 64.0,
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					                const SizedBox(height: 16.0),
 | 
				
			||||||
              const SizedBox(height: 16.0),
 | 
					                Text(
 | 
				
			||||||
              Text(
 | 
					                  'All caught up',
 | 
				
			||||||
                'All caught up',
 | 
					                  style: Theme.of(context).textTheme.headlineMedium?.copyWith(
 | 
				
			||||||
                style: Theme.of(context).textTheme.headlineMedium?.copyWith(
 | 
					                        color: Colors.white,
 | 
				
			||||||
                      color: Colors.white,
 | 
					                      ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                const SizedBox(height: 16.0),
 | 
				
			||||||
 | 
					                Text(
 | 
				
			||||||
 | 
					                  'Check back tomorrow for more memories',
 | 
				
			||||||
 | 
					                  style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
				
			||||||
 | 
					                        color: Colors.white,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                const SizedBox(height: 16.0),
 | 
				
			||||||
 | 
					                TextButton(
 | 
				
			||||||
 | 
					                  onPressed: widget.onStartOver,
 | 
				
			||||||
 | 
					                  child: Text(
 | 
				
			||||||
 | 
					                    'Start Over',
 | 
				
			||||||
 | 
					                    style: context.textTheme.displayMedium?.copyWith(
 | 
				
			||||||
 | 
					                      color: immichDarkThemePrimaryColor,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              const SizedBox(height: 16.0),
 | 
					 | 
				
			||||||
              Text(
 | 
					 | 
				
			||||||
                'Check back tomorrow for more memories',
 | 
					 | 
				
			||||||
                style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
					 | 
				
			||||||
                      color: Colors.white,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              const SizedBox(height: 16.0),
 | 
					 | 
				
			||||||
              TextButton(
 | 
					 | 
				
			||||||
                onPressed: widget.onStartOver,
 | 
					 | 
				
			||||||
                child: Text(
 | 
					 | 
				
			||||||
                  'Start Over',
 | 
					 | 
				
			||||||
                  style: context.textTheme.displayMedium?.copyWith(
 | 
					 | 
				
			||||||
                    color: immichDarkThemePrimaryColor,
 | 
					 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ],
 | 
				
			||||||
            ],
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					          Positioned(
 | 
				
			||||||
        Column(
 | 
					            bottom: 0,
 | 
				
			||||||
          children: [
 | 
					            left: 0,
 | 
				
			||||||
            SizedBox(
 | 
					            right: 0,
 | 
				
			||||||
              height: 48,
 | 
					            child: Padding(
 | 
				
			||||||
              child: AnimatedBuilder(
 | 
					              padding: const EdgeInsets.only(bottom: 16.0),
 | 
				
			||||||
                animation: _animation,
 | 
					              child: Column(
 | 
				
			||||||
                builder: (context, child) {
 | 
					                children: [
 | 
				
			||||||
                  return Transform.translate(
 | 
					                  SizedBox(
 | 
				
			||||||
                    offset: Offset(0, 5 * _animationController.value),
 | 
					                    height: 48,
 | 
				
			||||||
                    child: child,
 | 
					                    child: AnimatedBuilder(
 | 
				
			||||||
                  );
 | 
					                      animation: _animation,
 | 
				
			||||||
                },
 | 
					                      builder: (context, child) {
 | 
				
			||||||
                child: const Icon(
 | 
					                        return Transform.translate(
 | 
				
			||||||
                  size: 32,
 | 
					                          offset: Offset(0, 8 * _animationController.value),
 | 
				
			||||||
                  Icons.expand_less_sharp,
 | 
					                          child: child,
 | 
				
			||||||
                  color: Colors.white,
 | 
					                        );
 | 
				
			||||||
                ),
 | 
					                      },
 | 
				
			||||||
 | 
					                      child: const Icon(
 | 
				
			||||||
 | 
					                        size: 32,
 | 
				
			||||||
 | 
					                        Icons.expand_less_sharp,
 | 
				
			||||||
 | 
					                        color: Colors.white,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  Text(
 | 
				
			||||||
 | 
					                    'Swipe up to close',
 | 
				
			||||||
 | 
					                    style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
				
			||||||
 | 
					                          color: Colors.white,
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            Text(
 | 
					          ),
 | 
				
			||||||
              'Swipe up to close',
 | 
					        ],
 | 
				
			||||||
              style: Theme.of(context).textTheme.bodyMedium?.copyWith(
 | 
					      ),
 | 
				
			||||||
                    color: Colors.white,
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          ],
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,41 +16,46 @@ class MemoryLane extends HookConsumerWidget {
 | 
				
			|||||||
    final memoryLane = memoryLaneFutureProvider
 | 
					    final memoryLane = memoryLaneFutureProvider
 | 
				
			||||||
        .whenData(
 | 
					        .whenData(
 | 
				
			||||||
          (memories) => memories != null
 | 
					          (memories) => memories != null
 | 
				
			||||||
              ? Container(
 | 
					              ? SizedBox(
 | 
				
			||||||
                  margin: const EdgeInsets.only(top: 10, left: 10),
 | 
					 | 
				
			||||||
                  height: 200,
 | 
					                  height: 200,
 | 
				
			||||||
                  child: ListView.builder(
 | 
					                  child: ListView.builder(
 | 
				
			||||||
                    scrollDirection: Axis.horizontal,
 | 
					                    scrollDirection: Axis.horizontal,
 | 
				
			||||||
                    shrinkWrap: true,
 | 
					                    shrinkWrap: true,
 | 
				
			||||||
                    itemCount: memories.length,
 | 
					                    itemCount: memories.length,
 | 
				
			||||||
 | 
					                    padding: const EdgeInsets.only(
 | 
				
			||||||
 | 
					                      right: 8.0,
 | 
				
			||||||
 | 
					                      bottom: 8,
 | 
				
			||||||
 | 
					                      top: 10,
 | 
				
			||||||
 | 
					                      left: 10,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                    itemBuilder: (context, index) {
 | 
					                    itemBuilder: (context, index) {
 | 
				
			||||||
                      final memory = memories[index];
 | 
					                      final memory = memories[index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                      return Padding(
 | 
					                      return GestureDetector(
 | 
				
			||||||
                        padding: const EdgeInsets.only(right: 8.0, bottom: 8),
 | 
					                        onTap: () {
 | 
				
			||||||
                        child: GestureDetector(
 | 
					                          HapticFeedback.heavyImpact();
 | 
				
			||||||
                          onTap: () {
 | 
					                          context.pushRoute(
 | 
				
			||||||
                            HapticFeedback.heavyImpact();
 | 
					                            MemoryRoute(
 | 
				
			||||||
                            context.pushRoute(
 | 
					                              memories: memories,
 | 
				
			||||||
                              MemoryRoute(
 | 
					                              memoryIndex: index,
 | 
				
			||||||
                                memories: memories,
 | 
					                            ),
 | 
				
			||||||
                                memoryIndex: index,
 | 
					                          );
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        child: Stack(
 | 
				
			||||||
 | 
					                          children: [
 | 
				
			||||||
 | 
					                            Card(
 | 
				
			||||||
 | 
					                              elevation: 3,
 | 
				
			||||||
 | 
					                              shape: RoundedRectangleBorder(
 | 
				
			||||||
 | 
					                                borderRadius: BorderRadius.circular(13.0),
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                            );
 | 
					                              clipBehavior: Clip.hardEdge,
 | 
				
			||||||
                          },
 | 
					                              child: ColorFiltered(
 | 
				
			||||||
                          child: Stack(
 | 
					                                colorFilter: ColorFilter.mode(
 | 
				
			||||||
                            children: [
 | 
					                                  Colors.black.withOpacity(0.2),
 | 
				
			||||||
                              Card(
 | 
					                                  BlendMode.darken,
 | 
				
			||||||
                                elevation: 3,
 | 
					 | 
				
			||||||
                                shape: RoundedRectangleBorder(
 | 
					 | 
				
			||||||
                                  borderRadius: BorderRadius.circular(13.0),
 | 
					 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                                clipBehavior: Clip.hardEdge,
 | 
					                                child: Hero(
 | 
				
			||||||
                                child: ColorFiltered(
 | 
					                                  tag: 'memory-${memory.assets[0].id}',
 | 
				
			||||||
                                  colorFilter: ColorFilter.mode(
 | 
					 | 
				
			||||||
                                    Colors.black.withOpacity(0.2),
 | 
					 | 
				
			||||||
                                    BlendMode.darken,
 | 
					 | 
				
			||||||
                                  ),
 | 
					 | 
				
			||||||
                                  child: ImmichImage(
 | 
					                                  child: ImmichImage(
 | 
				
			||||||
                                    memory.assets[0],
 | 
					                                    memory.assets[0],
 | 
				
			||||||
                                    fit: BoxFit.cover,
 | 
					                                    fit: BoxFit.cover,
 | 
				
			||||||
@ -61,25 +66,25 @@ class MemoryLane extends HookConsumerWidget {
 | 
				
			|||||||
                                  ),
 | 
					                                  ),
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                              Positioned(
 | 
					                            ),
 | 
				
			||||||
                                bottom: 16,
 | 
					                            Positioned(
 | 
				
			||||||
                                left: 16,
 | 
					                              bottom: 16,
 | 
				
			||||||
                                child: ConstrainedBox(
 | 
					                              left: 16,
 | 
				
			||||||
                                  constraints: const BoxConstraints(
 | 
					                              child: ConstrainedBox(
 | 
				
			||||||
                                    maxWidth: 114,
 | 
					                                constraints: const BoxConstraints(
 | 
				
			||||||
                                  ),
 | 
					                                  maxWidth: 114,
 | 
				
			||||||
                                  child: Text(
 | 
					                                ),
 | 
				
			||||||
                                    memory.title,
 | 
					                                child: Text(
 | 
				
			||||||
                                    style: const TextStyle(
 | 
					                                  memory.title,
 | 
				
			||||||
                                      fontWeight: FontWeight.w600,
 | 
					                                  style: const TextStyle(
 | 
				
			||||||
                                      color: Colors.white,
 | 
					                                    fontWeight: FontWeight.w600,
 | 
				
			||||||
                                      fontSize: 15,
 | 
					                                    color: Colors.white,
 | 
				
			||||||
                                    ),
 | 
					                                    fontSize: 15,
 | 
				
			||||||
                                  ),
 | 
					                                  ),
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                              ),
 | 
					                              ),
 | 
				
			||||||
                            ],
 | 
					                            ),
 | 
				
			||||||
                          ),
 | 
					                          ],
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      );
 | 
					                      );
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/constants/immich_colors.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/extensions/build_context_extensions.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MemoryProgressIndicator extends StatelessWidget {
 | 
				
			||||||
 | 
					  /// The number of ticks in the progress indicator
 | 
				
			||||||
 | 
					  final int ticks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The current value of the indicator
 | 
				
			||||||
 | 
					  final double value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const MemoryProgressIndicator({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.ticks,
 | 
				
			||||||
 | 
					    required this.value,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return LayoutBuilder(
 | 
				
			||||||
 | 
					      builder: (context, constraints) {
 | 
				
			||||||
 | 
					        final tickWidth = constraints.maxWidth / ticks;
 | 
				
			||||||
 | 
					        return ClipRRect(
 | 
				
			||||||
 | 
					          borderRadius: const BorderRadius.all(Radius.circular(2.0)),
 | 
				
			||||||
 | 
					          child: Stack(
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					              LinearProgressIndicator(
 | 
				
			||||||
 | 
					                value: value,
 | 
				
			||||||
 | 
					                backgroundColor: Colors.grey[600],
 | 
				
			||||||
 | 
					                color: immichDarkThemePrimaryColor,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              Row(
 | 
				
			||||||
 | 
					                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
 | 
				
			||||||
 | 
					                children: List.generate(
 | 
				
			||||||
 | 
					                  ticks,
 | 
				
			||||||
 | 
					                  (i) => Container(
 | 
				
			||||||
 | 
					                    width: tickWidth,
 | 
				
			||||||
 | 
					                    height: 4,
 | 
				
			||||||
 | 
					                    decoration: BoxDecoration(
 | 
				
			||||||
 | 
					                      border: i == 0
 | 
				
			||||||
 | 
					                          ? null
 | 
				
			||||||
 | 
					                          : Border(
 | 
				
			||||||
 | 
					                              left: BorderSide(
 | 
				
			||||||
 | 
					                                color: context.colorScheme.onSecondaryContainer,
 | 
				
			||||||
 | 
					                                width: 1,
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -7,6 +7,7 @@ import 'package:immich_mobile/modules/memories/models/memory.dart';
 | 
				
			|||||||
import 'package:immich_mobile/modules/memories/ui/memory_bottom_info.dart';
 | 
					import 'package:immich_mobile/modules/memories/ui/memory_bottom_info.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/memories/ui/memory_card.dart';
 | 
					import 'package:immich_mobile/modules/memories/ui/memory_card.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/memories/ui/memory_epilogue.dart';
 | 
					import 'package:immich_mobile/modules/memories/ui/memory_epilogue.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/memories/ui/memory_progress_indicator.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/asset.dart';
 | 
					import 'package:immich_mobile/shared/models/asset.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/ui/immich_image.dart';
 | 
					import 'package:immich_mobile/shared/ui/immich_image.dart';
 | 
				
			||||||
import 'package:openapi/api.dart' as api;
 | 
					import 'package:openapi/api.dart' as api;
 | 
				
			||||||
@ -24,15 +25,28 @@ class MemoryPage extends HookConsumerWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
					  Widget build(BuildContext context, WidgetRef ref) {
 | 
				
			||||||
    final memoryPageController = usePageController(initialPage: memoryIndex);
 | 
					 | 
				
			||||||
    final memoryAssetPageController = usePageController();
 | 
					 | 
				
			||||||
    final currentMemory = useState(memories[memoryIndex]);
 | 
					    final currentMemory = useState(memories[memoryIndex]);
 | 
				
			||||||
    final currentAssetPage = useState(0);
 | 
					    final currentAssetPage = useState(0);
 | 
				
			||||||
 | 
					    final currentMemoryIndex = useState(memoryIndex);
 | 
				
			||||||
    final assetProgress = useState(
 | 
					    final assetProgress = useState(
 | 
				
			||||||
      "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}",
 | 
					      "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}",
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const bgColor = Colors.black;
 | 
					    const bgColor = Colors.black;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The list of all of the asset page controllers
 | 
				
			||||||
 | 
					    final memoryAssetPageControllers =
 | 
				
			||||||
 | 
					        List.generate(memories.length, (i) => usePageController());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// The main vertically scrolling page controller with each list of memories
 | 
				
			||||||
 | 
					    final memoryPageController = usePageController(initialPage: memoryIndex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // The Page Controller that scrolls horizontally with all of the assets
 | 
				
			||||||
 | 
					    useEffect(() {
 | 
				
			||||||
 | 
					      // Memories is an immersive activity
 | 
				
			||||||
 | 
					      SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    toNextMemory() {
 | 
					    toNextMemory() {
 | 
				
			||||||
      memoryPageController.nextPage(
 | 
					      memoryPageController.nextPage(
 | 
				
			||||||
        duration: const Duration(milliseconds: 500),
 | 
					        duration: const Duration(milliseconds: 500),
 | 
				
			||||||
@ -43,7 +57,10 @@ class MemoryPage extends HookConsumerWidget {
 | 
				
			|||||||
    toNextAsset(int currentAssetIndex) {
 | 
					    toNextAsset(int currentAssetIndex) {
 | 
				
			||||||
      if (currentAssetIndex + 1 < currentMemory.value.assets.length) {
 | 
					      if (currentAssetIndex + 1 < currentMemory.value.assets.length) {
 | 
				
			||||||
        // Go to the next asset
 | 
					        // Go to the next asset
 | 
				
			||||||
        memoryAssetPageController.nextPage(
 | 
					        PageController controller =
 | 
				
			||||||
 | 
					            memoryAssetPageControllers[currentMemoryIndex.value];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        controller.nextPage(
 | 
				
			||||||
          curve: Curves.easeInOut,
 | 
					          curve: Curves.easeInOut,
 | 
				
			||||||
          duration: const Duration(milliseconds: 500),
 | 
					          duration: const Duration(milliseconds: 500),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -154,67 +171,122 @@ class MemoryPage extends HookConsumerWidget {
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      child: Scaffold(
 | 
					      child: Scaffold(
 | 
				
			||||||
        backgroundColor: bgColor,
 | 
					        backgroundColor: bgColor,
 | 
				
			||||||
        body: SafeArea(
 | 
					        body: PopScope(
 | 
				
			||||||
          child: PageView.builder(
 | 
					          onPopInvoked: (didPop) {
 | 
				
			||||||
            physics: const BouncingScrollPhysics(
 | 
					            // Remove immersive mode and go back to normal mode
 | 
				
			||||||
              parent: AlwaysScrollableScrollPhysics(),
 | 
					            SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
 | 
				
			||||||
            ),
 | 
					          },
 | 
				
			||||||
            scrollDirection: Axis.vertical,
 | 
					          child: SafeArea(
 | 
				
			||||||
            controller: memoryPageController,
 | 
					            child: PageView.builder(
 | 
				
			||||||
            onPageChanged: (pageNumber) {
 | 
					              physics: const BouncingScrollPhysics(
 | 
				
			||||||
              HapticFeedback.mediumImpact();
 | 
					                parent: AlwaysScrollableScrollPhysics(),
 | 
				
			||||||
              if (pageNumber < memories.length) {
 | 
					              ),
 | 
				
			||||||
                currentMemory.value = memories[pageNumber];
 | 
					              scrollDirection: Axis.vertical,
 | 
				
			||||||
              }
 | 
					              controller: memoryPageController,
 | 
				
			||||||
 | 
					              onPageChanged: (pageNumber) {
 | 
				
			||||||
 | 
					                HapticFeedback.mediumImpact();
 | 
				
			||||||
 | 
					                if (pageNumber < memories.length) {
 | 
				
			||||||
 | 
					                  currentMemoryIndex.value = pageNumber;
 | 
				
			||||||
 | 
					                  currentMemory.value = memories[pageNumber];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              currentAssetPage.value = 0;
 | 
					                currentAssetPage.value = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
              updateProgressText();
 | 
					                updateProgressText();
 | 
				
			||||||
            },
 | 
					              },
 | 
				
			||||||
            itemCount: memories.length + 1,
 | 
					              itemCount: memories.length + 1,
 | 
				
			||||||
            itemBuilder: (context, mIndex) {
 | 
					              itemBuilder: (context, mIndex) {
 | 
				
			||||||
              // Build last page
 | 
					                // Build last page
 | 
				
			||||||
              if (mIndex == memories.length) {
 | 
					                if (mIndex == memories.length) {
 | 
				
			||||||
                return MemoryEpilogue(
 | 
					                  return MemoryEpilogue(
 | 
				
			||||||
                  onStartOver: () => memoryPageController.animateToPage(
 | 
					                    onStartOver: () => memoryPageController.animateToPage(
 | 
				
			||||||
                    0,
 | 
					                      0,
 | 
				
			||||||
                    duration: const Duration(seconds: 1),
 | 
					                      duration: const Duration(seconds: 1),
 | 
				
			||||||
                    curve: Curves.easeInOut,
 | 
					                      curve: Curves.easeInOut,
 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              // Build horizontal page
 | 
					 | 
				
			||||||
              return Column(
 | 
					 | 
				
			||||||
                children: [
 | 
					 | 
				
			||||||
                  Expanded(
 | 
					 | 
				
			||||||
                    child: PageView.builder(
 | 
					 | 
				
			||||||
                      physics: const BouncingScrollPhysics(
 | 
					 | 
				
			||||||
                        parent: AlwaysScrollableScrollPhysics(),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      controller: memoryAssetPageController,
 | 
					 | 
				
			||||||
                      onPageChanged: onAssetChanged,
 | 
					 | 
				
			||||||
                      scrollDirection: Axis.horizontal,
 | 
					 | 
				
			||||||
                      itemCount: memories[mIndex].assets.length,
 | 
					 | 
				
			||||||
                      itemBuilder: (context, index) {
 | 
					 | 
				
			||||||
                        final asset = memories[mIndex].assets[index];
 | 
					 | 
				
			||||||
                        return Container(
 | 
					 | 
				
			||||||
                          color: Colors.black,
 | 
					 | 
				
			||||||
                          child: MemoryCard(
 | 
					 | 
				
			||||||
                            asset: asset,
 | 
					 | 
				
			||||||
                            onTap: () => toNextAsset(index),
 | 
					 | 
				
			||||||
                            onClose: () => context.popRoute(),
 | 
					 | 
				
			||||||
                            rightCornerText: assetProgress.value,
 | 
					 | 
				
			||||||
                            title: memories[mIndex].title,
 | 
					 | 
				
			||||||
                            showTitle: index == 0,
 | 
					 | 
				
			||||||
                          ),
 | 
					 | 
				
			||||||
                        );
 | 
					 | 
				
			||||||
                      },
 | 
					 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                  );
 | 
				
			||||||
                  MemoryBottomInfo(memory: memories[mIndex]),
 | 
					                }
 | 
				
			||||||
                ],
 | 
					                // Build horizontal page
 | 
				
			||||||
              );
 | 
					                final assetController = memoryAssetPageControllers[mIndex];
 | 
				
			||||||
            },
 | 
					                return Column(
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    Padding(
 | 
				
			||||||
 | 
					                      padding: const EdgeInsets.only(
 | 
				
			||||||
 | 
					                        left: 24.0,
 | 
				
			||||||
 | 
					                        right: 24.0,
 | 
				
			||||||
 | 
					                        top: 8.0,
 | 
				
			||||||
 | 
					                        bottom: 2.0,
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      child: AnimatedBuilder(
 | 
				
			||||||
 | 
					                        animation: assetController,
 | 
				
			||||||
 | 
					                        builder: (context, child) {
 | 
				
			||||||
 | 
					                          double value = 0.0;
 | 
				
			||||||
 | 
					                          if (assetController.hasClients) {
 | 
				
			||||||
 | 
					                            // We can only access [page] if this has clients
 | 
				
			||||||
 | 
					                            value = assetController.page ?? 0;
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                          return MemoryProgressIndicator(
 | 
				
			||||||
 | 
					                            ticks: memories[mIndex].assets.length,
 | 
				
			||||||
 | 
					                            value: (value + 1) / memories[mIndex].assets.length,
 | 
				
			||||||
 | 
					                          );
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    Expanded(
 | 
				
			||||||
 | 
					                      child: Stack(
 | 
				
			||||||
 | 
					                        children: [
 | 
				
			||||||
 | 
					                          PageView.builder(
 | 
				
			||||||
 | 
					                            physics: const BouncingScrollPhysics(
 | 
				
			||||||
 | 
					                              parent: AlwaysScrollableScrollPhysics(),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            controller: assetController,
 | 
				
			||||||
 | 
					                            onPageChanged: onAssetChanged,
 | 
				
			||||||
 | 
					                            scrollDirection: Axis.horizontal,
 | 
				
			||||||
 | 
					                            itemCount: memories[mIndex].assets.length,
 | 
				
			||||||
 | 
					                            itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					                              final asset = memories[mIndex].assets[index];
 | 
				
			||||||
 | 
					                              return Container(
 | 
				
			||||||
 | 
					                                color: Colors.black,
 | 
				
			||||||
 | 
					                                child: MemoryCard(
 | 
				
			||||||
 | 
					                                  asset: asset,
 | 
				
			||||||
 | 
					                                  onTap: () => toNextAsset(index),
 | 
				
			||||||
 | 
					                                  title: memories[mIndex].title,
 | 
				
			||||||
 | 
					                                  showTitle: index == 0,
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                              );
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                          Positioned(
 | 
				
			||||||
 | 
					                            top: 8,
 | 
				
			||||||
 | 
					                            left: 8,
 | 
				
			||||||
 | 
					                            child: MaterialButton(
 | 
				
			||||||
 | 
					                              minWidth: 0,
 | 
				
			||||||
 | 
					                              onPressed: () {
 | 
				
			||||||
 | 
					                                // auto_route doesn't invoke pop scope, so
 | 
				
			||||||
 | 
					                                // turn off full screen mode here
 | 
				
			||||||
 | 
					                                // https://github.com/Milad-Akarie/auto_route_library/issues/1799
 | 
				
			||||||
 | 
					                                context.popRoute();
 | 
				
			||||||
 | 
					                                SystemChrome.setEnabledSystemUIMode(
 | 
				
			||||||
 | 
					                                  SystemUiMode.edgeToEdge,
 | 
				
			||||||
 | 
					                                );
 | 
				
			||||||
 | 
					                              },
 | 
				
			||||||
 | 
					                              shape: const CircleBorder(),
 | 
				
			||||||
 | 
					                              color: Colors.white.withOpacity(0.2),
 | 
				
			||||||
 | 
					                              elevation: 0,
 | 
				
			||||||
 | 
					                              child: const Icon(
 | 
				
			||||||
 | 
					                                Icons.close_rounded,
 | 
				
			||||||
 | 
					                                color: Colors.white,
 | 
				
			||||||
 | 
					                              ),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    MemoryBottomInfo(memory: memories[mIndex]),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user