Update events view, add responsiveness, switch to dark mode

This commit is contained in:
Shawn 2022-11-14 11:59:45 -07:00
parent 973d9c61cf
commit 34a0ae7dbf
7 changed files with 195 additions and 77 deletions

View File

@ -158,7 +158,7 @@ class _Corners {
class _Sizes { class _Sizes {
double get maxContentWidth1 => 800; double get maxContentWidth1 => 800;
double get maxContentWidth2 => 600; double get maxContentWidth2 => 600;
double get maxContentWidth3 => 400; double get maxContentWidth3 => 500;
final Size minAppSize = Size(450, 600); final Size minAppSize = Size(450, 600);
} }

View File

@ -1,37 +1,47 @@
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/common/string_utils.dart'; import 'package:wonders/logic/common/string_utils.dart';
import 'package:wonders/ui/common/themed_text.dart';
class TimelineEventCard extends StatelessWidget { 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 int year;
final String text; final String text;
final bool darkMode;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MergeSemantics( return MergeSemantics(
child: Padding( child: Padding(
padding: EdgeInsets.only(bottom: $styles.insets.sm), padding: EdgeInsets.only(bottom: $styles.insets.sm),
child: Container( child: DefaultTextColor(
color: $styles.colors.offWhite, color: darkMode ? Colors.white : Colors.black,
padding: EdgeInsets.all($styles.insets.sm), child: Container(
child: Row( color: darkMode ? $styles.colors.greyStrong : $styles.colors.offWhite,
children: [ padding: EdgeInsets.all($styles.insets.sm),
SizedBox( child: Row(
width: 75, children: [
child: Column( /// Date
crossAxisAlignment: CrossAxisAlignment.start, SizedBox(
children: [ width: 75,
Text('${year.abs()}', style: $styles.text.h3.copyWith(fontWeight: FontWeight.w400, height: 1)), child: Column(
Text(StringUtils.getYrSuffix(year), style: $styles.text.bodySmall), 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)), /// Divider
Gap($styles.insets.sm), Center(child: Container(width: 1, height: 50, color: darkMode ? Colors.white : $styles.colors.black)),
Expanded( Gap($styles.insets.sm),
child: Text(text, style: $styles.text.bodySmall),
), /// Text content
], Expanded(
child: Text(text, style: $styles.text.bodySmall),
),
],
),
), ),
), ),
), ),

View File

@ -1,8 +1,19 @@
part of '../wonder_events.dart'; part of '../wonder_events.dart';
class _EventsList extends StatefulWidget { 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 WonderData data;
final double topHeight;
final bool blurOnScroll;
final bool showTopGradient;
final bool showBottomGradient;
@override @override
State<_EventsList> createState() => _EventsListState(); State<_EventsList> createState() => _EventsListState();
@ -16,8 +27,6 @@ class _EventsListState extends State<_EventsList> {
super.dispose(); super.dispose();
} }
void _handleGlobalTimelinePressed() => context.push(ScreenPaths.timeline(widget.data.type));
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PopRouterOnOverScroll( return PopRouterOnOverScroll(
@ -28,9 +37,9 @@ class _EventsListState extends State<_EventsList> {
AnimatedBuilder( AnimatedBuilder(
animation: _scroller, animation: _scroller,
builder: (_, __) { builder: (_, __) {
bool showBackdrop = true; bool showBackdrop = widget.blurOnScroll;
double backdropAmt = 0; double backdropAmt = 0;
if (_scroller.hasClients) { if (_scroller.hasClients && showBackdrop) {
double blurStart = 50; double blurStart = 50;
double maxScroll = 150; double maxScroll = 150;
double scrollPx = _scroller.position.pixels - blurStart; double scrollPx = _scroller.position.pixels - blurStart;
@ -77,46 +86,55 @@ class _EventsListState extends State<_EventsList> {
for (var e in events.entries) { for (var e in events.entries) {
final delay = 100.ms + (100 * listItems.length).ms; final delay = 100.ms + (100 * listItems.length).ms;
listItems.add( listItems.add(
TimelineEventCard(year: e.key, text: e.value) TimelineEventCard(year: e.key, text: e.value, darkMode: true)
.animate() .animate()
.fade(delay: delay, duration: $styles.times.med * 1.5) .fade(delay: delay, duration: $styles.times.med * 1.5)
.slide(begin: Offset(0, 1), curve: Curves.easeOutBack), .slide(begin: Offset(0, 1), curve: Curves.easeOutBack),
); );
} }
return SingleChildScrollView( return Stack(
controller: _scroller, children: [
child: Column( SingleChildScrollView(
children: [ controller: _scroller,
IgnorePointer(child: Gap(WonderEvents._topHeight)), child: Column(
Container( children: [
decoration: BoxDecoration( IgnorePointer(child: Gap(widget.topHeight)),
color: $styles.colors.white, Container(
borderRadius: BorderRadius.circular($styles.corners.md), decoration: BoxDecoration(
), color: $styles.colors.black,
padding: EdgeInsets.symmetric(horizontal: $styles.insets.md), borderRadius: BorderRadius.circular($styles.corners.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,
), ),
Gap($styles.insets.xl), padding: EdgeInsets.symmetric(horizontal: $styles.insets.md),
CompassDivider(isExpanded: true), child: Column(
Gap($styles.insets.md), children: [
HiddenCollectible(widget.data.type, index: 2, size: 150), Gap($styles.insets.xs),
Gap(150), 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),
),
),
],
); );
} }
} }

View File

@ -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,
),
);
}
}

View File

@ -1,11 +1,12 @@
part of '../wonder_events.dart'; part of '../wonder_events.dart';
class _TopContent extends StatelessWidget { class _WonderImageWithTimeline extends StatelessWidget {
const _TopContent({Key? key, required this.data}) : super(key: key); const _WonderImageWithTimeline({Key? key, required this.data, required this.height}) : super(key: key);
final WonderData data; final WonderData data;
final double height;
Color _fixLuminence(Color color, [double luminence = 0.35]) { Color _fixLuminance(Color color, [double luminance = 0.35]) {
double d = luminence - color.computeLuminance(); double d = luminance - color.computeLuminance();
if (d <= 0) return color; if (d <= 0) return color;
int r = color.red, g = color.green, b = color.blue; 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()); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(
height: WonderEvents._topHeight, height: height,
child: MergeSemantics( child: MergeSemantics(
child: LightText( child: LightText(
child: SeparatedColumn( child: SeparatedColumn(
@ -45,7 +46,7 @@ class _TopContent extends StatelessWidget {
timelineBuilder: (_, data, isSelected) { timelineBuilder: (_, data, isSelected) {
return Container( return Container(
decoration: BoxDecoration( 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), border: Border.all(color: $styles.colors.greyMedium),
borderRadius: BorderRadius.circular($styles.corners.md), borderRadius: BorderRadius.circular($styles.corners.md),
), ),

View File

@ -2,7 +2,7 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/common/string_utils.dart'; import 'package:wonders/logic/common/string_utils.dart';
import 'package:wonders/logic/data/wonder_data.dart'; import 'package:wonders/logic/data/wonder_data.dart';
import 'package:wonders/ui/common/app_backdrop.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/curved_clippers.dart';
import 'package:wonders/ui/common/hidden_collectible.dart'; import 'package:wonders/ui/common/hidden_collectible.dart';
import 'package:wonders/ui/common/list_gradient.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'; import 'package:wonders/ui/wonder_illustrations/common/wonder_title_text.dart';
part 'widgets/_events_list.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 { class WonderEvents extends StatelessWidget {
static const double _topHeight = 450;
WonderEvents({Key? key, required this.type}) : super(key: key); WonderEvents({Key? key, required this.type}) : super(key: key);
final WonderType type; final WonderType type;
late final _data = wondersLogic.getData(type); late final _data = wondersLogic.getData(type);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder(builder: (_, constraints) { void handleTimelineBtnPressed() => context.push(ScreenPaths.timeline(type));
return LayoutBuilder(builder: (context, constraints) {
return Container( return Container(
color: $styles.colors.black, color: $styles.colors.black,
child: SafeArea( child: SafeArea(
bottom: false, 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: Center(
child: SizedBox( child: SizedBox(
width: $styles.sizes.maxContentWidth1, width: $styles.sizes.maxContentWidth3,
child: Stack( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [ children: [
/// Top content, sits underneath scrolling list Gap($styles.insets.lg),
_TopContent(data: _data), Expanded(child: Center(child: _WonderImageWithTimeline(data: _data, height: 500))),
Gap($styles.insets.lg),
/// Scrolling Events list, takes up the full view SizedBox(width: 300, child: _TimelineBtn(type: type)),
_EventsList(data: _data), 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),
],
),
],
),
),
); );
}); });
} }

View File

@ -297,7 +297,7 @@ packages:
name: flutter_inappwebview name: flutter_inappwebview
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.4.3+7" version: "5.7.1"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description: