mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-02 18:59:15 -05:00 
			
		
		
		
	Draggable scroll bar is implemented from commmunity library
This commit is contained in:
		
							parent
							
								
									56c92cd83b
								
							
						
					
					
						commit
						0d8fddf537
					
				
							
								
								
									
										617
									
								
								mobile/lib/modules/home/ui/draggable_scrollbar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										617
									
								
								mobile/lib/modules/home/ui/draggable_scrollbar.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,617 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Build the Scroll Thumb and label using the current configuration
 | 
				
			||||||
 | 
					typedef ScrollThumbBuilder = Widget Function(
 | 
				
			||||||
 | 
					  Color backgroundColor,
 | 
				
			||||||
 | 
					  Animation<double> thumbAnimation,
 | 
				
			||||||
 | 
					  Animation<double> labelAnimation,
 | 
				
			||||||
 | 
					  double height, {
 | 
				
			||||||
 | 
					  Text? labelText,
 | 
				
			||||||
 | 
					  BoxConstraints? labelConstraints,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Build a Text widget using the current scroll offset
 | 
				
			||||||
 | 
					typedef LabelTextBuilder = Text Function(double offsetY);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged
 | 
				
			||||||
 | 
					/// for quick navigation of the BoxScrollView.
 | 
				
			||||||
 | 
					class DraggableScrollbar extends StatefulWidget {
 | 
				
			||||||
 | 
					  /// The view that will be scrolled with the scroll thumb
 | 
				
			||||||
 | 
					  final CustomScrollView child;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// A function that builds a thumb using the current configuration
 | 
				
			||||||
 | 
					  final ScrollThumbBuilder scrollThumbBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The height of the scroll thumb
 | 
				
			||||||
 | 
					  final double heightScrollThumb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The background color of the label and thumb
 | 
				
			||||||
 | 
					  final Color backgroundColor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The amount of padding that should surround the thumb
 | 
				
			||||||
 | 
					  final EdgeInsetsGeometry? padding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Determines how quickly the scrollbar will animate in and out
 | 
				
			||||||
 | 
					  final Duration scrollbarAnimationDuration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// How long should the thumb be visible before fading out
 | 
				
			||||||
 | 
					  final Duration scrollbarTimeToFade;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Build a Text widget from the current offset in the BoxScrollView
 | 
				
			||||||
 | 
					  final LabelTextBuilder? labelTextBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Determines box constraints for Container displaying label
 | 
				
			||||||
 | 
					  final BoxConstraints? labelConstraints;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// The ScrollController for the BoxScrollView
 | 
				
			||||||
 | 
					  final ScrollController controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Determines scrollThumb displaying. If you draw own ScrollThumb and it is true you just don't need to use animation parameters in [scrollThumbBuilder]
 | 
				
			||||||
 | 
					  final bool alwaysVisibleScrollThumb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DraggableScrollbar({
 | 
				
			||||||
 | 
					    Key? key,
 | 
				
			||||||
 | 
					    this.alwaysVisibleScrollThumb = false,
 | 
				
			||||||
 | 
					    required this.heightScrollThumb,
 | 
				
			||||||
 | 
					    required this.backgroundColor,
 | 
				
			||||||
 | 
					    required this.scrollThumbBuilder,
 | 
				
			||||||
 | 
					    required this.child,
 | 
				
			||||||
 | 
					    required this.controller,
 | 
				
			||||||
 | 
					    this.padding,
 | 
				
			||||||
 | 
					    this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
 | 
				
			||||||
 | 
					    this.scrollbarTimeToFade = const Duration(milliseconds: 600),
 | 
				
			||||||
 | 
					    this.labelTextBuilder,
 | 
				
			||||||
 | 
					    this.labelConstraints,
 | 
				
			||||||
 | 
					  })  : assert(child.scrollDirection == Axis.vertical),
 | 
				
			||||||
 | 
					        super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DraggableScrollbar.rrect({
 | 
				
			||||||
 | 
					    Key? key,
 | 
				
			||||||
 | 
					    Key? scrollThumbKey,
 | 
				
			||||||
 | 
					    this.alwaysVisibleScrollThumb = false,
 | 
				
			||||||
 | 
					    required this.child,
 | 
				
			||||||
 | 
					    required this.controller,
 | 
				
			||||||
 | 
					    this.heightScrollThumb = 48.0,
 | 
				
			||||||
 | 
					    this.backgroundColor = Colors.white,
 | 
				
			||||||
 | 
					    this.padding,
 | 
				
			||||||
 | 
					    this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
 | 
				
			||||||
 | 
					    this.scrollbarTimeToFade = const Duration(milliseconds: 600),
 | 
				
			||||||
 | 
					    this.labelTextBuilder,
 | 
				
			||||||
 | 
					    this.labelConstraints,
 | 
				
			||||||
 | 
					  })  : assert(child.scrollDirection == Axis.vertical),
 | 
				
			||||||
 | 
					        scrollThumbBuilder = _thumbRRectBuilder(scrollThumbKey, alwaysVisibleScrollThumb),
 | 
				
			||||||
 | 
					        super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DraggableScrollbar.arrows({
 | 
				
			||||||
 | 
					    Key? key,
 | 
				
			||||||
 | 
					    Key? scrollThumbKey,
 | 
				
			||||||
 | 
					    this.alwaysVisibleScrollThumb = false,
 | 
				
			||||||
 | 
					    required this.child,
 | 
				
			||||||
 | 
					    required this.controller,
 | 
				
			||||||
 | 
					    this.heightScrollThumb = 48.0,
 | 
				
			||||||
 | 
					    this.backgroundColor = Colors.white,
 | 
				
			||||||
 | 
					    this.padding,
 | 
				
			||||||
 | 
					    this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
 | 
				
			||||||
 | 
					    this.scrollbarTimeToFade = const Duration(milliseconds: 600),
 | 
				
			||||||
 | 
					    this.labelTextBuilder,
 | 
				
			||||||
 | 
					    this.labelConstraints,
 | 
				
			||||||
 | 
					  })  : assert(child.scrollDirection == Axis.vertical),
 | 
				
			||||||
 | 
					        scrollThumbBuilder = _thumbArrowBuilder(scrollThumbKey, alwaysVisibleScrollThumb),
 | 
				
			||||||
 | 
					        super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  DraggableScrollbar.semicircle({
 | 
				
			||||||
 | 
					    Key? key,
 | 
				
			||||||
 | 
					    Key? scrollThumbKey,
 | 
				
			||||||
 | 
					    this.alwaysVisibleScrollThumb = false,
 | 
				
			||||||
 | 
					    required this.child,
 | 
				
			||||||
 | 
					    required this.controller,
 | 
				
			||||||
 | 
					    this.heightScrollThumb = 48.0,
 | 
				
			||||||
 | 
					    this.backgroundColor = Colors.white,
 | 
				
			||||||
 | 
					    this.padding,
 | 
				
			||||||
 | 
					    this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
 | 
				
			||||||
 | 
					    this.scrollbarTimeToFade = const Duration(milliseconds: 600),
 | 
				
			||||||
 | 
					    this.labelTextBuilder,
 | 
				
			||||||
 | 
					    this.labelConstraints,
 | 
				
			||||||
 | 
					  })  : assert(child.scrollDirection == Axis.vertical),
 | 
				
			||||||
 | 
					        scrollThumbBuilder = _thumbSemicircleBuilder(heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb),
 | 
				
			||||||
 | 
					        super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  _DraggableScrollbarState createState() => _DraggableScrollbarState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static buildScrollThumbAndLabel(
 | 
				
			||||||
 | 
					      {required Widget scrollThumb,
 | 
				
			||||||
 | 
					      required Color backgroundColor,
 | 
				
			||||||
 | 
					      required Animation<double>? thumbAnimation,
 | 
				
			||||||
 | 
					      required Animation<double>? labelAnimation,
 | 
				
			||||||
 | 
					      required Text? labelText,
 | 
				
			||||||
 | 
					      required BoxConstraints? labelConstraints,
 | 
				
			||||||
 | 
					      required bool alwaysVisibleScrollThumb}) {
 | 
				
			||||||
 | 
					    var scrollThumbAndLabel = labelText == null
 | 
				
			||||||
 | 
					        ? scrollThumb
 | 
				
			||||||
 | 
					        : Row(
 | 
				
			||||||
 | 
					            mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					            mainAxisAlignment: MainAxisAlignment.end,
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					              ScrollLabel(
 | 
				
			||||||
 | 
					                animation: labelAnimation,
 | 
				
			||||||
 | 
					                child: labelText,
 | 
				
			||||||
 | 
					                backgroundColor: backgroundColor,
 | 
				
			||||||
 | 
					                constraints: labelConstraints,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              scrollThumb,
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (alwaysVisibleScrollThumb) {
 | 
				
			||||||
 | 
					      return scrollThumbAndLabel;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return SlideFadeTransition(
 | 
				
			||||||
 | 
					      animation: thumbAnimation!,
 | 
				
			||||||
 | 
					      child: scrollThumbAndLabel,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static ScrollThumbBuilder _thumbSemicircleBuilder(double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      Color backgroundColor,
 | 
				
			||||||
 | 
					      Animation<double> thumbAnimation,
 | 
				
			||||||
 | 
					      Animation<double> labelAnimation,
 | 
				
			||||||
 | 
					      double height, {
 | 
				
			||||||
 | 
					      Text? labelText,
 | 
				
			||||||
 | 
					      BoxConstraints? labelConstraints,
 | 
				
			||||||
 | 
					    }) {
 | 
				
			||||||
 | 
					      final scrollThumb = CustomPaint(
 | 
				
			||||||
 | 
					        key: scrollThumbKey,
 | 
				
			||||||
 | 
					        foregroundPainter: ArrowCustomPainter(Colors.grey),
 | 
				
			||||||
 | 
					        child: Material(
 | 
				
			||||||
 | 
					          elevation: 4.0,
 | 
				
			||||||
 | 
					          child: Container(
 | 
				
			||||||
 | 
					            constraints: BoxConstraints.tight(Size(width, height)),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          color: backgroundColor,
 | 
				
			||||||
 | 
					          borderRadius: BorderRadius.only(
 | 
				
			||||||
 | 
					            topLeft: Radius.circular(height),
 | 
				
			||||||
 | 
					            bottomLeft: Radius.circular(height),
 | 
				
			||||||
 | 
					            topRight: const Radius.circular(4.0),
 | 
				
			||||||
 | 
					            bottomRight: const Radius.circular(4.0),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return buildScrollThumbAndLabel(
 | 
				
			||||||
 | 
					        scrollThumb: scrollThumb,
 | 
				
			||||||
 | 
					        backgroundColor: backgroundColor,
 | 
				
			||||||
 | 
					        thumbAnimation: thumbAnimation,
 | 
				
			||||||
 | 
					        labelAnimation: labelAnimation,
 | 
				
			||||||
 | 
					        labelText: labelText,
 | 
				
			||||||
 | 
					        labelConstraints: labelConstraints,
 | 
				
			||||||
 | 
					        alwaysVisibleScrollThumb: alwaysVisibleScrollThumb,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static ScrollThumbBuilder _thumbArrowBuilder(Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      Color backgroundColor,
 | 
				
			||||||
 | 
					      Animation<double> thumbAnimation,
 | 
				
			||||||
 | 
					      Animation<double> labelAnimation,
 | 
				
			||||||
 | 
					      double height, {
 | 
				
			||||||
 | 
					      Text? labelText,
 | 
				
			||||||
 | 
					      BoxConstraints? labelConstraints,
 | 
				
			||||||
 | 
					    }) {
 | 
				
			||||||
 | 
					      final scrollThumb = ClipPath(
 | 
				
			||||||
 | 
					        child: Container(
 | 
				
			||||||
 | 
					          height: height,
 | 
				
			||||||
 | 
					          width: 20.0,
 | 
				
			||||||
 | 
					          decoration: BoxDecoration(
 | 
				
			||||||
 | 
					            color: backgroundColor,
 | 
				
			||||||
 | 
					            borderRadius: const BorderRadius.all(
 | 
				
			||||||
 | 
					              Radius.circular(12.0),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        clipper: ArrowClipper(),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return buildScrollThumbAndLabel(
 | 
				
			||||||
 | 
					        scrollThumb: scrollThumb,
 | 
				
			||||||
 | 
					        backgroundColor: backgroundColor,
 | 
				
			||||||
 | 
					        thumbAnimation: thumbAnimation,
 | 
				
			||||||
 | 
					        labelAnimation: labelAnimation,
 | 
				
			||||||
 | 
					        labelText: labelText,
 | 
				
			||||||
 | 
					        labelConstraints: labelConstraints,
 | 
				
			||||||
 | 
					        alwaysVisibleScrollThumb: alwaysVisibleScrollThumb,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static ScrollThumbBuilder _thumbRRectBuilder(Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      Color backgroundColor,
 | 
				
			||||||
 | 
					      Animation<double> thumbAnimation,
 | 
				
			||||||
 | 
					      Animation<double> labelAnimation,
 | 
				
			||||||
 | 
					      double height, {
 | 
				
			||||||
 | 
					      Text? labelText,
 | 
				
			||||||
 | 
					      BoxConstraints? labelConstraints,
 | 
				
			||||||
 | 
					    }) {
 | 
				
			||||||
 | 
					      final scrollThumb = Material(
 | 
				
			||||||
 | 
					        elevation: 4.0,
 | 
				
			||||||
 | 
					        child: Container(
 | 
				
			||||||
 | 
					          constraints: BoxConstraints.tight(
 | 
				
			||||||
 | 
					            Size(16.0, height),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        color: backgroundColor,
 | 
				
			||||||
 | 
					        borderRadius: const BorderRadius.all(Radius.circular(7.0)),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return buildScrollThumbAndLabel(
 | 
				
			||||||
 | 
					        scrollThumb: scrollThumb,
 | 
				
			||||||
 | 
					        backgroundColor: backgroundColor,
 | 
				
			||||||
 | 
					        thumbAnimation: thumbAnimation,
 | 
				
			||||||
 | 
					        labelAnimation: labelAnimation,
 | 
				
			||||||
 | 
					        labelText: labelText,
 | 
				
			||||||
 | 
					        labelConstraints: labelConstraints,
 | 
				
			||||||
 | 
					        alwaysVisibleScrollThumb: alwaysVisibleScrollThumb,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ScrollLabel extends StatelessWidget {
 | 
				
			||||||
 | 
					  final Animation<double>? animation;
 | 
				
			||||||
 | 
					  final Color backgroundColor;
 | 
				
			||||||
 | 
					  final Text child;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final BoxConstraints? constraints;
 | 
				
			||||||
 | 
					  static const BoxConstraints _defaultConstraints = BoxConstraints.tightFor(width: 72.0, height: 28.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const ScrollLabel({
 | 
				
			||||||
 | 
					    Key? key,
 | 
				
			||||||
 | 
					    required this.child,
 | 
				
			||||||
 | 
					    required this.animation,
 | 
				
			||||||
 | 
					    required this.backgroundColor,
 | 
				
			||||||
 | 
					    this.constraints = _defaultConstraints,
 | 
				
			||||||
 | 
					  }) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return FadeTransition(
 | 
				
			||||||
 | 
					      opacity: animation!,
 | 
				
			||||||
 | 
					      child: Container(
 | 
				
			||||||
 | 
					        margin: const EdgeInsets.only(right: 12.0),
 | 
				
			||||||
 | 
					        child: Material(
 | 
				
			||||||
 | 
					          elevation: 4.0,
 | 
				
			||||||
 | 
					          color: backgroundColor,
 | 
				
			||||||
 | 
					          borderRadius: const BorderRadius.all(Radius.circular(16.0)),
 | 
				
			||||||
 | 
					          child: Container(
 | 
				
			||||||
 | 
					            constraints: constraints ?? _defaultConstraints,
 | 
				
			||||||
 | 
					            alignment: Alignment.center,
 | 
				
			||||||
 | 
					            child: child,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin {
 | 
				
			||||||
 | 
					  late double _barOffset;
 | 
				
			||||||
 | 
					  late double _viewOffset;
 | 
				
			||||||
 | 
					  late bool _isDragInProcess;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  late AnimationController _thumbAnimationController;
 | 
				
			||||||
 | 
					  late Animation<double> _thumbAnimation;
 | 
				
			||||||
 | 
					  late AnimationController _labelAnimationController;
 | 
				
			||||||
 | 
					  late Animation<double> _labelAnimation;
 | 
				
			||||||
 | 
					  Timer? _fadeoutTimer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void initState() {
 | 
				
			||||||
 | 
					    super.initState();
 | 
				
			||||||
 | 
					    _barOffset = 0.0;
 | 
				
			||||||
 | 
					    _viewOffset = 0.0;
 | 
				
			||||||
 | 
					    _isDragInProcess = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _thumbAnimationController = AnimationController(
 | 
				
			||||||
 | 
					      vsync: this,
 | 
				
			||||||
 | 
					      duration: widget.scrollbarAnimationDuration,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _thumbAnimation = CurvedAnimation(
 | 
				
			||||||
 | 
					      parent: _thumbAnimationController,
 | 
				
			||||||
 | 
					      curve: Curves.fastOutSlowIn,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _labelAnimationController = AnimationController(
 | 
				
			||||||
 | 
					      vsync: this,
 | 
				
			||||||
 | 
					      duration: widget.scrollbarAnimationDuration,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _labelAnimation = CurvedAnimation(
 | 
				
			||||||
 | 
					      parent: _labelAnimationController,
 | 
				
			||||||
 | 
					      curve: Curves.fastOutSlowIn,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void dispose() {
 | 
				
			||||||
 | 
					    _thumbAnimationController.dispose();
 | 
				
			||||||
 | 
					    _labelAnimationController.dispose();
 | 
				
			||||||
 | 
					    _fadeoutTimer?.cancel();
 | 
				
			||||||
 | 
					    super.dispose();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double get barMaxScrollExtent => context.size!.height - widget.heightScrollThumb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double get barMinScrollExtent => 0.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double get viewMaxScrollExtent => widget.controller.position.maxScrollExtent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double get viewMinScrollExtent => widget.controller.position.minScrollExtent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    Text? labelText;
 | 
				
			||||||
 | 
					    if (widget.labelTextBuilder != null && _isDragInProcess) {
 | 
				
			||||||
 | 
					      labelText = widget.labelTextBuilder!(
 | 
				
			||||||
 | 
					        _viewOffset + _barOffset + widget.heightScrollThumb / 2,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
 | 
				
			||||||
 | 
					      //print("LayoutBuilder constraints=$constraints");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return NotificationListener<ScrollNotification>(
 | 
				
			||||||
 | 
					        onNotification: (ScrollNotification notification) {
 | 
				
			||||||
 | 
					          changePosition(notification);
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        child: Stack(
 | 
				
			||||||
 | 
					          children: <Widget>[
 | 
				
			||||||
 | 
					            RepaintBoundary(
 | 
				
			||||||
 | 
					              child: widget.child,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            RepaintBoundary(
 | 
				
			||||||
 | 
					                child: GestureDetector(
 | 
				
			||||||
 | 
					              onVerticalDragStart: _onVerticalDragStart,
 | 
				
			||||||
 | 
					              onVerticalDragUpdate: _onVerticalDragUpdate,
 | 
				
			||||||
 | 
					              onVerticalDragEnd: _onVerticalDragEnd,
 | 
				
			||||||
 | 
					              child: Container(
 | 
				
			||||||
 | 
					                alignment: Alignment.topRight,
 | 
				
			||||||
 | 
					                margin: EdgeInsets.only(top: _barOffset),
 | 
				
			||||||
 | 
					                padding: widget.padding,
 | 
				
			||||||
 | 
					                child: widget.scrollThumbBuilder(
 | 
				
			||||||
 | 
					                  widget.backgroundColor,
 | 
				
			||||||
 | 
					                  _thumbAnimation,
 | 
				
			||||||
 | 
					                  _labelAnimation,
 | 
				
			||||||
 | 
					                  widget.heightScrollThumb,
 | 
				
			||||||
 | 
					                  labelText: labelText,
 | 
				
			||||||
 | 
					                  labelConstraints: widget.labelConstraints,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            )),
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  //scroll bar has received notification that it's view was scrolled
 | 
				
			||||||
 | 
					  //so it should also changes his position
 | 
				
			||||||
 | 
					  //but only if it isn't dragged
 | 
				
			||||||
 | 
					  changePosition(ScrollNotification notification) {
 | 
				
			||||||
 | 
					    if (_isDragInProcess) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setState(() {
 | 
				
			||||||
 | 
					      if (notification is ScrollUpdateNotification) {
 | 
				
			||||||
 | 
					        _barOffset += getBarDelta(
 | 
				
			||||||
 | 
					          notification.scrollDelta!,
 | 
				
			||||||
 | 
					          barMaxScrollExtent,
 | 
				
			||||||
 | 
					          viewMaxScrollExtent,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (_barOffset < barMinScrollExtent) {
 | 
				
			||||||
 | 
					          _barOffset = barMinScrollExtent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (_barOffset > barMaxScrollExtent) {
 | 
				
			||||||
 | 
					          _barOffset = barMaxScrollExtent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _viewOffset += notification.scrollDelta!;
 | 
				
			||||||
 | 
					        if (_viewOffset < widget.controller.position.minScrollExtent) {
 | 
				
			||||||
 | 
					          _viewOffset = widget.controller.position.minScrollExtent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (_viewOffset > viewMaxScrollExtent) {
 | 
				
			||||||
 | 
					          _viewOffset = viewMaxScrollExtent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (notification is ScrollUpdateNotification || notification is OverscrollNotification) {
 | 
				
			||||||
 | 
					        if (_thumbAnimationController.status != AnimationStatus.forward) {
 | 
				
			||||||
 | 
					          _thumbAnimationController.forward();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _fadeoutTimer?.cancel();
 | 
				
			||||||
 | 
					        _fadeoutTimer = Timer(widget.scrollbarTimeToFade, () {
 | 
				
			||||||
 | 
					          _thumbAnimationController.reverse();
 | 
				
			||||||
 | 
					          _labelAnimationController.reverse();
 | 
				
			||||||
 | 
					          _fadeoutTimer = null;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double getBarDelta(
 | 
				
			||||||
 | 
					    double scrollViewDelta,
 | 
				
			||||||
 | 
					    double barMaxScrollExtent,
 | 
				
			||||||
 | 
					    double viewMaxScrollExtent,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return scrollViewDelta * barMaxScrollExtent / viewMaxScrollExtent;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double getScrollViewDelta(
 | 
				
			||||||
 | 
					    double barDelta,
 | 
				
			||||||
 | 
					    double barMaxScrollExtent,
 | 
				
			||||||
 | 
					    double viewMaxScrollExtent,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return barDelta * viewMaxScrollExtent / barMaxScrollExtent;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onVerticalDragStart(DragStartDetails details) {
 | 
				
			||||||
 | 
					    setState(() {
 | 
				
			||||||
 | 
					      _isDragInProcess = true;
 | 
				
			||||||
 | 
					      _labelAnimationController.forward();
 | 
				
			||||||
 | 
					      _fadeoutTimer?.cancel();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onVerticalDragUpdate(DragUpdateDetails details) {
 | 
				
			||||||
 | 
					    setState(() {
 | 
				
			||||||
 | 
					      if (_thumbAnimationController.status != AnimationStatus.forward) {
 | 
				
			||||||
 | 
					        _thumbAnimationController.forward();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (_isDragInProcess) {
 | 
				
			||||||
 | 
					        _barOffset += details.delta.dy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (_barOffset < barMinScrollExtent) {
 | 
				
			||||||
 | 
					          _barOffset = barMinScrollExtent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (_barOffset > barMaxScrollExtent) {
 | 
				
			||||||
 | 
					          _barOffset = barMaxScrollExtent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        double viewDelta = getScrollViewDelta(details.delta.dy, barMaxScrollExtent, viewMaxScrollExtent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _viewOffset = widget.controller.position.pixels + viewDelta;
 | 
				
			||||||
 | 
					        if (_viewOffset < widget.controller.position.minScrollExtent) {
 | 
				
			||||||
 | 
					          _viewOffset = widget.controller.position.minScrollExtent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (_viewOffset > viewMaxScrollExtent) {
 | 
				
			||||||
 | 
					          _viewOffset = viewMaxScrollExtent;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        widget.controller.jumpTo(_viewOffset);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _onVerticalDragEnd(DragEndDetails details) {
 | 
				
			||||||
 | 
					    _fadeoutTimer = Timer(widget.scrollbarTimeToFade, () {
 | 
				
			||||||
 | 
					      _thumbAnimationController.reverse();
 | 
				
			||||||
 | 
					      _labelAnimationController.reverse();
 | 
				
			||||||
 | 
					      _fadeoutTimer = null;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    setState(() {
 | 
				
			||||||
 | 
					      _isDragInProcess = false;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Draws 2 triangles like arrow up and arrow down
 | 
				
			||||||
 | 
					class ArrowCustomPainter extends CustomPainter {
 | 
				
			||||||
 | 
					  Color color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ArrowCustomPainter(this.color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void paint(Canvas canvas, Size size) {
 | 
				
			||||||
 | 
					    final paint = Paint()..color = color;
 | 
				
			||||||
 | 
					    const width = 12.0;
 | 
				
			||||||
 | 
					    const height = 8.0;
 | 
				
			||||||
 | 
					    final baseX = size.width / 2;
 | 
				
			||||||
 | 
					    final baseY = size.height / 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canvas.drawPath(
 | 
				
			||||||
 | 
					      _trianglePath(Offset(baseX, baseY - 2.0), width, height, true),
 | 
				
			||||||
 | 
					      paint,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    canvas.drawPath(
 | 
				
			||||||
 | 
					      _trianglePath(Offset(baseX, baseY + 2.0), width, height, false),
 | 
				
			||||||
 | 
					      paint,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static Path _trianglePath(Offset o, double width, double height, bool isUp) {
 | 
				
			||||||
 | 
					    return Path()
 | 
				
			||||||
 | 
					      ..moveTo(o.dx, o.dy)
 | 
				
			||||||
 | 
					      ..lineTo(o.dx + width, o.dy)
 | 
				
			||||||
 | 
					      ..lineTo(o.dx + (width / 2), isUp ? o.dy - height : o.dy + height)
 | 
				
			||||||
 | 
					      ..close();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///This cut 2 lines in arrow shape
 | 
				
			||||||
 | 
					class ArrowClipper extends CustomClipper<Path> {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Path getClip(Size size) {
 | 
				
			||||||
 | 
					    Path path = Path();
 | 
				
			||||||
 | 
					    path.lineTo(0.0, size.height);
 | 
				
			||||||
 | 
					    path.lineTo(size.width, size.height);
 | 
				
			||||||
 | 
					    path.lineTo(size.width, 0.0);
 | 
				
			||||||
 | 
					    path.lineTo(0.0, 0.0);
 | 
				
			||||||
 | 
					    path.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    double arrowWidth = 8.0;
 | 
				
			||||||
 | 
					    double startPointX = (size.width - arrowWidth) / 2;
 | 
				
			||||||
 | 
					    double startPointY = size.height / 2 - arrowWidth / 2;
 | 
				
			||||||
 | 
					    path.moveTo(startPointX, startPointY);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX + arrowWidth, startPointY);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX + arrowWidth, startPointY + 1.0);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX, startPointY + 1.0);
 | 
				
			||||||
 | 
					    path.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    startPointY = size.height / 2 + arrowWidth / 2;
 | 
				
			||||||
 | 
					    path.moveTo(startPointX + arrowWidth, startPointY);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX, startPointY);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX, startPointY - 1.0);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0);
 | 
				
			||||||
 | 
					    path.lineTo(startPointX + arrowWidth, startPointY - 1.0);
 | 
				
			||||||
 | 
					    path.close();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return path;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SlideFadeTransition extends StatelessWidget {
 | 
				
			||||||
 | 
					  final Animation<double> animation;
 | 
				
			||||||
 | 
					  final Widget child;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const SlideFadeTransition({
 | 
				
			||||||
 | 
					    Key? key,
 | 
				
			||||||
 | 
					    required this.animation,
 | 
				
			||||||
 | 
					    required this.child,
 | 
				
			||||||
 | 
					  }) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return AnimatedBuilder(
 | 
				
			||||||
 | 
					      animation: animation,
 | 
				
			||||||
 | 
					      builder: (context, child) => animation.value == 0.0 ? Container() : child!,
 | 
				
			||||||
 | 
					      child: SlideTransition(
 | 
				
			||||||
 | 
					        position: Tween(
 | 
				
			||||||
 | 
					          begin: const Offset(0.3, 0.0),
 | 
				
			||||||
 | 
					          end: const Offset(0.0, 0.0),
 | 
				
			||||||
 | 
					        ).animate(animation),
 | 
				
			||||||
 | 
					        child: FadeTransition(
 | 
				
			||||||
 | 
					          opacity: animation,
 | 
				
			||||||
 | 
					          child: child,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,15 +1,12 @@
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/rendering.dart';
 | 
					 | 
				
			||||||
import 'package:flutter_hooks/flutter_hooks.dart';
 | 
					import 'package:flutter_hooks/flutter_hooks.dart';
 | 
				
			||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
					import 'package:hooks_riverpod/hooks_riverpod.dart';
 | 
				
			||||||
 | 
					import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
 | 
					import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/ui/profile_drawer.dart';
 | 
					import 'package:immich_mobile/modules/home/ui/profile_drawer.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/models/backup_state.model.dart';
 | 
					 | 
				
			||||||
import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.dart';
 | 
					import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/ui/image_grid.dart';
 | 
					import 'package:immich_mobile/modules/home/ui/image_grid.dart';
 | 
				
			||||||
import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
 | 
					import 'package:immich_mobile/modules/home/providers/asset.provider.dart';
 | 
				
			||||||
import 'package:immich_mobile/shared/providers/backup.provider.dart';
 | 
					 | 
				
			||||||
import 'package:visibility_detector/visibility_detector.dart';
 | 
					 | 
				
			||||||
import 'package:intl/intl.dart';
 | 
					import 'package:intl/intl.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HomePage extends HookConsumerWidget {
 | 
					class HomePage extends HookConsumerWidget {
 | 
				
			||||||
@ -21,8 +18,8 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
    ScrollController _scrollController = useScrollController();
 | 
					    ScrollController _scrollController = useScrollController();
 | 
				
			||||||
    List<ImmichAssetGroupByDate> assetGroup = ref.watch(assetProvider);
 | 
					    List<ImmichAssetGroupByDate> assetGroup = ref.watch(assetProvider);
 | 
				
			||||||
    List<Widget> imageGridGroup = [];
 | 
					    List<Widget> imageGridGroup = [];
 | 
				
			||||||
    List<GlobalKey> monthGroupKey = [];
 | 
					    final scrollLabelText = useState("");
 | 
				
			||||||
    final monthInView = useState<String>("");
 | 
					
 | 
				
			||||||
    _scrollControllerCallback() {
 | 
					    _scrollControllerCallback() {
 | 
				
			||||||
      var endOfPage = _scrollController.position.maxScrollExtent;
 | 
					      var endOfPage = _scrollController.position.maxScrollExtent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -35,13 +32,6 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        _showBackToTopBtn.value = false;
 | 
					        _showBackToTopBtn.value = false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Quick Scroll For Jumping to Month
 | 
					 | 
				
			||||||
      if (_scrollController.position.userScrollDirection == ScrollDirection.forward) {
 | 
					 | 
				
			||||||
        // Scroll UP
 | 
					 | 
				
			||||||
      } else if (_scrollController.position.userScrollDirection == ScrollDirection.reverse) {
 | 
					 | 
				
			||||||
        // SCroll Down
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() {
 | 
					    useEffect(() {
 | 
				
			||||||
@ -55,53 +45,29 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
      };
 | 
					      };
 | 
				
			||||||
    }, []);
 | 
					    }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SliverToBoxAdapter _buildMonthGroupTitle(String dateTitle, BuildContext context) {
 | 
					 | 
				
			||||||
      return SliverToBoxAdapter(
 | 
					 | 
				
			||||||
        child: Padding(
 | 
					 | 
				
			||||||
          padding: const EdgeInsets.only(left: 10.0, top: 32),
 | 
					 | 
				
			||||||
          child: Text(
 | 
					 | 
				
			||||||
            DateFormat('MMMM, y').format(
 | 
					 | 
				
			||||||
              DateTime.parse(dateTitle),
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            style: TextStyle(
 | 
					 | 
				
			||||||
              fontSize: 24,
 | 
					 | 
				
			||||||
              fontWeight: FontWeight.bold,
 | 
					 | 
				
			||||||
              color: Theme.of(context).primaryColor,
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    SliverToBoxAdapter _buildDateGroupTitle(String dateTitle) {
 | 
					    SliverToBoxAdapter _buildDateGroupTitle(String dateTitle) {
 | 
				
			||||||
      var currentYear = DateTime.now().year;
 | 
					      var currentYear = DateTime.now().year;
 | 
				
			||||||
      var groupYear = DateTime.parse(dateTitle).year;
 | 
					      var groupYear = DateTime.parse(dateTitle).year;
 | 
				
			||||||
      var formatDateTemplate = currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy';
 | 
					      var formatDateTemplate = currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy';
 | 
				
			||||||
      var dateText = DateFormat(formatDateTemplate).format(DateTime.parse(dateTitle));
 | 
					      var dateText = DateFormat(formatDateTemplate).format(DateTime.parse(dateTitle));
 | 
				
			||||||
      var monthText = DateFormat('MMMM, y').format(DateTime.parse(dateTitle));
 | 
					
 | 
				
			||||||
      return SliverToBoxAdapter(
 | 
					      return SliverToBoxAdapter(
 | 
				
			||||||
        child: VisibilityDetector(
 | 
					        child: Padding(
 | 
				
			||||||
          key: Key(dateText),
 | 
					          padding: const EdgeInsets.only(top: 24.0, bottom: 24.0, left: 3.0),
 | 
				
			||||||
          onVisibilityChanged: (visibilityInfo) {
 | 
					          child: Row(
 | 
				
			||||||
            monthInView.value = monthText;
 | 
					            children: [
 | 
				
			||||||
          },
 | 
					              Padding(
 | 
				
			||||||
          child: Padding(
 | 
					                padding: const EdgeInsets.only(left: 8.0, bottom: 5.0, top: 5.0),
 | 
				
			||||||
            padding: const EdgeInsets.only(top: 24.0, bottom: 24.0, left: 3.0),
 | 
					                child: Text(
 | 
				
			||||||
            child: Row(
 | 
					                  dateText,
 | 
				
			||||||
              children: [
 | 
					                  style: const TextStyle(
 | 
				
			||||||
                Padding(
 | 
					                    fontSize: 14,
 | 
				
			||||||
                  padding: const EdgeInsets.only(left: 8.0, bottom: 5.0, top: 5.0),
 | 
					                    fontWeight: FontWeight.bold,
 | 
				
			||||||
                  child: Text(
 | 
					                    color: Colors.black87,
 | 
				
			||||||
                    dateText,
 | 
					 | 
				
			||||||
                    style: const TextStyle(
 | 
					 | 
				
			||||||
                      fontSize: 14,
 | 
					 | 
				
			||||||
                      fontWeight: FontWeight.bold,
 | 
					 | 
				
			||||||
                      color: Colors.black87,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ],
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ],
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
@ -121,58 +87,82 @@ class HomePage extends HookConsumerWidget {
 | 
				
			|||||||
          if ((currentMonth! - previousMonth!) != 0) {
 | 
					          if ((currentMonth! - previousMonth!) != 0) {
 | 
				
			||||||
            var monthTitleText = DateFormat('MMMM, y').format(DateTime.parse(dateTitle));
 | 
					            var monthTitleText = DateFormat('MMMM, y').format(DateTime.parse(dateTitle));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            imageGridGroup.add(_buildMonthGroupTitle(monthTitleText, context));
 | 
					            imageGridGroup.add(
 | 
				
			||||||
 | 
					              MonthlyTitleText(monthTitleText: monthTitleText),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          imageGridGroup.add(
 | 
					          imageGridGroup.add(
 | 
				
			||||||
            _buildDateGroupTitle(dateTitle),
 | 
					            _buildDateGroupTitle(dateTitle),
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          imageGridGroup.add(ImageGrid(assetGroup: assetGroup));
 | 
					          imageGridGroup.add(
 | 
				
			||||||
 | 
					            ImageGrid(assetGroup: assetGroup),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          lastGroupDate = dateTitle;
 | 
					          lastGroupDate = dateTitle;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return SafeArea(
 | 
					      return SafeArea(
 | 
				
			||||||
        child: Stack(children: [
 | 
					        child: DraggableScrollbar.semicircle(
 | 
				
			||||||
          RawScrollbar(
 | 
					          // labelTextBuilder: (offset) {
 | 
				
			||||||
            minThumbLength: 50,
 | 
					          //   final int currentItem = _scrollController.hasClients
 | 
				
			||||||
            isAlwaysShown: false,
 | 
					          //       ? (_scrollController.offset / _scrollController.position.maxScrollExtent * imageGridGroup.length)
 | 
				
			||||||
            interactive: true,
 | 
					          //           .floor()
 | 
				
			||||||
 | 
					          //       : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          //   if (imageGridGroup[currentItem] is MonthlyTitleText) {
 | 
				
			||||||
 | 
					          //     MonthlyTitleText item = imageGridGroup[currentItem] as MonthlyTitleText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          //     scrollLabelText.value = item.monthTitleText;
 | 
				
			||||||
 | 
					          //   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          //   return Text(scrollLabelText.value);
 | 
				
			||||||
 | 
					          // },
 | 
				
			||||||
 | 
					          // labelConstraints: const BoxConstraints.tightFor(width: 200.0, height: 30.0),
 | 
				
			||||||
 | 
					          controller: _scrollController,
 | 
				
			||||||
 | 
					          heightScrollThumb: 40.0,
 | 
				
			||||||
 | 
					          child: CustomScrollView(
 | 
				
			||||||
            controller: _scrollController,
 | 
					            controller: _scrollController,
 | 
				
			||||||
            thickness: 50,
 | 
					            slivers: [
 | 
				
			||||||
            crossAxisMargin: -20,
 | 
					              ImmichSliverAppBar(imageGridGroup: imageGridGroup),
 | 
				
			||||||
            mainAxisMargin: 70,
 | 
					              ...imageGridGroup,
 | 
				
			||||||
            timeToFade: const Duration(seconds: 2),
 | 
					            ],
 | 
				
			||||||
            thumbColor: Colors.blueGrey,
 | 
					 | 
				
			||||||
            radius: const Radius.circular(30),
 | 
					 | 
				
			||||||
            child: CustomScrollView(
 | 
					 | 
				
			||||||
              controller: _scrollController,
 | 
					 | 
				
			||||||
              slivers: [
 | 
					 | 
				
			||||||
                ImmichSliverAppBar(imageGridGroup: imageGridGroup),
 | 
					 | 
				
			||||||
                ...imageGridGroup,
 | 
					 | 
				
			||||||
              ],
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ]),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      drawer: const ProfileDrawer(),
 | 
					      drawer: const ProfileDrawer(),
 | 
				
			||||||
      body: _buildBody(),
 | 
					      body: _buildBody(),
 | 
				
			||||||
      floatingActionButton: _showBackToTopBtn.value
 | 
					    );
 | 
				
			||||||
          ? FloatingActionButton.small(
 | 
					  }
 | 
				
			||||||
              enableFeedback: true,
 | 
					}
 | 
				
			||||||
              backgroundColor: Theme.of(context).secondaryHeaderColor,
 | 
					
 | 
				
			||||||
              foregroundColor: Theme.of(context).primaryColor,
 | 
					class MonthlyTitleText extends StatelessWidget {
 | 
				
			||||||
              onPressed: () {
 | 
					  const MonthlyTitleText({
 | 
				
			||||||
                _scrollController.animateTo(0, duration: const Duration(seconds: 1), curve: Curves.easeOutExpo);
 | 
					    Key? key,
 | 
				
			||||||
              },
 | 
					    required this.monthTitleText,
 | 
				
			||||||
              child: const Icon(Icons.keyboard_arrow_up_rounded),
 | 
					  }) : super(key: key);
 | 
				
			||||||
            )
 | 
					
 | 
				
			||||||
          : null,
 | 
					  final String monthTitleText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return SliverToBoxAdapter(
 | 
				
			||||||
 | 
					      child: Padding(
 | 
				
			||||||
 | 
					        padding: const EdgeInsets.only(left: 10.0, top: 32),
 | 
				
			||||||
 | 
					        child: Text(
 | 
				
			||||||
 | 
					          monthTitleText,
 | 
				
			||||||
 | 
					          style: TextStyle(
 | 
				
			||||||
 | 
					            fontSize: 24,
 | 
				
			||||||
 | 
					            fontWeight: FontWeight.bold,
 | 
				
			||||||
 | 
					            color: Theme.of(context).primaryColor,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user