diff --git a/lib/common_libs.dart b/lib/common_libs.dart index c9578c57..54f17731 100644 --- a/lib/common_libs.dart +++ b/lib/common_libs.dart @@ -26,4 +26,5 @@ export 'package:wonders/ui/common/controls/buttons.dart'; export 'package:wonders/ui/common/controls/circle_buttons.dart'; export 'package:wonders/ui/common/controls/scroll_decorator.dart'; export 'package:wonders/ui/common/controls/app_image.dart'; +export 'package:wonders/ui/common/listenable_builder.dart'; export 'package:flutter_animate/flutter_animate.dart'; diff --git a/lib/ui/common/controls/scroll_decorator.dart b/lib/ui/common/controls/scroll_decorator.dart index 05c191c7..0800c2bd 100644 --- a/lib/ui/common/controls/scroll_decorator.dart +++ b/lib/ui/common/controls/scroll_decorator.dart @@ -1,6 +1,4 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; +import 'package:wonders/common_libs.dart'; /// Easily add visual decorations to a scrolling widget based on the state of its controller. class ScrollDecorator extends StatefulWidget { @@ -125,8 +123,8 @@ class _ScrollDecoratorState extends State { @override Widget build(BuildContext context) { content = widget.builder(currentController); - return AnimatedBuilder( - animation: currentController, + return ListenableBuilder( + listenable: currentController, builder: (_, __) { return Stack( children: [ diff --git a/lib/ui/common/fade_color_transition.dart b/lib/ui/common/fade_color_transition.dart index fd394a4b..b3b971f7 100644 --- a/lib/ui/common/fade_color_transition.dart +++ b/lib/ui/common/fade_color_transition.dart @@ -8,8 +8,8 @@ class FadeColorTransition extends StatelessWidget { final Color color; @override - Widget build(BuildContext context) => AnimatedBuilder( - animation: animation, + Widget build(BuildContext context) => ListenableBuilder( + listenable: animation, builder: (_, __) => Container(color: color.withOpacity(animation.value)), ); } diff --git a/lib/ui/common/listenable_builder.dart b/lib/ui/common/listenable_builder.dart new file mode 100644 index 00000000..7400681b --- /dev/null +++ b/lib/ui/common/listenable_builder.dart @@ -0,0 +1,10 @@ +import 'package:flutter/cupertino.dart'; + +class ListenableBuilder extends AnimatedBuilder { + const ListenableBuilder({ + Key? key, + required Listenable listenable, + required TransitionBuilder builder, + Widget? child, + }) : super(key: key, animation: listenable, builder: builder, child: child); +} diff --git a/lib/ui/common/modals/fullscreen_url_img_viewer.dart b/lib/ui/common/modals/fullscreen_url_img_viewer.dart index 5e5571cc..b1e1ea37 100644 --- a/lib/ui/common/modals/fullscreen_url_img_viewer.dart +++ b/lib/ui/common/modals/fullscreen_url_img_viewer.dart @@ -26,8 +26,8 @@ class _FullscreenUrlImgViewerState extends State { @override Widget build(BuildContext context) { - Widget content = AnimatedBuilder( - animation: _isZoomed, + Widget content = ListenableBuilder( + listenable: _isZoomed, builder: (_, __) { final bool enableSwipe = !_isZoomed.value && widget.urls.length > 1; return PageView.builder( diff --git a/lib/ui/screens/artifact/artifact_carousel/artifact_carousel_screen.dart b/lib/ui/screens/artifact/artifact_carousel/artifact_carousel_screen.dart index bde57027..f9decc27 100644 --- a/lib/ui/screens/artifact/artifact_carousel/artifact_carousel_screen.dart +++ b/lib/ui/screens/artifact/artifact_carousel/artifact_carousel_screen.dart @@ -218,8 +218,8 @@ class _ArtifactScreenState extends State { child: PageView.builder( controller: _controller, clipBehavior: Clip.none, - itemBuilder: (context, index) => AnimatedBuilder( - animation: _controller, + itemBuilder: (context, index) => ListenableBuilder( + listenable: _controller, builder: (_, __) { return _CarouselItem( index: index, diff --git a/lib/ui/screens/editorial/editorial_screen.dart b/lib/ui/screens/editorial/editorial_screen.dart index 1744c43c..364607df 100644 --- a/lib/ui/screens/editorial/editorial_screen.dart +++ b/lib/ui/screens/editorial/editorial_screen.dart @@ -164,8 +164,8 @@ class _WonderEditorialScreenState extends State { ), /// Home Btn - AnimatedBuilder( - animation: _scroller, + ListenableBuilder( + listenable: _scroller, builder: (_, child) { return AnimatedOpacity( opacity: _scrollPos.value > 0 ? 0 : 1, diff --git a/lib/ui/screens/editorial/widgets/_circular_title_bar.dart b/lib/ui/screens/editorial/widgets/_circular_title_bar.dart index f4c3ac5e..9ffca6a5 100644 --- a/lib/ui/screens/editorial/widgets/_circular_title_bar.dart +++ b/lib/ui/screens/editorial/widgets/_circular_title_bar.dart @@ -94,8 +94,8 @@ class _AnimatedCircleWithTextState extends State<_AnimatedCircleWithText> with S @override Widget build(_) { - return AnimatedBuilder( - animation: _anim, + return ListenableBuilder( + listenable: _anim, builder: (_, __) { var rot = _prevIndex > widget.index ? -pi : pi; return Transform.rotate( diff --git a/lib/ui/screens/editorial/widgets/_title_text.dart b/lib/ui/screens/editorial/widgets/_title_text.dart index 8b73b6a9..97f9c553 100644 --- a/lib/ui/screens/editorial/widgets/_title_text.dart +++ b/lib/ui/screens/editorial/widgets/_title_text.dart @@ -46,8 +46,8 @@ class _TitleText extends StatelessWidget { /// Wonder title text Semantics( sortKey: OrdinalSortKey(0), - child: AnimatedBuilder( - animation: scroller, + child: ListenableBuilder( + listenable: scroller, builder: (_, __) { final yPos = ContextUtils.getGlobalPos(context)?.dy ?? 0; bool enableHero = yPos > -100; @@ -68,8 +68,8 @@ class _TitleText extends StatelessWidget { ExcludeSemantics( child: Padding( padding: EdgeInsets.symmetric(horizontal: $styles.insets.md), - child: AnimatedBuilder( - animation: scroller, + child: ListenableBuilder( + listenable: scroller, builder: (_, __) => CompassDivider( isExpanded: scroller.position.pixels <= 0, linesColor: data.type.fgColor, diff --git a/lib/ui/screens/timeline/widgets/_bottom_scrubber.dart b/lib/ui/screens/timeline/widgets/_bottom_scrubber.dart index cb5a1a41..b75d6829 100644 --- a/lib/ui/screens/timeline/widgets/_bottom_scrubber.dart +++ b/lib/ui/screens/timeline/widgets/_bottom_scrubber.dart @@ -54,8 +54,8 @@ class _BottomScrubber extends StatelessWidget { ), /// Visible area, follows the position of scroller - AnimatedBuilder( - animation: scroller, + ListenableBuilder( + listenable: scroller, builder: (_, __) { ScrollPosition? pos; if (scroller.hasClients) pos = scroller.position; diff --git a/lib/ui/screens/timeline/widgets/_scrolling_viewport.dart b/lib/ui/screens/timeline/widgets/_scrolling_viewport.dart index 27476ac9..f2a7ab97 100644 --- a/lib/ui/screens/timeline/widgets/_scrolling_viewport.dart +++ b/lib/ui/screens/timeline/widgets/_scrolling_viewport.dart @@ -61,8 +61,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> { // Dashed line with a year that changes as we scroll IgnorePointer( ignoringSemantics: false, - child: AnimatedBuilder( - animation: controller.scroller, + child: ListenableBuilder( + listenable: controller.scroller, builder: (_, __) { return _DashedDividerWithYear(controller.calculateYearFromScrollPos()); }, @@ -79,8 +79,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> { Widget buildTimelineSection(WonderData data) { return ClipRRect( borderRadius: BorderRadius.circular(99), - child: AnimatedBuilder( - animation: controller.scroller, + child: ListenableBuilder( + listenable: controller.scroller, builder: (_, __) => TimelineSection( data, controller.calculateYearFromScrollPos(), @@ -128,8 +128,8 @@ class _ScalingViewportState extends State<_ScrollingViewport> { ), /// Event Markers, rebuilds on scroll - AnimatedBuilder( - animation: controller.scroller, + ListenableBuilder( + listenable: controller.scroller, builder: (_, __) => _EventMarkers( controller.calculateYearFromScrollPos(), onEventChanged: _handleEventMarkerChanged, diff --git a/lib/ui/screens/wonder_events/widgets/_events_list.dart b/lib/ui/screens/wonder_events/widgets/_events_list.dart index 9ebfd30d..e32d80ba 100644 --- a/lib/ui/screens/wonder_events/widgets/_events_list.dart +++ b/lib/ui/screens/wonder_events/widgets/_events_list.dart @@ -29,47 +29,10 @@ class _EventsListState extends State<_EventsList> { @override Widget build(BuildContext context) { - return LayoutBuilder( - builder: (_, constraints) { - return Stack( - children: [ - AnimatedBuilder( - animation: _scroller, - builder: (_, __) { - bool showBackdrop = widget.blurOnScroll; - double backdropAmt = 0; - if (_scroller.hasClients && showBackdrop) { - double blurStart = 50; - double maxScroll = 150; - double scrollPx = _scroller.position.pixels - blurStart; - // Normalize scroll position to a value between 0 and 1 - backdropAmt = (_scroller.position.pixels - blurStart).clamp(0, maxScroll) / maxScroll; - // Disable backdrop once it is offscreen for an easy perf win - showBackdrop = (scrollPx <= 500); - } - // Container provides a underlay which gets darker as the background blurs - return Stack( - children: [ - if (showBackdrop) ...[ - AppBackdrop( - strength: backdropAmt, - child: IgnorePointer( - child: Container( - color: $styles.colors.black.withOpacity(backdropAmt * .6), - ), - )), - ], - _buildScrollingList() - ], - ); - }, - ), - ], - ); - }, - ); + return widget.blurOnScroll ? _buildScrollingListWithBlur() : _buildScrollingList(); } + /// The actual content of the scrolling list Widget _buildScrollingList() { Container buildHandle() { return Container( @@ -136,4 +99,40 @@ class _EventsListState extends State<_EventsList> { ], ); } + + /// Wraps the list in a scroll listener + Widget _buildScrollingListWithBlur() { + return ListenableBuilder( + listenable: _scroller, + child: _buildScrollingList(), + builder: (_, child) { + bool showBackdrop = true; + double backdropAmt = 0; + if (_scroller.hasClients && showBackdrop) { + double blurStart = 50; + double maxScroll = 150; + double scrollPx = _scroller.position.pixels - blurStart; + // Normalize scroll position to a value between 0 and 1 + backdropAmt = (_scroller.position.pixels - blurStart).clamp(0, maxScroll) / maxScroll; + // Disable backdrop once it is offscreen for an easy perf win + showBackdrop = (scrollPx <= 500); + } + // Container provides a underlay which gets darker as the background blurs + return Stack( + children: [ + if (showBackdrop) ...[ + AppBackdrop( + strength: backdropAmt, + child: IgnorePointer( + child: Container( + color: $styles.colors.black.withOpacity(backdropAmt * .6), + ), + )), + ], + child!, + ], + ); + }, + ); + } } diff --git a/lib/ui/screens/wonder_events/wonder_events.dart b/lib/ui/screens/wonder_events/wonder_events.dart index f09c636d..ed28f225 100644 --- a/lib/ui/screens/wonder_events/wonder_events.dart +++ b/lib/ui/screens/wonder_events/wonder_events.dart @@ -6,7 +6,6 @@ import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/curved_clippers.dart'; import 'package:wonders/ui/common/hidden_collectible.dart'; import 'package:wonders/ui/common/list_gradient.dart'; -import 'package:wonders/ui/common/pop_router_on_over_scroll.dart'; import 'package:wonders/ui/common/themed_text.dart'; import 'package:wonders/ui/common/timeline_event_card.dart'; import 'package:wonders/ui/common/wonders_timeline_builder.dart'; diff --git a/lib/ui/wonder_illustrations/common/animated_clouds.dart b/lib/ui/wonder_illustrations/common/animated_clouds.dart index a39eeb78..121bcbee 100644 --- a/lib/ui/wonder_illustrations/common/animated_clouds.dart +++ b/lib/ui/wonder_illustrations/common/animated_clouds.dart @@ -89,8 +89,8 @@ class _AnimatedCloudsState extends State with SingleTickerProvid return RepaintBoundary( child: ClipRect( child: OverflowBox( - child: AnimatedBuilder( - animation: _anim, + child: ListenableBuilder( + listenable: _anim, builder: (_, __) { // A stack with 2 sets of clouds, one set is moving out of view while the other moves in. return Stack( diff --git a/lib/ui/wonder_illustrations/common/paint_textures.dart b/lib/ui/wonder_illustrations/common/paint_textures.dart index 196f027a..ef382fdd 100644 --- a/lib/ui/wonder_illustrations/common/paint_textures.dart +++ b/lib/ui/wonder_illustrations/common/paint_textures.dart @@ -12,8 +12,8 @@ class IllustrationTexture extends StatelessWidget { final Animation? opacity; @override - Widget build(BuildContext context) => AnimatedBuilder( - animation: opacity ?? AlwaysStoppedAnimation(1), + Widget build(BuildContext context) => ListenableBuilder( + listenable: opacity ?? AlwaysStoppedAnimation(1), builder: (context, child) => ClipRect( child: Transform.scale( scaleX: scale * (flipX ? -1 : 1),