From 667108943f53487eb10b395db9ee2d512195ae87 Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 14 Nov 2022 15:36:34 -0700 Subject: [PATCH] Add centeredBox widget, polish editorial --- lib/ui/common/centered_box.dart | 20 ++++ lib/ui/common/listenable_builder.dart | 9 +- .../collectible_found_screen.dart | 49 ++++---- .../screens/collection/collection_screen.dart | 1 + .../widgets/_collection_footer.dart | 22 ++-- .../screens/editorial/editorial_screen.dart | 1 + .../widgets/_collapsing_pull_quote_image.dart | 3 +- .../widgets/_sliding_image_stack.dart | 94 ++++++--------- lib/ui/screens/timeline/timeline_screen.dart | 18 +-- .../timeline/widgets/_bottom_scrubber.dart | 110 +++++++++--------- .../timeline/widgets/_scrolling_viewport.dart | 60 +++++----- .../screens/wonder_events/wonder_events.dart | 91 +++++++-------- pubspec.yaml | 2 +- 13 files changed, 240 insertions(+), 240 deletions(-) create mode 100644 lib/ui/common/centered_box.dart diff --git a/lib/ui/common/centered_box.dart b/lib/ui/common/centered_box.dart new file mode 100644 index 00000000..23af7b3a --- /dev/null +++ b/lib/ui/common/centered_box.dart @@ -0,0 +1,20 @@ +import 'package:wonders/common_libs.dart'; + +class CenteredBox extends StatelessWidget { + const CenteredBox({Key? key, required this.child, this.width, this.height, this.padding}) : super(key: key); + final Widget child; + final double? width; + final double? height; + final EdgeInsets? padding; + @override + Widget build(BuildContext context) => Padding( + padding: padding ?? EdgeInsets.zero, + child: Center( + child: SizedBox( + width: width, + height: height, + child: child, + ), + ), + ); +} diff --git a/lib/ui/common/listenable_builder.dart b/lib/ui/common/listenable_builder.dart index 7400681b..060ae972 100644 --- a/lib/ui/common/listenable_builder.dart +++ b/lib/ui/common/listenable_builder.dart @@ -1,10 +1,11 @@ import 'package:flutter/cupertino.dart'; +/// Replacement for the built in [AnimatedBuilder] because that name is semantically confusing. class ListenableBuilder extends AnimatedBuilder { const ListenableBuilder({ - Key? key, + super.key, required Listenable listenable, - required TransitionBuilder builder, - Widget? child, - }) : super(key: key, animation: listenable, builder: builder, child: child); + required super.builder, + super.child, + }) : super(animation: listenable); } diff --git a/lib/ui/screens/collectible_found/collectible_found_screen.dart b/lib/ui/screens/collectible_found/collectible_found_screen.dart index 3bce069b..fe4524de 100644 --- a/lib/ui/screens/collectible_found/collectible_found_screen.dart +++ b/lib/ui/screens/collectible_found/collectible_found_screen.dart @@ -1,6 +1,7 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/data/collectible_data.dart'; import 'package:particle_field/particle_field.dart'; +import 'package:wonders/ui/common/centered_box.dart'; part 'widgets/_animated_ribbon.dart'; part 'widgets/_celebration_particles.dart'; @@ -51,31 +52,29 @@ class CollectibleFoundScreen extends StatelessWidget { Animate().custom(duration: t, builder: (context, ratio, _) => _buildGradient(context, 1, ratio)), _CelebrationParticles(fadeMs: (t * 6).inMilliseconds), SafeArea( - child: Center( - child: SizedBox( - width: $styles.sizes.maxContentWidth3, - child: Column( - children: [ - Gap($styles.insets.lg), - Spacer(), - SizedBox( - height: context.heightPx * .35, - child: Center(child: Hero(tag: 'collectible_image_${collectible.id}', child: _buildImage(context))), - ), - Gap($styles.insets.lg), - _buildRibbon(context), - Gap($styles.insets.sm), - _buildTitle(context, collectible.title, $styles.text.h2, $styles.colors.offWhite, t * 1.5), - Gap($styles.insets.xs), - _buildTitle( - context, collectible.subtitle.toUpperCase(), $styles.text.title2, $styles.colors.accent1, t * 2), - Spacer(), - Gap($styles.insets.lg), - _buildCollectionButton(context), - Gap($styles.insets.lg), - Spacer(), - ], - ), + child: CenteredBox( + width: $styles.sizes.maxContentWidth3, + child: Column( + children: [ + Gap($styles.insets.lg), + Spacer(), + SizedBox( + height: context.heightPx * .35, + child: Center(child: Hero(tag: 'collectible_image_${collectible.id}', child: _buildImage(context))), + ), + Gap($styles.insets.lg), + _buildRibbon(context), + Gap($styles.insets.sm), + _buildTitle(context, collectible.title, $styles.text.h2, $styles.colors.offWhite, t * 1.5), + Gap($styles.insets.xs), + _buildTitle( + context, collectible.subtitle.toUpperCase(), $styles.text.title2, $styles.colors.accent1, t * 2), + Spacer(), + Gap($styles.insets.lg), + _buildCollectionButton(context), + Gap($styles.insets.lg), + Spacer(), + ], ), ), ), diff --git a/lib/ui/screens/collection/collection_screen.dart b/lib/ui/screens/collection/collection_screen.dart index 69574fae..14067f94 100644 --- a/lib/ui/screens/collection/collection_screen.dart +++ b/lib/ui/screens/collection/collection_screen.dart @@ -5,6 +5,7 @@ import 'package:wonders/logic/collectibles_logic.dart'; import 'package:wonders/logic/common/string_utils.dart'; import 'package:wonders/logic/data/collectible_data.dart'; import 'package:wonders/logic/data/wonder_data.dart'; +import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/controls/simple_header.dart'; import 'package:wonders/ui/common/modals/app_modals.dart'; diff --git a/lib/ui/screens/collection/widgets/_collection_footer.dart b/lib/ui/screens/collection/widgets/_collection_footer.dart index 0f28e27e..bca072cd 100644 --- a/lib/ui/screens/collection/widgets/_collection_footer.dart +++ b/lib/ui/screens/collection/widgets/_collection_footer.dart @@ -24,18 +24,16 @@ class _CollectionFooter extends StatelessWidget { color: $styles.colors.greyStrong, child: SafeArea( top: false, - child: Center( - child: SizedBox( - width: $styles.sizes.maxContentWidth1, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildProgressRow(context), - Gap($styles.insets.sm), - _buildProgressBar(context), - Gap($styles.insets.sm), - ], - ), + child: CenteredBox( + width: $styles.sizes.maxContentWidth1, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildProgressRow(context), + Gap($styles.insets.sm), + _buildProgressBar(context), + Gap($styles.insets.sm), + ], ), ), ), diff --git a/lib/ui/screens/editorial/editorial_screen.dart b/lib/ui/screens/editorial/editorial_screen.dart index 364607df..6a9e8820 100644 --- a/lib/ui/screens/editorial/editorial_screen.dart +++ b/lib/ui/screens/editorial/editorial_screen.dart @@ -9,6 +9,7 @@ import 'package:wonders/logic/common/string_utils.dart'; import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/ui/common/app_icons.dart'; import 'package:wonders/ui/common/blend_mask.dart'; +import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/compass_divider.dart'; import 'package:wonders/ui/common/curved_clippers.dart'; import 'package:wonders/ui/common/google_maps_marker.dart'; diff --git a/lib/ui/screens/editorial/widgets/_collapsing_pull_quote_image.dart b/lib/ui/screens/editorial/widgets/_collapsing_pull_quote_image.dart index 706db5e5..43b60c95 100644 --- a/lib/ui/screens/editorial/widgets/_collapsing_pull_quote_image.dart +++ b/lib/ui/screens/editorial/widgets/_collapsing_pull_quote_image.dart @@ -42,8 +42,9 @@ class _CollapsingPullQuoteImage extends StatelessWidget { // The sized boxes in the column collapse to a zero height, allowing the quotes to naturally sit over top of the image return MergeSemantics( - child: Padding( + child: CenteredBox( padding: EdgeInsets.symmetric(vertical: outerPadding), + width: 450, child: Stack( children: [ Container( diff --git a/lib/ui/screens/editorial/widgets/_sliding_image_stack.dart b/lib/ui/screens/editorial/widgets/_sliding_image_stack.dart index a40f127a..4edb35b6 100644 --- a/lib/ui/screens/editorial/widgets/_sliding_image_stack.dart +++ b/lib/ui/screens/editorial/widgets/_sliding_image_stack.dart @@ -8,7 +8,7 @@ class _SlidingImageStack extends StatelessWidget { @override Widget build(BuildContext context) { - final totalSize = Size(280, 400); + final totalSize = Size(400, 600); Container buildPhoto(double scale, String url, Alignment align, {bool top = true}) { return Container( width: totalSize.width * scale, @@ -24,68 +24,46 @@ class _SlidingImageStack extends StatelessWidget { } return ExcludeSemantics( - child: Padding( + child: CenteredBox( + width: totalSize.width, + height: totalSize.height, padding: const EdgeInsets.all(8.0), - child: SizedBox( - width: totalSize.width, - height: totalSize.height, - child: ValueListenableBuilder( - valueListenable: scrollPos, - builder: (context, value, child) { - double pctVisible = 0; - final yPos = ContextUtils.getGlobalPos(context)?.dy; - final height = ContextUtils.getSize(context)?.height; - if (yPos != null && height != null) { - final amtVisible = context.heightPx - yPos; - pctVisible = (amtVisible / height).clamp(0, 3); - } - return Stack( - children: [ - Center( - child: FractionalTranslation( - translation: Offset(0, 0.05 * pctVisible), - child: Transform( - alignment: Alignment.center, //origin: Offset(100, 100) - transform: Matrix4.rotationZ(0.9), - child: Container( - width: context.widthPx / 1.75, - height: context.widthPx, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.elliptical(200, 300)), - border: Border.all( - color: $styles.colors.accent2, - width: 1, - ), - ), - ), - ), + child: ValueListenableBuilder( + valueListenable: scrollPos, + builder: (context, value, child) { + double pctVisible = 0; + final yPos = ContextUtils.getGlobalPos(context)?.dy; + final height = ContextUtils.getSize(context)?.height; + if (yPos != null && height != null) { + final amtVisible = context.heightPx - yPos; + pctVisible = (amtVisible / height).clamp(0, 3); + } + return Stack( + children: [ + TopRight( + child: FractionalTranslation( + translation: Offset(0, -.1 + .2 * pctVisible), + child: buildPhoto( + .73, + type.photo3, + Alignment(0, -.3 + .6 * pctVisible), ), ), - TopRight( - child: FractionalTranslation( - translation: Offset(0, -.1 + .2 * pctVisible), - child: buildPhoto( - .73, - type.photo3, - Alignment(0, -.3 + .6 * pctVisible), - ), + ), + BottomLeft( + child: FractionalTranslation( + translation: Offset(0, -.14 * pctVisible), + child: buildPhoto( + .45, + type.photo4, + Alignment(0, .3 - .6 * pctVisible), + top: false, ), ), - BottomLeft( - child: FractionalTranslation( - translation: Offset(0, -.14 * pctVisible), - child: buildPhoto( - .45, - type.photo4, - Alignment(0, .3 - .6 * pctVisible), - top: false, - ), - ), - ), - ], - ); - }, - ), + ), + ], + ); + }, ), ), ); diff --git a/lib/ui/screens/timeline/timeline_screen.dart b/lib/ui/screens/timeline/timeline_screen.dart index b1379fef..7e20656f 100644 --- a/lib/ui/screens/timeline/timeline_screen.dart +++ b/lib/ui/screens/timeline/timeline_screen.dart @@ -8,6 +8,7 @@ import 'package:wonders/logic/common/string_utils.dart'; import 'package:wonders/logic/data/timeline_data.dart'; import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/ui/common/blend_mask.dart'; +import 'package:wonders/ui/common/centered_box.dart'; import 'package:wonders/ui/common/controls/simple_header.dart'; import 'package:wonders/ui/common/dashed_line.dart'; import 'package:wonders/ui/common/list_gradient.dart'; @@ -76,13 +77,16 @@ class _TimelineScreenState extends State { /// Mini Horizontal timeline, reacts to the state of the larger scrolling timeline, /// and changes the timelines scroll position on Hz drag - Padding( - padding: EdgeInsets.symmetric(horizontal: $styles.insets.lg), - child: _BottomScrubber( - _scroller, - size: scrubberSize, - timelineMinSize: minSize, - selectedWonder: widget.type, + CenteredBox( + width: $styles.sizes.maxContentWidth1, + child: Padding( + padding: EdgeInsets.symmetric(horizontal: $styles.insets.lg), + child: _BottomScrubber( + _scroller, + size: scrubberSize, + timelineMinSize: minSize, + selectedWonder: widget.type, + ), ), ), Gap($styles.insets.lg), diff --git a/lib/ui/screens/timeline/widgets/_bottom_scrubber.dart b/lib/ui/screens/timeline/widgets/_bottom_scrubber.dart index b75d6829..a66cf08a 100644 --- a/lib/ui/screens/timeline/widgets/_bottom_scrubber.dart +++ b/lib/ui/screens/timeline/widgets/_bottom_scrubber.dart @@ -29,67 +29,71 @@ class _BottomScrubber extends StatelessWidget { /// It might take a frame until we receive a valid scroller if (scroller == null) return SizedBox.shrink(); - void handleScrubberPan(DragUpdateDetails details) { - if (!scroller.hasClients) return; - double dragMultiplier = (scroller.position.maxScrollExtent + timelineMinSize) / context.widthPx; - double newPos = scroller.position.pixels + details.delta.dx * dragMultiplier; - scroller.position.jumpTo(newPos.clamp(0, scroller.position.maxScrollExtent)); - } - return SizedBox( - height: size, - child: Stack( - children: [ - /// Timeline background - Container( - padding: EdgeInsets.all($styles.insets.md), - decoration: BoxDecoration( - color: $styles.colors.greyStrong, - borderRadius: BorderRadius.circular($styles.corners.md), + return LayoutBuilder(builder: (context, constraints) { + void handleScrubberPan(DragUpdateDetails details) { + final totalWidth = constraints.maxWidth; + if (!scroller.hasClients) return; + double dragMultiplier = (scroller.position.maxScrollExtent + timelineMinSize) / totalWidth; + double newPos = scroller.position.pixels + details.delta.dx * dragMultiplier; + scroller.position.jumpTo(newPos.clamp(0, scroller.position.maxScrollExtent)); + } + + return SizedBox( + height: size, + child: Stack( + children: [ + /// Timeline background + Container( + padding: EdgeInsets.all($styles.insets.md), + decoration: BoxDecoration( + color: $styles.colors.greyStrong, + borderRadius: BorderRadius.circular($styles.corners.md), + ), + child: WondersTimelineBuilder( + crossAxisGap: 4, + selectedWonders: selectedWonder != null ? [selectedWonder!] : [], + ), ), - child: WondersTimelineBuilder( - crossAxisGap: 4, - selectedWonders: selectedWonder != null ? [selectedWonder!] : [], - ), - ), - /// Visible area, follows the position of scroller - ListenableBuilder( - listenable: scroller, - builder: (_, __) { - ScrollPosition? pos; - if (scroller.hasClients) pos = scroller.position; - // Get current scroll offset and move the viewport to match - double scrollFraction = _calculateScrollFraction(pos); - double viewPortFraction = _calculateViewPortFraction(pos); - final scrubberAlign = Alignment(-1 + scrollFraction * 2, 0); + /// Visible area, follows the position of scroller + ListenableBuilder( + listenable: scroller, + builder: (_, __) { + ScrollPosition? pos; + if (scroller.hasClients) pos = scroller.position; + // Get current scroll offset and move the viewport to match + double scrollFraction = _calculateScrollFraction(pos); + double viewPortFraction = _calculateViewPortFraction(pos); + final scrubberAlign = Alignment(-1 + scrollFraction * 2, 0); - return Positioned.fill( - child: Semantics( - container: true, - slider: true, - label: $strings.bottomScrubberSemanticTimeline, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onPanUpdate: handleScrubberPan, + return Positioned.fill( + child: Semantics( + container: true, + slider: true, + label: $strings.bottomScrubberSemanticTimeline, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onPanUpdate: handleScrubberPan, - /// Scrub area - child: Align( - alignment: scrubberAlign, - child: FractionallySizedBox( - widthFactor: viewPortFraction, - heightFactor: 1, - child: _buildOutlineBox(context, scrubberAlign), + /// Scrub area + child: Align( + alignment: scrubberAlign, + child: FractionallySizedBox( + widthFactor: viewPortFraction, + heightFactor: 1, + child: _buildOutlineBox(context, scrubberAlign), + ), ), ), ), - ), - ); - }, - ) - ], - ), - ); + ); + }, + ) + ], + ), + ); + }); } Container _buildOutlineBox(BuildContext context, Alignment alignment) { diff --git a/lib/ui/screens/timeline/widgets/_scrolling_viewport.dart b/lib/ui/screens/timeline/widgets/_scrolling_viewport.dart index f2a7ab97..8a5a9c36 100644 --- a/lib/ui/screens/timeline/widgets/_scrolling_viewport.dart +++ b/lib/ui/screens/timeline/widgets/_scrolling_viewport.dart @@ -95,48 +95,46 @@ class _ScalingViewportState extends State<_ScrollingViewport> { // cache constraints, so they can be used to maintain the selected year while zooming controller._constraints = constraints; double vtPadding = constraints.maxHeight / 2; - double size = controller.calculateContentHeight(); - final contentSize = min($styles.sizes.maxContentWidth2, constraints.maxWidth); + double height = controller.calculateContentHeight(); + final width = min($styles.sizes.maxContentWidth2, constraints.maxWidth); return Stack( children: [ SingleChildScrollView( controller: controller.scroller, padding: EdgeInsets.symmetric(vertical: vtPadding), // A stack inside a SizedBox which sets its overall height - child: Center( - child: SizedBox( - height: size, - width: contentSize, - child: Stack( - children: [ - /// Year Markers - _YearMarkers(), + child: CenteredBox( + height: height, + width: width, + child: Stack( + children: [ + /// Year Markers + _YearMarkers(), - /// individual timeline sections - Positioned.fill( - left: 100, - right: $styles.insets.sm, - child: FocusTraversalGroup( - //child: Placeholder(), - child: WondersTimelineBuilder( - axis: Axis.vertical, - crossAxisGap: max(6, (contentSize - (120 * 3)) / 2), - minSize: _minTimelineSize, - timelineBuilder: (_, data, __) => buildTimelineSection(data), - ), + /// individual timeline sections + Positioned.fill( + left: 100, + right: $styles.insets.sm, + child: FocusTraversalGroup( + //child: Placeholder(), + child: WondersTimelineBuilder( + axis: Axis.vertical, + crossAxisGap: max(6, (width - (120 * 3)) / 2), + minSize: _minTimelineSize, + timelineBuilder: (_, data, __) => buildTimelineSection(data), ), ), + ), - /// Event Markers, rebuilds on scroll - ListenableBuilder( - listenable: controller.scroller, - builder: (_, __) => _EventMarkers( - controller.calculateYearFromScrollPos(), - onEventChanged: _handleEventMarkerChanged, - ), + /// Event Markers, rebuilds on scroll + ListenableBuilder( + listenable: controller.scroller, + builder: (_, __) => _EventMarkers( + controller.calculateYearFromScrollPos(), + onEventChanged: _handleEventMarkerChanged, ), - ], - ), + ), + ], ), ), ), diff --git a/lib/ui/screens/wonder_events/wonder_events.dart b/lib/ui/screens/wonder_events/wonder_events.dart index ed28f225..4e6f7603 100644 --- a/lib/ui/screens/wonder_events/wonder_events.dart +++ b/lib/ui/screens/wonder_events/wonder_events.dart @@ -3,6 +3,7 @@ import 'package:wonders/logic/common/string_utils.dart'; import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/ui/common/app_backdrop.dart'; import 'package:wonders/ui/common/app_icons.dart'; +import 'package:wonders/ui/common/centered_box.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'; @@ -58,34 +59,30 @@ class WonderEvents extends StatelessWidget { children: [ /// WonderImage w/ Timeline btn Expanded( - child: Center( - child: SizedBox( - width: $styles.sizes.maxContentWidth3, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Gap($styles.insets.lg), - Expanded(child: Center(child: _WonderImageWithTimeline(data: _data, height: 500))), - Gap($styles.insets.lg), - SizedBox(width: 300, child: _TimelineBtn(type: type)), - Gap($styles.insets.xl), - ], - ), + child: CenteredBox( + width: $styles.sizes.maxContentWidth3, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Gap($styles.insets.lg), + Expanded(child: Center(child: _WonderImageWithTimeline(data: _data, height: 500))), + Gap($styles.insets.lg), + SizedBox(width: 300, child: _TimelineBtn(type: type)), + Gap($styles.insets.xl), + ], ), ), ), /// EventsList Expanded( - child: Center( - child: SizedBox( - width: $styles.sizes.maxContentWidth1, - child: _EventsList( - data: _data, - topHeight: 100, - blurOnScroll: false, - ), + child: CenteredBox( + width: $styles.sizes.maxContentWidth1, + child: _EventsList( + data: _data, + topHeight: 100, + blurOnScroll: false, ), ), ), @@ -97,35 +94,33 @@ class WonderEvents extends StatelessWidget { Widget _buildPortrait() { return LayoutBuilder(builder: (_, constraints) { double topHeight = max(constraints.maxHeight * .55, 200); - return Center( - child: SizedBox( - width: $styles.sizes.maxContentWidth3, - child: Stack( - children: [ - /// Top content, sits underneath scrolling list - _WonderImageWithTimeline(height: topHeight, data: _data), + return CenteredBox( + width: $styles.sizes.maxContentWidth3, + child: Stack( + children: [ + /// Top content, sits underneath scrolling list + _WonderImageWithTimeline(height: topHeight, data: _data), - /// EventsList + TimelineBtn - Column( - children: [ - Expanded( - /// List - child: _EventsList( - data: _data, - topHeight: topHeight, - blurOnScroll: true, - showTopGradient: false, - ), + /// EventsList + TimelineBtn + Column( + children: [ + Expanded( + /// List + child: _EventsList( + data: _data, + topHeight: topHeight, + blurOnScroll: true, + showTopGradient: false, ), - Gap($styles.insets.lg), + ), + Gap($styles.insets.lg), - /// Btn - _TimelineBtn(type: _data.type, width: $styles.sizes.maxContentWidth3), - Gap($styles.insets.xl), - ], - ), - ], - ), + /// Btn + _TimelineBtn(type: _data.type, width: $styles.sizes.maxContentWidth3), + Gap($styles.insets.xl), + ], + ), + ], ), ); }); diff --git a/pubspec.yaml b/pubspec.yaml index 2c77449d..ab8c80b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: "none" version: 1.9.7 environment: - sdk: ">=2.16.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: flutter: