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 {
 | 
			
		||||
  final Asset asset;
 | 
			
		||||
  final void Function() onTap;
 | 
			
		||||
  final void Function() onClose;
 | 
			
		||||
  final String title;
 | 
			
		||||
  final String? rightCornerText;
 | 
			
		||||
  final bool showTitle;
 | 
			
		||||
 | 
			
		||||
  const MemoryCard({
 | 
			
		||||
    required this.asset,
 | 
			
		||||
    required this.onTap,
 | 
			
		||||
    required this.onClose,
 | 
			
		||||
    required this.title,
 | 
			
		||||
    required this.showTitle,
 | 
			
		||||
    this.rightCornerText,
 | 
			
		||||
    super.key,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@ -65,34 +61,34 @@ class MemoryCard extends StatelessWidget {
 | 
			
		||||
          ),
 | 
			
		||||
          GestureDetector(
 | 
			
		||||
            onTap: onTap,
 | 
			
		||||
            child: ImmichImage(
 | 
			
		||||
              asset,
 | 
			
		||||
              fit: BoxFit.fitWidth,
 | 
			
		||||
              height: double.infinity,
 | 
			
		||||
              width: double.infinity,
 | 
			
		||||
              type: ThumbnailFormat.JPEG,
 | 
			
		||||
              preferredLocalAssetSize: 2048,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          Positioned(
 | 
			
		||||
            top: 2.0,
 | 
			
		||||
            left: 2.0,
 | 
			
		||||
            child: IconButton(
 | 
			
		||||
              onPressed: onClose,
 | 
			
		||||
              icon: const Icon(Icons.close_rounded),
 | 
			
		||||
              color: Colors.grey[400],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          Positioned(
 | 
			
		||||
            right: 18.0,
 | 
			
		||||
            top: 18.0,
 | 
			
		||||
            child: Text(
 | 
			
		||||
              rightCornerText ?? "",
 | 
			
		||||
              style: TextStyle(
 | 
			
		||||
                color: Colors.grey[200],
 | 
			
		||||
                fontSize: 12.0,
 | 
			
		||||
                fontWeight: FontWeight.bold,
 | 
			
		||||
              ),
 | 
			
		||||
            child: LayoutBuilder(
 | 
			
		||||
              builder: (context, constraints) {
 | 
			
		||||
                // Determine the fit using the aspect ratio
 | 
			
		||||
                BoxFit fit = BoxFit.fitWidth;
 | 
			
		||||
                if (asset.width != null && asset.height != null) {
 | 
			
		||||
                  final aspectRatio = asset.width! / asset.height!;
 | 
			
		||||
                  final phoneAspectRatio =
 | 
			
		||||
                      constraints.maxWidth / constraints.maxHeight;
 | 
			
		||||
                  // Look for a 25% difference in either direction
 | 
			
		||||
                  if (phoneAspectRatio * .75 < aspectRatio &&
 | 
			
		||||
                      phoneAspectRatio * 1.25 > aspectRatio) {
 | 
			
		||||
                    // Cover to look nice if we have nearly the same aspect ratio
 | 
			
		||||
                    fit = BoxFit.cover;
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return Hero(
 | 
			
		||||
                  tag: 'memory-${asset.id}',
 | 
			
		||||
                  child: ImmichImage(
 | 
			
		||||
                    asset,
 | 
			
		||||
                    fit: fit,
 | 
			
		||||
                    height: double.infinity,
 | 
			
		||||
                    width: double.infinity,
 | 
			
		||||
                    type: ThumbnailFormat.JPEG,
 | 
			
		||||
                    preferredLocalAssetSize: 2048,
 | 
			
		||||
                  ),
 | 
			
		||||
                );
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          if (showTitle)
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,7 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
 | 
			
		||||
  late final _animationController = AnimationController(
 | 
			
		||||
    vsync: this,
 | 
			
		||||
    duration: const Duration(
 | 
			
		||||
      seconds: 3,
 | 
			
		||||
      seconds: 2,
 | 
			
		||||
    ),
 | 
			
		||||
  )..repeat(
 | 
			
		||||
      reverse: true,
 | 
			
		||||
@ -29,7 +29,7 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _animation = CurvedAnimation(
 | 
			
		||||
      parent: _animationController,
 | 
			
		||||
      curve: Curves.easeInOut,
 | 
			
		||||
      curve: Curves.easeIn,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -41,74 +41,82 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Column(
 | 
			
		||||
      crossAxisAlignment: CrossAxisAlignment.center,
 | 
			
		||||
      mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
      children: [
 | 
			
		||||
        Expanded(
 | 
			
		||||
          child: Column(
 | 
			
		||||
            mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
            children: [
 | 
			
		||||
              const Icon(
 | 
			
		||||
                Icons.check_circle_outline_sharp,
 | 
			
		||||
                color: immichDarkThemePrimaryColor,
 | 
			
		||||
                size: 64.0,
 | 
			
		||||
              ),
 | 
			
		||||
              const SizedBox(height: 16.0),
 | 
			
		||||
              Text(
 | 
			
		||||
                'All caught up',
 | 
			
		||||
                style: Theme.of(context).textTheme.headlineMedium?.copyWith(
 | 
			
		||||
                      color: Colors.white,
 | 
			
		||||
    return SafeArea(
 | 
			
		||||
      child: Stack(
 | 
			
		||||
        children: [
 | 
			
		||||
          Positioned.fill(
 | 
			
		||||
            child: Column(
 | 
			
		||||
              mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
              children: [
 | 
			
		||||
                const Icon(
 | 
			
		||||
                  Icons.check_circle_outline_sharp,
 | 
			
		||||
                  color: immichDarkThemePrimaryColor,
 | 
			
		||||
                  size: 64.0,
 | 
			
		||||
                ),
 | 
			
		||||
                const SizedBox(height: 16.0),
 | 
			
		||||
                Text(
 | 
			
		||||
                  'All caught up',
 | 
			
		||||
                  style: Theme.of(context).textTheme.headlineMedium?.copyWith(
 | 
			
		||||
                        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,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            SizedBox(
 | 
			
		||||
              height: 48,
 | 
			
		||||
              child: AnimatedBuilder(
 | 
			
		||||
                animation: _animation,
 | 
			
		||||
                builder: (context, child) {
 | 
			
		||||
                  return Transform.translate(
 | 
			
		||||
                    offset: Offset(0, 5 * _animationController.value),
 | 
			
		||||
                    child: child,
 | 
			
		||||
                  );
 | 
			
		||||
                },
 | 
			
		||||
                child: const Icon(
 | 
			
		||||
                  size: 32,
 | 
			
		||||
                  Icons.expand_less_sharp,
 | 
			
		||||
                  color: Colors.white,
 | 
			
		||||
                ),
 | 
			
		||||
          Positioned(
 | 
			
		||||
            bottom: 0,
 | 
			
		||||
            left: 0,
 | 
			
		||||
            right: 0,
 | 
			
		||||
            child: Padding(
 | 
			
		||||
              padding: const EdgeInsets.only(bottom: 16.0),
 | 
			
		||||
              child: Column(
 | 
			
		||||
                children: [
 | 
			
		||||
                  SizedBox(
 | 
			
		||||
                    height: 48,
 | 
			
		||||
                    child: AnimatedBuilder(
 | 
			
		||||
                      animation: _animation,
 | 
			
		||||
                      builder: (context, child) {
 | 
			
		||||
                        return Transform.translate(
 | 
			
		||||
                          offset: Offset(0, 8 * _animationController.value),
 | 
			
		||||
                          child: child,
 | 
			
		||||
                        );
 | 
			
		||||
                      },
 | 
			
		||||
                      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
 | 
			
		||||
        .whenData(
 | 
			
		||||
          (memories) => memories != null
 | 
			
		||||
              ? Container(
 | 
			
		||||
                  margin: const EdgeInsets.only(top: 10, left: 10),
 | 
			
		||||
              ? SizedBox(
 | 
			
		||||
                  height: 200,
 | 
			
		||||
                  child: ListView.builder(
 | 
			
		||||
                    scrollDirection: Axis.horizontal,
 | 
			
		||||
                    shrinkWrap: true,
 | 
			
		||||
                    itemCount: memories.length,
 | 
			
		||||
                    padding: const EdgeInsets.only(
 | 
			
		||||
                      right: 8.0,
 | 
			
		||||
                      bottom: 8,
 | 
			
		||||
                      top: 10,
 | 
			
		||||
                      left: 10,
 | 
			
		||||
                    ),
 | 
			
		||||
                    itemBuilder: (context, index) {
 | 
			
		||||
                      final memory = memories[index];
 | 
			
		||||
 | 
			
		||||
                      return Padding(
 | 
			
		||||
                        padding: const EdgeInsets.only(right: 8.0, bottom: 8),
 | 
			
		||||
                        child: GestureDetector(
 | 
			
		||||
                          onTap: () {
 | 
			
		||||
                            HapticFeedback.heavyImpact();
 | 
			
		||||
                            context.pushRoute(
 | 
			
		||||
                              MemoryRoute(
 | 
			
		||||
                                memories: memories,
 | 
			
		||||
                                memoryIndex: index,
 | 
			
		||||
                      return GestureDetector(
 | 
			
		||||
                        onTap: () {
 | 
			
		||||
                          HapticFeedback.heavyImpact();
 | 
			
		||||
                          context.pushRoute(
 | 
			
		||||
                            MemoryRoute(
 | 
			
		||||
                              memories: memories,
 | 
			
		||||
                              memoryIndex: index,
 | 
			
		||||
                            ),
 | 
			
		||||
                          );
 | 
			
		||||
                        },
 | 
			
		||||
                        child: Stack(
 | 
			
		||||
                          children: [
 | 
			
		||||
                            Card(
 | 
			
		||||
                              elevation: 3,
 | 
			
		||||
                              shape: RoundedRectangleBorder(
 | 
			
		||||
                                borderRadius: BorderRadius.circular(13.0),
 | 
			
		||||
                              ),
 | 
			
		||||
                            );
 | 
			
		||||
                          },
 | 
			
		||||
                          child: Stack(
 | 
			
		||||
                            children: [
 | 
			
		||||
                              Card(
 | 
			
		||||
                                elevation: 3,
 | 
			
		||||
                                shape: RoundedRectangleBorder(
 | 
			
		||||
                                  borderRadius: BorderRadius.circular(13.0),
 | 
			
		||||
                              clipBehavior: Clip.hardEdge,
 | 
			
		||||
                              child: ColorFiltered(
 | 
			
		||||
                                colorFilter: ColorFilter.mode(
 | 
			
		||||
                                  Colors.black.withOpacity(0.2),
 | 
			
		||||
                                  BlendMode.darken,
 | 
			
		||||
                                ),
 | 
			
		||||
                                clipBehavior: Clip.hardEdge,
 | 
			
		||||
                                child: ColorFiltered(
 | 
			
		||||
                                  colorFilter: ColorFilter.mode(
 | 
			
		||||
                                    Colors.black.withOpacity(0.2),
 | 
			
		||||
                                    BlendMode.darken,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                child: Hero(
 | 
			
		||||
                                  tag: 'memory-${memory.assets[0].id}',
 | 
			
		||||
                                  child: ImmichImage(
 | 
			
		||||
                                    memory.assets[0],
 | 
			
		||||
                                    fit: BoxFit.cover,
 | 
			
		||||
@ -61,25 +66,25 @@ class MemoryLane extends HookConsumerWidget {
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                              ),
 | 
			
		||||
                              Positioned(
 | 
			
		||||
                                bottom: 16,
 | 
			
		||||
                                left: 16,
 | 
			
		||||
                                child: ConstrainedBox(
 | 
			
		||||
                                  constraints: const BoxConstraints(
 | 
			
		||||
                                    maxWidth: 114,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  child: Text(
 | 
			
		||||
                                    memory.title,
 | 
			
		||||
                                    style: const TextStyle(
 | 
			
		||||
                                      fontWeight: FontWeight.w600,
 | 
			
		||||
                                      color: Colors.white,
 | 
			
		||||
                                      fontSize: 15,
 | 
			
		||||
                                    ),
 | 
			
		||||
                            ),
 | 
			
		||||
                            Positioned(
 | 
			
		||||
                              bottom: 16,
 | 
			
		||||
                              left: 16,
 | 
			
		||||
                              child: ConstrainedBox(
 | 
			
		||||
                                constraints: const BoxConstraints(
 | 
			
		||||
                                  maxWidth: 114,
 | 
			
		||||
                                ),
 | 
			
		||||
                                child: Text(
 | 
			
		||||
                                  memory.title,
 | 
			
		||||
                                  style: const TextStyle(
 | 
			
		||||
                                    fontWeight: FontWeight.w600,
 | 
			
		||||
                                    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_card.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/ui/immich_image.dart';
 | 
			
		||||
import 'package:openapi/api.dart' as api;
 | 
			
		||||
@ -24,15 +25,28 @@ class MemoryPage extends HookConsumerWidget {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context, WidgetRef ref) {
 | 
			
		||||
    final memoryPageController = usePageController(initialPage: memoryIndex);
 | 
			
		||||
    final memoryAssetPageController = usePageController();
 | 
			
		||||
    final currentMemory = useState(memories[memoryIndex]);
 | 
			
		||||
    final currentAssetPage = useState(0);
 | 
			
		||||
    final currentMemoryIndex = useState(memoryIndex);
 | 
			
		||||
    final assetProgress = useState(
 | 
			
		||||
      "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}",
 | 
			
		||||
    );
 | 
			
		||||
    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() {
 | 
			
		||||
      memoryPageController.nextPage(
 | 
			
		||||
        duration: const Duration(milliseconds: 500),
 | 
			
		||||
@ -43,7 +57,10 @@ class MemoryPage extends HookConsumerWidget {
 | 
			
		||||
    toNextAsset(int currentAssetIndex) {
 | 
			
		||||
      if (currentAssetIndex + 1 < currentMemory.value.assets.length) {
 | 
			
		||||
        // Go to the next asset
 | 
			
		||||
        memoryAssetPageController.nextPage(
 | 
			
		||||
        PageController controller =
 | 
			
		||||
            memoryAssetPageControllers[currentMemoryIndex.value];
 | 
			
		||||
 | 
			
		||||
        controller.nextPage(
 | 
			
		||||
          curve: Curves.easeInOut,
 | 
			
		||||
          duration: const Duration(milliseconds: 500),
 | 
			
		||||
        );
 | 
			
		||||
@ -154,67 +171,122 @@ class MemoryPage extends HookConsumerWidget {
 | 
			
		||||
      },
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        backgroundColor: bgColor,
 | 
			
		||||
        body: SafeArea(
 | 
			
		||||
          child: PageView.builder(
 | 
			
		||||
            physics: const BouncingScrollPhysics(
 | 
			
		||||
              parent: AlwaysScrollableScrollPhysics(),
 | 
			
		||||
            ),
 | 
			
		||||
            scrollDirection: Axis.vertical,
 | 
			
		||||
            controller: memoryPageController,
 | 
			
		||||
            onPageChanged: (pageNumber) {
 | 
			
		||||
              HapticFeedback.mediumImpact();
 | 
			
		||||
              if (pageNumber < memories.length) {
 | 
			
		||||
                currentMemory.value = memories[pageNumber];
 | 
			
		||||
              }
 | 
			
		||||
        body: PopScope(
 | 
			
		||||
          onPopInvoked: (didPop) {
 | 
			
		||||
            // Remove immersive mode and go back to normal mode
 | 
			
		||||
            SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
 | 
			
		||||
          },
 | 
			
		||||
          child: SafeArea(
 | 
			
		||||
            child: PageView.builder(
 | 
			
		||||
              physics: const BouncingScrollPhysics(
 | 
			
		||||
                parent: AlwaysScrollableScrollPhysics(),
 | 
			
		||||
              ),
 | 
			
		||||
              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();
 | 
			
		||||
            },
 | 
			
		||||
            itemCount: memories.length + 1,
 | 
			
		||||
            itemBuilder: (context, mIndex) {
 | 
			
		||||
              // Build last page
 | 
			
		||||
              if (mIndex == memories.length) {
 | 
			
		||||
                return MemoryEpilogue(
 | 
			
		||||
                  onStartOver: () => memoryPageController.animateToPage(
 | 
			
		||||
                    0,
 | 
			
		||||
                    duration: const Duration(seconds: 1),
 | 
			
		||||
                    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,
 | 
			
		||||
                          ),
 | 
			
		||||
                        );
 | 
			
		||||
                      },
 | 
			
		||||
                updateProgressText();
 | 
			
		||||
              },
 | 
			
		||||
              itemCount: memories.length + 1,
 | 
			
		||||
              itemBuilder: (context, mIndex) {
 | 
			
		||||
                // Build last page
 | 
			
		||||
                if (mIndex == memories.length) {
 | 
			
		||||
                  return MemoryEpilogue(
 | 
			
		||||
                    onStartOver: () => memoryPageController.animateToPage(
 | 
			
		||||
                      0,
 | 
			
		||||
                      duration: const Duration(seconds: 1),
 | 
			
		||||
                      curve: Curves.easeInOut,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  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