From 34a0ae7dbf7a2e8b6f65cb085eeeed3ce45166f9 Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 14 Nov 2022 11:59:45 -0700 Subject: [PATCH] Update events view, add responsiveness, switch to dark mode --- lib/styles/styles.dart | 2 +- lib/ui/common/timeline_event_card.dart | 52 ++++++----- .../wonder_events/widgets/_events_list.dart | 92 +++++++++++-------- .../wonder_events/widgets/_timeline_btn.dart | 20 ++++ ....dart => _wonder_image_with_timeline.dart} | 13 +-- .../screens/wonder_events/wonder_events.dart | 91 +++++++++++++++--- pubspec.lock | 2 +- 7 files changed, 195 insertions(+), 77 deletions(-) create mode 100644 lib/ui/screens/wonder_events/widgets/_timeline_btn.dart rename lib/ui/screens/wonder_events/widgets/{_top_content.dart => _wonder_image_with_timeline.dart} (90%) diff --git a/lib/styles/styles.dart b/lib/styles/styles.dart index 9b71ac24..9d6af33c 100644 --- a/lib/styles/styles.dart +++ b/lib/styles/styles.dart @@ -158,7 +158,7 @@ class _Corners { class _Sizes { double get maxContentWidth1 => 800; double get maxContentWidth2 => 600; - double get maxContentWidth3 => 400; + double get maxContentWidth3 => 500; final Size minAppSize = Size(450, 600); } diff --git a/lib/ui/common/timeline_event_card.dart b/lib/ui/common/timeline_event_card.dart index 46c43dd7..84ecb3bb 100644 --- a/lib/ui/common/timeline_event_card.dart +++ b/lib/ui/common/timeline_event_card.dart @@ -1,37 +1,47 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/common/string_utils.dart'; +import 'package:wonders/ui/common/themed_text.dart'; class TimelineEventCard extends StatelessWidget { - const TimelineEventCard({Key? key, required this.year, required this.text}) : super(key: key); + const TimelineEventCard({Key? key, required this.year, required this.text, this.darkMode = false}) : super(key: key); final int year; final String text; + final bool darkMode; @override Widget build(BuildContext context) { return MergeSemantics( child: Padding( padding: EdgeInsets.only(bottom: $styles.insets.sm), - child: Container( - color: $styles.colors.offWhite, - padding: EdgeInsets.all($styles.insets.sm), - child: Row( - children: [ - SizedBox( - width: 75, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('${year.abs()}', style: $styles.text.h3.copyWith(fontWeight: FontWeight.w400, height: 1)), - Text(StringUtils.getYrSuffix(year), style: $styles.text.bodySmall), - ], + child: DefaultTextColor( + color: darkMode ? Colors.white : Colors.black, + child: Container( + color: darkMode ? $styles.colors.greyStrong : $styles.colors.offWhite, + padding: EdgeInsets.all($styles.insets.sm), + child: Row( + children: [ + /// Date + SizedBox( + width: 75, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('${year.abs()}', style: $styles.text.h3.copyWith(fontWeight: FontWeight.w400, height: 1)), + Text(StringUtils.getYrSuffix(year), style: $styles.text.bodySmall), + ], + ), ), - ), - Center(child: Container(width: 1, height: 50, color: $styles.colors.black)), - Gap($styles.insets.sm), - Expanded( - child: Text(text, style: $styles.text.bodySmall), - ), - ], + + /// Divider + Center(child: Container(width: 1, height: 50, color: darkMode ? Colors.white : $styles.colors.black)), + Gap($styles.insets.sm), + + /// Text content + Expanded( + child: Text(text, style: $styles.text.bodySmall), + ), + ], + ), ), ), ), diff --git a/lib/ui/screens/wonder_events/widgets/_events_list.dart b/lib/ui/screens/wonder_events/widgets/_events_list.dart index f8e2b314..5189acd6 100644 --- a/lib/ui/screens/wonder_events/widgets/_events_list.dart +++ b/lib/ui/screens/wonder_events/widgets/_events_list.dart @@ -1,8 +1,19 @@ part of '../wonder_events.dart'; class _EventsList extends StatefulWidget { - const _EventsList({Key? key, required this.data}) : super(key: key); + const _EventsList( + {Key? key, + required this.data, + this.topHeight = 0, + this.blurOnScroll = false, + this.showTopGradient = true, + this.showBottomGradient = true}) + : super(key: key); final WonderData data; + final double topHeight; + final bool blurOnScroll; + final bool showTopGradient; + final bool showBottomGradient; @override State<_EventsList> createState() => _EventsListState(); @@ -16,8 +27,6 @@ class _EventsListState extends State<_EventsList> { super.dispose(); } - void _handleGlobalTimelinePressed() => context.push(ScreenPaths.timeline(widget.data.type)); - @override Widget build(BuildContext context) { return PopRouterOnOverScroll( @@ -28,9 +37,9 @@ class _EventsListState extends State<_EventsList> { AnimatedBuilder( animation: _scroller, builder: (_, __) { - bool showBackdrop = true; + bool showBackdrop = widget.blurOnScroll; double backdropAmt = 0; - if (_scroller.hasClients) { + if (_scroller.hasClients && showBackdrop) { double blurStart = 50; double maxScroll = 150; double scrollPx = _scroller.position.pixels - blurStart; @@ -77,46 +86,55 @@ class _EventsListState extends State<_EventsList> { for (var e in events.entries) { final delay = 100.ms + (100 * listItems.length).ms; listItems.add( - TimelineEventCard(year: e.key, text: e.value) + TimelineEventCard(year: e.key, text: e.value, darkMode: true) .animate() .fade(delay: delay, duration: $styles.times.med * 1.5) .slide(begin: Offset(0, 1), curve: Curves.easeOutBack), ); } - return SingleChildScrollView( - controller: _scroller, - child: Column( - children: [ - IgnorePointer(child: Gap(WonderEvents._topHeight)), - Container( - decoration: BoxDecoration( - color: $styles.colors.white, - borderRadius: BorderRadius.circular($styles.corners.md), - ), - padding: EdgeInsets.symmetric(horizontal: $styles.insets.md), - child: Column( - children: [ - Gap($styles.insets.xs), - buildHandle(), - Gap($styles.insets.sm), - ...listItems, - Gap($styles.insets.lg), - AppBtn.from( - text: $strings.eventsListButtonOpenGlobal, - expand: true, - onPressed: _handleGlobalTimelinePressed, - semanticLabel: $strings.eventsListButtonOpenGlobal, + return Stack( + children: [ + SingleChildScrollView( + controller: _scroller, + child: Column( + children: [ + IgnorePointer(child: Gap(widget.topHeight)), + Container( + decoration: BoxDecoration( + color: $styles.colors.black, + borderRadius: BorderRadius.circular($styles.corners.md), ), - Gap($styles.insets.xl), - CompassDivider(isExpanded: true), - Gap($styles.insets.md), - HiddenCollectible(widget.data.type, index: 2, size: 150), - Gap(150), - ], + padding: EdgeInsets.symmetric(horizontal: $styles.insets.md), + child: Column( + children: [ + Gap($styles.insets.xs), + buildHandle(), + Gap($styles.insets.sm), + ...listItems, + Gap($styles.insets.xl), + HiddenCollectible(widget.data.type, index: 2, size: 150), + Gap(150), + ], + ), + ), + ], + ), + ), + + /// Vertical gradient on btm + if (widget.showBottomGradient) + Positioned.fill( + child: BottomCenter( + child: ListOverscollGradient(bottomUp: true, size: 100), ), ), - ], - ), + if (widget.showTopGradient) + Positioned.fill( + child: TopCenter( + child: ListOverscollGradient(size: 100), + ), + ), + ], ); } } diff --git a/lib/ui/screens/wonder_events/widgets/_timeline_btn.dart b/lib/ui/screens/wonder_events/widgets/_timeline_btn.dart new file mode 100644 index 00000000..68344b92 --- /dev/null +++ b/lib/ui/screens/wonder_events/widgets/_timeline_btn.dart @@ -0,0 +1,20 @@ +part of '../wonder_events.dart'; + +class _TimelineBtn extends StatelessWidget { + const _TimelineBtn({Key? key, required this.type}) : super(key: key); + final WonderType type; + + @override + Widget build(BuildContext context) { + void handleBtnPressed() => context.push(ScreenPaths.timeline(type)); + return Padding( + padding: EdgeInsets.symmetric(horizontal: $styles.insets.md), + child: AppBtn.from( + text: $strings.eventsListButtonOpenGlobal, + expand: true, + onPressed: handleBtnPressed, + semanticLabel: $strings.eventsListButtonOpenGlobal, + ), + ); + } +} diff --git a/lib/ui/screens/wonder_events/widgets/_top_content.dart b/lib/ui/screens/wonder_events/widgets/_wonder_image_with_timeline.dart similarity index 90% rename from lib/ui/screens/wonder_events/widgets/_top_content.dart rename to lib/ui/screens/wonder_events/widgets/_wonder_image_with_timeline.dart index e656106b..b6689e74 100644 --- a/lib/ui/screens/wonder_events/widgets/_top_content.dart +++ b/lib/ui/screens/wonder_events/widgets/_wonder_image_with_timeline.dart @@ -1,11 +1,12 @@ part of '../wonder_events.dart'; -class _TopContent extends StatelessWidget { - const _TopContent({Key? key, required this.data}) : super(key: key); +class _WonderImageWithTimeline extends StatelessWidget { + const _WonderImageWithTimeline({Key? key, required this.data, required this.height}) : super(key: key); final WonderData data; + final double height; - Color _fixLuminence(Color color, [double luminence = 0.35]) { - double d = luminence - color.computeLuminance(); + Color _fixLuminance(Color color, [double luminance = 0.35]) { + double d = luminance - color.computeLuminance(); if (d <= 0) return color; int r = color.red, g = color.green, b = color.blue; return Color.fromARGB(255, (r + (255 - r) * d).toInt(), (g + (255 - g) * d).toInt(), (b + (255 - b) * d).toInt()); @@ -14,7 +15,7 @@ class _TopContent extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( - height: WonderEvents._topHeight, + height: height, child: MergeSemantics( child: LightText( child: SeparatedColumn( @@ -45,7 +46,7 @@ class _TopContent extends StatelessWidget { timelineBuilder: (_, data, isSelected) { return Container( decoration: BoxDecoration( - color: isSelected ? _fixLuminence(data.type.fgColor) : Colors.transparent, + color: isSelected ? _fixLuminance(data.type.fgColor) : Colors.transparent, border: Border.all(color: $styles.colors.greyMedium), borderRadius: BorderRadius.circular($styles.corners.md), ), diff --git a/lib/ui/screens/wonder_events/wonder_events.dart b/lib/ui/screens/wonder_events/wonder_events.dart index 83e09776..b72474d4 100644 --- a/lib/ui/screens/wonder_events/wonder_events.dart +++ b/lib/ui/screens/wonder_events/wonder_events.dart @@ -2,7 +2,7 @@ import 'package:wonders/common_libs.dart'; 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/compass_divider.dart'; +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'; @@ -13,36 +13,105 @@ import 'package:wonders/ui/common/wonders_timeline_builder.dart'; import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart'; part 'widgets/_events_list.dart'; -part 'widgets/_top_content.dart'; +part 'widgets/_timeline_btn.dart'; +part 'widgets/_wonder_image_with_timeline.dart'; class WonderEvents extends StatelessWidget { - static const double _topHeight = 450; WonderEvents({Key? key, required this.type}) : super(key: key); final WonderType type; late final _data = wondersLogic.getData(type); @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (_, constraints) { + void handleTimelineBtnPressed() => context.push(ScreenPaths.timeline(type)); + return LayoutBuilder(builder: (context, constraints) { return Container( color: $styles.colors.black, child: SafeArea( bottom: false, + child: Stack( + children: [ + Positioned.fill( + top: $styles.insets.lg, + child: context.isLandscape ? _buildLandscape() : _buildPortrait(), + ), + Positioned( + right: $styles.insets.lg, + top: $styles.insets.lg, + child: CircleIconBtn( + icon: AppIcons.timeline, + onPressed: handleTimelineBtnPressed, + semanticLabel: $strings.eventsListButtonOpenGlobal)), + ], + ), + ), + ); + }); + } + + /// Landscape layout is a row, with the WonderImage on left and events on the right + Widget _buildLandscape() { + return Row( + children: [ + /// WonderImage w/ Timeline btn sits on the left + Expanded( child: Center( child: SizedBox( - width: $styles.sizes.maxContentWidth1, - child: Stack( + width: $styles.sizes.maxContentWidth3, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - /// Top content, sits underneath scrolling list - _TopContent(data: _data), - - /// Scrolling Events list, takes up the full view - _EventsList(data: _data), + 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), ], ), ), ), ), + + /// Scrolling event list + Expanded( + child: Center( + child: SizedBox( + width: $styles.sizes.maxContentWidth1, + child: _EventsList(data: _data, topHeight: 100, blurOnScroll: false), + ), + ), + ), + ], + ); + } + + /// Portrait layout is a stack with the list scrolling overtop of the WonderImage + 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), + + /// Scrolling Events list, takes up the full view + Column( + children: [ + Expanded( + child: _EventsList(data: _data, topHeight: topHeight, blurOnScroll: true, showTopGradient: false), + ), + Gap($styles.insets.lg), + SizedBox(width: $styles.sizes.maxContentWidth3, child: _TimelineBtn(type: _data.type)), + Gap($styles.insets.xl), + ], + ), + ], + ), + ), ); }); } diff --git a/pubspec.lock b/pubspec.lock index 2a4895cb..5f5e7a12 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -297,7 +297,7 @@ packages: name: flutter_inappwebview url: "https://pub.dartlang.org" source: hosted - version: "5.4.3+7" + version: "5.7.1" flutter_lints: dependency: "direct dev" description: