Inject menu padding into the detail views so they can have their backgrounds overlap with the nav-rail and its curved corner.

This commit is contained in:
Shawn 2023-04-18 11:47:03 -06:00
parent 0f2e2afd9b
commit 50d4bac794
6 changed files with 130 additions and 113 deletions

View File

@ -12,8 +12,9 @@ part 'widgets/_bottom_text_content.dart';
part 'widgets/_collapsing_carousel_item.dart';
class ArtifactCarouselScreen extends StatefulWidget {
const ArtifactCarouselScreen({Key? key, required this.type, this.contentPadding = EdgeInsets.zero}) : super(key: key);
final WonderType type;
const ArtifactCarouselScreen({Key? key, required this.type}) : super(key: key);
final EdgeInsets contentPadding;
@override
State<ArtifactCarouselScreen> createState() => _ArtifactScreenState();
@ -82,55 +83,61 @@ class _ArtifactScreenState extends State<ArtifactCarouselScreen> {
}),
),
/// BgCircle
_buildBgCircle(bottomHeight),
Padding(
padding: widget.contentPadding,
child: Stack(
children: [
/// BgCircle
_buildBgCircle(bottomHeight),
/// Carousel Items
PageView.builder(
controller: _pageController,
itemBuilder: (_, index) {
final wrappedIndex = index % pages.length;
final child = pages[wrappedIndex];
return ValueListenableBuilder<double>(
valueListenable: _currentPage,
builder: (_, value, __) {
final int offset = (value.round() - index).abs();
return _CollapsingCarouselItem(
width: itemWidth,
indexOffset: min(3, offset),
onPressed: () => _handleArtifactTap(index),
title: _artifacts[wrappedIndex].title,
child: child,
);
},
);
},
),
/// Carousel Items
PageView.builder(
controller: _pageController,
itemBuilder: (_, index) {
final wrappedIndex = index % pages.length;
final child = pages[wrappedIndex];
return ValueListenableBuilder<double>(
valueListenable: _currentPage,
builder: (_, value, __) {
final int offset = (value.round() - index).abs();
return _CollapsingCarouselItem(
width: itemWidth,
indexOffset: min(3, offset),
onPressed: () => _handleArtifactTap(index),
title: _artifacts[wrappedIndex].title,
child: child,
);
},
);
},
),
/// Bottom Text
BottomCenter(
child: ValueListenableBuilder<int>(
valueListenable: _currentArtifactIndex,
builder: (_, value, __) => _BottomTextContent(
artifact: _artifacts[value],
height: bottomHeight,
shortMode: shortMode,
state: this,
),
),
),
/// Bottom Text
BottomCenter(
child: ValueListenableBuilder<int>(
valueListenable: _currentArtifactIndex,
builder: (_, value, __) => _BottomTextContent(
artifact: _artifacts[value],
height: bottomHeight,
shortMode: shortMode,
state: this,
),
),
),
/// Header
AppHeader(
title: $strings.artifactsTitleArtifacts,
showBackBtn: false,
isTransparent: true,
trailing: (context) => CircleBtn(
semanticLabel: $strings.artifactsButtonBrowse,
onPressed: _handleSearchTap,
child: AppIcon(AppIcons.search),
),
),
/// Header
AppHeader(
title: $strings.artifactsTitleArtifacts,
showBackBtn: false,
isTransparent: true,
trailing: (context) => CircleBtn(
semanticLabel: $strings.artifactsButtonBrowse,
onPressed: _handleSearchTap,
child: AppIcon(AppIcons.search),
),
),
],
))
],
);
}

View File

@ -38,9 +38,11 @@ part 'widgets/_title_text.dart';
part 'widgets/_top_illustration.dart';
class WonderEditorialScreen extends StatefulWidget {
const WonderEditorialScreen(this.data, {Key? key, required this.onScroll}) : super(key: key);
const WonderEditorialScreen(this.data, {Key? key, required this.onScroll, required this.contentPadding})
: super(key: key);
final WonderData data;
final void Function(double scrollPos) onScroll;
final EdgeInsets contentPadding;
@override
State<WonderEditorialScreen> createState() => _WonderEditorialScreenState();
@ -95,62 +97,71 @@ class _WonderEditorialScreenState extends State<WonderEditorialScreen> {
return Opacity(opacity: opacity, child: child);
},
// This is due to a bug: https://github.com/flutter/flutter/issues/101872
child: RepaintBoundary(child: _TopIllustration(widget.data.type)),
child: RepaintBoundary(
child: _TopIllustration(
widget.data.type,
// Polish: Inject the content padding into the illustration as an offset, so it can center itself relative to the content
// this allows the background to extend underneath the vertical side nav when it has rounded corners.
fgOffset: Offset(widget.contentPadding.left / 2, 0),
)),
),
),
/// Scrolling content - Includes an invisible gap at the top, and then scrolls over the illustration
TopCenter(
child: SizedBox(
child: FocusTraversalGroup(
child: CustomScrollView(
primary: false,
controller: _scroller,
scrollBehavior: ScrollConfiguration.of(context).copyWith(),
key: PageStorageKey('editorial'),
slivers: [
/// Invisible padding at the top of the list, so the illustration shows through the btm
SliverToBoxAdapter(
child: SizedBox(height: illustrationHeight),
),
/// Text content, animates itself to hide behind the app bar as it scrolls up
SliverToBoxAdapter(
child: ValueListenableBuilder<double>(
valueListenable: _scrollPos,
builder: (_, value, child) {
double offsetAmt = max(0, value * .3);
double opacity = (1 - offsetAmt / 150).clamp(0, 1);
return Transform.translate(
offset: Offset(0, offsetAmt),
child: Opacity(opacity: opacity, child: child),
);
},
child: _TitleText(widget.data, scroller: _scroller),
child: Padding(
padding: widget.contentPadding,
child: SizedBox(
child: FocusTraversalGroup(
child: CustomScrollView(
primary: false,
controller: _scroller,
scrollBehavior: ScrollConfiguration.of(context).copyWith(),
key: PageStorageKey('editorial'),
slivers: [
/// Invisible padding at the top of the list, so the illustration shows through the btm
SliverToBoxAdapter(
child: SizedBox(height: illustrationHeight),
),
),
/// Collapsing App bar, pins to the top of the list
SliverAppBar(
pinned: true,
collapsedHeight: minAppBarHeight,
toolbarHeight: minAppBarHeight,
expandedHeight: maxAppBarHeight,
backgroundColor: Colors.transparent,
elevation: 0,
leading: SizedBox.shrink(),
flexibleSpace: SizedBox.expand(
child: _AppBar(
widget.data.type,
scrollPos: _scrollPos,
sectionIndex: _sectionIndex,
/// Text content, animates itself to hide behind the app bar as it scrolls up
SliverToBoxAdapter(
child: ValueListenableBuilder<double>(
valueListenable: _scrollPos,
builder: (_, value, child) {
double offsetAmt = max(0, value * .3);
double opacity = (1 - offsetAmt / 150).clamp(0, 1);
return Transform.translate(
offset: Offset(0, offsetAmt),
child: Opacity(opacity: opacity, child: child),
);
},
child: _TitleText(widget.data, scroller: _scroller),
),
),
),
/// Editorial content (text and images)
_ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex),
],
/// Collapsing App bar, pins to the top of the list
SliverAppBar(
pinned: true,
collapsedHeight: minAppBarHeight,
toolbarHeight: minAppBarHeight,
expandedHeight: maxAppBarHeight,
backgroundColor: Colors.transparent,
elevation: 0,
leading: SizedBox.shrink(),
flexibleSpace: SizedBox.expand(
child: _AppBar(
widget.data.type,
scrollPos: _scrollPos,
sectionIndex: _sectionIndex,
),
),
),
/// Editorial content (text and images)
_ScrollingContent(widget.data, scrollPos: _scrollPos, sectionNotifier: _sectionIndex),
],
),
),
),
),

View File

@ -1,8 +1,9 @@
part of '../editorial_screen.dart';
class _TopIllustration extends StatelessWidget {
const _TopIllustration(this.type, {Key? key}) : super(key: key);
const _TopIllustration(this.type, {Key? key, this.fgOffset = Offset.zero}) : super(key: key);
final WonderType type;
final Offset fgOffset;
@override
Widget build(BuildContext context) {
@ -11,7 +12,7 @@ class _TopIllustration extends StatelessWidget {
WonderIllustration(type, config: WonderIllustrationConfig.bg(enableAnims: false, shortMode: true)),
Transform.translate(
// Small bump down to make sure we cover the edge between the editorial page and the sky.
offset: Offset(0, 10),
offset: fgOffset + Offset(0, 10),
child: WonderIllustration(
type,
config: WonderIllustrationConfig.mg(enableAnims: false, shortMode: true),

View File

@ -36,7 +36,12 @@ class WonderDetailsTabMenu extends StatelessWidget {
top: isVertical ? context.mq.viewPadding.top : buttonInset,
right: isVertical ? buttonInset : 0,
),
child: ColoredBox(color: $styles.colors.offWhite),
child: Container(
decoration: BoxDecoration(
color: $styles.colors.offWhite,
borderRadius: isVertical ? BorderRadius.only(topRight: Radius.circular(32)) : null,
),
),
),
),
),

View File

@ -57,7 +57,6 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
bool showTabBarBg = tabIndex != 1;
final tabBarSize = _tabBarSize ?? 0;
final menuPadding = _useNavRail ? EdgeInsets.only(left: tabBarSize) : EdgeInsets.only(bottom: tabBarSize);
return ColoredBox(
color: Colors.black,
child: Stack(
@ -66,19 +65,10 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
LazyIndexedStack(
index: _tabController.index,
children: [
Padding(
padding: menuPadding,
child: WonderEditorialScreen(wonder, onScroll: _handleDetailsScrolled),
),
WonderEditorialScreen(wonder, contentPadding: menuPadding, onScroll: _handleDetailsScrolled),
PhotoGallery(collectionId: wonder.unsplashCollectionId, wonderType: wonder.type),
Padding(
padding: menuPadding,
child: ArtifactCarouselScreen(type: wonder.type),
),
Padding(
padding: menuPadding,
child: WonderEvents(type: widget.type),
),
ArtifactCarouselScreen(type: wonder.type, contentPadding: menuPadding),
WonderEvents(type: widget.type, contentPadding: menuPadding),
],
),

View File

@ -19,9 +19,9 @@ part 'widgets/_timeline_btn.dart';
part 'widgets/_wonder_image_with_timeline.dart';
class WonderEvents extends StatefulWidget {
const WonderEvents({Key? key, required this.type}) : super(key: key);
const WonderEvents({Key? key, required this.type, this.contentPadding = EdgeInsets.zero}) : super(key: key);
final WonderType type;
final EdgeInsets contentPadding;
@override
State<WonderEvents> createState() => _WonderEventsState();
}
@ -51,7 +51,10 @@ class _WonderEventsState extends State<WonderEvents> {
/// Main view
Positioned.fill(
top: $styles.insets.sm,
child: useTwoColumnLayout ? _buildTwoColumn(context) : _buildSingleColumn(),
child: Padding(
padding: widget.contentPadding,
child: useTwoColumnLayout ? _buildTwoColumn(context) : _buildSingleColumn(),
),
),
/// Header w/ TimelineBtn