diff --git a/lib/ui/components/linear_gradient.dart b/lib/ui/components/linear_gradient.dart index bdb2ffe0..17963722 100644 --- a/lib/ui/components/linear_gradient.dart +++ b/lib/ui/components/linear_gradient.dart @@ -5,26 +5,21 @@ import 'package:envoy/ui/theme/envoy_colors.dart'; import 'package:flutter/material.dart'; -class LinearGradients { - static LinearGradient blogPostGradient(double topGradientEnd) { - return LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: const [ - EnvoyColors.solidWhite, - Colors.transparent, - Colors.transparent, - EnvoyColors.solidWhite, - ], - stops: [0.0, topGradientEnd, 0.85, 0.96], - ); - } -} - class ScrollGradientMask extends StatefulWidget { final Widget child; + final double start; + final double topGradientValue; + final double bottomGradientValue; + final double end; - const ScrollGradientMask({required this.child, super.key}); + const ScrollGradientMask({ + required this.child, + this.start = 0.0, + this.topGradientValue = 0.05, + this.bottomGradientValue = 0.95, + this.end = 1.0, + super.key, + }); @override ScrollGradientMaskState createState() => ScrollGradientMaskState(); @@ -33,6 +28,8 @@ class ScrollGradientMask extends StatefulWidget { class ScrollGradientMaskState extends State { final ValueNotifier topGradientEndNotifier = ValueNotifier(0.0); + final ValueNotifier bottomGradientStartNotifier = + ValueNotifier(1.0); late ScrollController _scrollController; @override @@ -40,43 +37,85 @@ class ScrollGradientMaskState extends State { super.initState(); _scrollController = ScrollController() ..addListener(() { - if (_scrollController.offset == 0) { - topGradientEndNotifier.value = 0.0; - } else { - topGradientEndNotifier.value = 0.05; - } + _updateGradients(); }); + + topGradientEndNotifier.value = widget.start; + bottomGradientStartNotifier.value = widget.bottomGradientValue; + } + + void _updateGradients() { + if (_scrollController.offset == 0) { + topGradientEndNotifier.value = widget.start; + bottomGradientStartNotifier.value = widget.bottomGradientValue; + } else if (_scrollController.offset >= + _scrollController.position.maxScrollExtent) { + topGradientEndNotifier.value = widget.topGradientValue; + bottomGradientStartNotifier.value = widget.end; + } else { + topGradientEndNotifier.value = widget.topGradientValue; + bottomGradientStartNotifier.value = widget.bottomGradientValue; + } } @override void dispose() { _scrollController.dispose(); topGradientEndNotifier.dispose(); + bottomGradientStartNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ValueListenableBuilder( - valueListenable: topGradientEndNotifier, - builder: (context, topGradientEnd, child) { - return ShaderMask( - shaderCallback: (Rect rect) { - return LinearGradients.blogPostGradient(topGradientEnd) - .createShader(rect); + valueListenable: bottomGradientStartNotifier, + builder: (context, bottomGradientStart, child) { + return ValueListenableBuilder( + valueListenable: topGradientEndNotifier, + builder: (context, topGradientEnd, child) { + return ShaderMask( + shaderCallback: (Rect rect) { + return LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: const [ + EnvoyColors.solidWhite, + Colors.transparent, + Colors.transparent, + EnvoyColors.solidWhite, + ], + stops: [ + 0.0, + topGradientEnd, + bottomGradientStart, + 1.0, + ], + ).createShader(rect); + }, + blendMode: BlendMode.dstOut, + child: NotificationListener( + onNotification: (scrollNotification) { + if (scrollNotification.metrics.pixels == 0) { + topGradientEndNotifier.value = widget.start; + bottomGradientStartNotifier.value = + widget.bottomGradientValue; + } else if (scrollNotification.metrics.pixels >= + scrollNotification.metrics.maxScrollExtent) { + topGradientEndNotifier.value = widget.topGradientValue; + bottomGradientStartNotifier.value = widget.end; + } else { + topGradientEndNotifier.value = widget.topGradientValue; + bottomGradientStartNotifier.value = + widget.bottomGradientValue; + } + return true; + }, + child: widget.child, + ), + ); }, - blendMode: BlendMode.dstOut, - child: NotificationListener( - onNotification: (scrollNotification) { - if (scrollNotification.metrics.pixels == 0) { - topGradientEndNotifier.value = 0.0; - } else { - topGradientEndNotifier.value = 0.05; - } - return true; - }, - child: widget.child, - ), + child: widget.child, ); }, child: widget.child, diff --git a/lib/ui/home/cards/learn/components/blog_post_card.dart b/lib/ui/home/cards/learn/components/blog_post_card.dart index eae912eb..c05f69a3 100644 --- a/lib/ui/home/cards/learn/components/blog_post_card.dart +++ b/lib/ui/home/cards/learn/components/blog_post_card.dart @@ -156,6 +156,8 @@ class BlogPostCardState extends State { Widget build(BuildContext context) { return Center( child: ScrollGradientMask( + bottomGradientValue: 0.85, + end: 0.96, child: Padding( padding: const EdgeInsets.only( bottom: EnvoySpacing.large1, diff --git a/lib/ui/home/cards/learn/learn_card.dart b/lib/ui/home/cards/learn/learn_card.dart index f38d6cb4..bfc070d9 100644 --- a/lib/ui/home/cards/learn/learn_card.dart +++ b/lib/ui/home/cards/learn/learn_card.dart @@ -64,6 +64,8 @@ class _LearnCardState extends ConsumerState { final bool isAllEmpty = isSearchEmpty || isFilterEmpty; return ScrollGradientMask( + bottomGradientValue: 0.85, + end: 0.96, child: CustomScrollView( physics: isAllEmpty ? const NeverScrollableScrollPhysics() : null, slivers: [