Initial pass at vertical menu for smaller landscape views
This commit is contained in:
parent
f786e3f5dc
commit
e09aa3d1b8
@ -4,12 +4,19 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
|||||||
static double bottomPadding = 0;
|
static double bottomPadding = 0;
|
||||||
static double buttonInset = 12;
|
static double buttonInset = 12;
|
||||||
|
|
||||||
const WonderDetailsTabMenu({Key? key, required this.tabController, this.showBg = false, required this.wonderType})
|
const WonderDetailsTabMenu(
|
||||||
|
{Key? key,
|
||||||
|
required this.tabController,
|
||||||
|
this.showBg = false,
|
||||||
|
required this.wonderType,
|
||||||
|
this.axis = Axis.horizontal})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final TabController tabController;
|
final TabController tabController;
|
||||||
final bool showBg;
|
final bool showBg;
|
||||||
final WonderType wonderType;
|
final WonderType wonderType;
|
||||||
|
final Axis axis;
|
||||||
|
bool get isVertical => axis == Axis.vertical;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -19,53 +26,79 @@ class WonderDetailsTabMenu extends StatelessWidget {
|
|||||||
bottomPadding = max(context.mq.padding.bottom, $styles.insets.xs * 1.5);
|
bottomPadding = max(context.mq.padding.bottom, $styles.insets.xs * 1.5);
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
//Background
|
/// Background, animates in and out based on `showBg`
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
duration: $styles.times.fast,
|
duration: $styles.times.fast,
|
||||||
opacity: showBg ? 1 : 0,
|
opacity: showBg ? 1 : 0,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(top: buttonInset),
|
padding: EdgeInsets.only(
|
||||||
|
top: isVertical ? context.mq.viewPadding.top : buttonInset,
|
||||||
|
right: isVertical ? buttonInset : 0,
|
||||||
|
),
|
||||||
child: ColoredBox(color: $styles.colors.offWhite),
|
child: ColoredBox(color: $styles.colors.offWhite),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Buttons
|
|
||||||
|
/// Buttons
|
||||||
|
/// A Stack with a row or column of tabButtons, and an illustrated homeButton sitting on top.
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: $styles.insets.sm, right: $styles.insets.xxs, bottom: bottomPadding),
|
padding: EdgeInsets.only(left: $styles.insets.sm, right: $styles.insets.xxs, bottom: bottomPadding),
|
||||||
// TabButtons are a Stack with a row of icon buttons, and an illustrated home button sitting on top.
|
child: Stack(
|
||||||
// The home buttons shows / hides itself based on `showHomeBtn`
|
children: [
|
||||||
// The row contains an animated placeholder gap which makes room for the icon as it transitions in.
|
SizedBox(
|
||||||
child: IntrinsicHeight(
|
width: isVertical ? null : double.infinity,
|
||||||
child: Stack(
|
height: isVertical ? double.infinity : null,
|
||||||
children: [
|
child: FocusTraversalGroup(
|
||||||
// Main tab btns + animated gap
|
child: Flex(
|
||||||
Center(
|
direction: axis,
|
||||||
child: FocusTraversalGroup(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Row(
|
crossAxisAlignment: isVertical ? CrossAxisAlignment.start : CrossAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
/// Home btn
|
||||||
// Home btn
|
_WonderHomeBtn(
|
||||||
_WonderHomeBtn(
|
size: homeBtnSize,
|
||||||
size: homeBtnSize,
|
wonderType: wonderType,
|
||||||
wonderType: wonderType,
|
borderSize: showBg ? 6 : 2,
|
||||||
borderSize: showBg ? 6 : 2,
|
),
|
||||||
),
|
|
||||||
_TabBtn(0, tabController,
|
/// Tabs
|
||||||
iconImg: 'editorial', label: $strings.wonderDetailsTabLabelInformation, color: iconColor),
|
_TabBtn(
|
||||||
_TabBtn(1, tabController,
|
0,
|
||||||
iconImg: 'photos', label: $strings.wonderDetailsTabLabelImages, color: iconColor),
|
tabController,
|
||||||
_TabBtn(2, tabController,
|
iconImg: 'editorial',
|
||||||
iconImg: 'artifacts', label: $strings.wonderDetailsTabLabelArtifacts, color: iconColor),
|
label: $strings.wonderDetailsTabLabelInformation,
|
||||||
_TabBtn(3, tabController,
|
color: iconColor,
|
||||||
iconImg: 'timeline', label: $strings.wonderDetailsTabLabelEvents, color: iconColor),
|
axis: axis,
|
||||||
],
|
),
|
||||||
),
|
_TabBtn(
|
||||||
|
1,
|
||||||
|
tabController,
|
||||||
|
iconImg: 'photos',
|
||||||
|
label: $strings.wonderDetailsTabLabelImages,
|
||||||
|
color: iconColor,
|
||||||
|
axis: axis,
|
||||||
|
),
|
||||||
|
_TabBtn(
|
||||||
|
2,
|
||||||
|
tabController,
|
||||||
|
iconImg: 'artifacts',
|
||||||
|
label: $strings.wonderDetailsTabLabelArtifacts,
|
||||||
|
color: iconColor,
|
||||||
|
axis: axis,
|
||||||
|
),
|
||||||
|
_TabBtn(3, tabController,
|
||||||
|
iconImg: 'timeline',
|
||||||
|
label: $strings.wonderDetailsTabLabelEvents,
|
||||||
|
color: iconColor,
|
||||||
|
axis: axis),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -111,16 +144,21 @@ class _TabBtn extends StatelessWidget {
|
|||||||
required this.iconImg,
|
required this.iconImg,
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.label,
|
required this.label,
|
||||||
|
required this.axis,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
static const double _minWidth = 50;
|
static const double minSize = 50;
|
||||||
static const double _maxWidth = 120;
|
static const double maxSize = 100;
|
||||||
|
static const double crossBtnSize = 60;
|
||||||
|
|
||||||
final int index;
|
final int index;
|
||||||
final TabController tabController;
|
final TabController tabController;
|
||||||
final String iconImg;
|
final String iconImg;
|
||||||
final Color color;
|
final Color color;
|
||||||
final String label;
|
final String label;
|
||||||
|
final Axis axis;
|
||||||
|
|
||||||
|
bool get _isVertical => axis == Axis.vertical;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -129,7 +167,7 @@ class _TabBtn extends StatelessWidget {
|
|||||||
final iconImgPath = '${ImagePaths.common}/tab-$iconImg${selected ? '-active' : ''}.png';
|
final iconImgPath = '${ImagePaths.common}/tab-$iconImg${selected ? '-active' : ''}.png';
|
||||||
String tabLabel = localizations.tabLabel(tabIndex: index + 1, tabCount: tabController.length);
|
String tabLabel = localizations.tabLabel(tabIndex: index + 1, tabCount: tabController.length);
|
||||||
tabLabel = '$label: $tabLabel';
|
tabLabel = '$label: $tabLabel';
|
||||||
final double btnWidth = (context.widthPx / 6).clamp(_minWidth, _maxWidth);
|
final double mainBtnSize = ((_isVertical ? context.heightPx : context.widthPx) / 6).clamp(minSize, maxSize);
|
||||||
return MergeSemantics(
|
return MergeSemantics(
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
selected: selected,
|
selected: selected,
|
||||||
@ -139,7 +177,7 @@ class _TabBtn extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(top: $styles.insets.md + $styles.insets.xs, bottom: $styles.insets.sm),
|
padding: EdgeInsets.only(top: $styles.insets.md + $styles.insets.xs, bottom: $styles.insets.sm),
|
||||||
onPressed: () => tabController.index = index,
|
onPressed: () => tabController.index = index,
|
||||||
semanticLabel: label,
|
semanticLabel: label,
|
||||||
minimumSize: Size(btnWidth, 0),
|
minimumSize: _isVertical ? Size(crossBtnSize, mainBtnSize) : Size(mainBtnSize, crossBtnSize),
|
||||||
// Image icon
|
// Image icon
|
||||||
child: Image.asset(iconImgPath, height: 32, width: 32, color: selected ? null : color),
|
child: Image.asset(iconImgPath, height: 32, width: 32, color: selected ? null : color),
|
||||||
),
|
),
|
||||||
|
@ -26,7 +26,8 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
|
|||||||
AnimationController? _fade;
|
AnimationController? _fade;
|
||||||
|
|
||||||
final _detailsHasScrolled = ValueNotifier(false);
|
final _detailsHasScrolled = ValueNotifier(false);
|
||||||
double? _tabBarHeight;
|
double? _tabBarSize;
|
||||||
|
bool _useNavRail = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -42,16 +43,20 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
|
|||||||
void _handleDetailsScrolled(double scrollPos) => _detailsHasScrolled.value = scrollPos > 0;
|
void _handleDetailsScrolled(double scrollPos) => _detailsHasScrolled.value = scrollPos > 0;
|
||||||
|
|
||||||
void _handleTabMenuSized(Size size) {
|
void _handleTabMenuSized(Size size) {
|
||||||
setState(() => _tabBarHeight = size.height - WonderDetailsTabMenu.buttonInset);
|
setState(() {
|
||||||
|
_tabBarSize = (_useNavRail ? size.width : size.height) - WonderDetailsTabMenu.buttonInset;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
_useNavRail = context.isLandscape && context.heightPx < 900;
|
||||||
|
|
||||||
final wonder = wondersLogic.getData(widget.type);
|
final wonder = wondersLogic.getData(widget.type);
|
||||||
int tabIndex = _tabController.index;
|
int tabIndex = _tabController.index;
|
||||||
bool showTabBarBg = tabIndex != 1;
|
bool showTabBarBg = tabIndex != 1;
|
||||||
final tabBarHeight = _tabBarHeight ?? 0;
|
final tabBarSize = _tabBarSize ?? 0;
|
||||||
//final double tabBarHeight = WonderDetailsTabMenu.bottomPadding + 60;
|
final menuPadding = _useNavRail ? EdgeInsets.only(left: tabBarSize) : EdgeInsets.only(bottom: tabBarSize);
|
||||||
return ColoredBox(
|
return ColoredBox(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@ -62,21 +67,41 @@ class _WonderDetailsScreenState extends State<WonderDetailsScreen>
|
|||||||
children: [
|
children: [
|
||||||
WonderEditorialScreen(wonder, onScroll: _handleDetailsScrolled),
|
WonderEditorialScreen(wonder, onScroll: _handleDetailsScrolled),
|
||||||
PhotoGallery(collectionId: wonder.unsplashCollectionId, wonderType: wonder.type),
|
PhotoGallery(collectionId: wonder.unsplashCollectionId, wonderType: wonder.type),
|
||||||
Padding(padding: EdgeInsets.only(bottom: tabBarHeight), child: ArtifactCarouselScreen(type: wonder.type)),
|
AnimatedPadding(
|
||||||
Padding(padding: EdgeInsets.only(bottom: tabBarHeight), child: WonderEvents(type: widget.type)),
|
duration: $styles.times.fast,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
padding: menuPadding,
|
||||||
|
child: ArtifactCarouselScreen(type: wonder.type),
|
||||||
|
),
|
||||||
|
AnimatedPadding(
|
||||||
|
duration: $styles.times.fast,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
padding: menuPadding,
|
||||||
|
child: WonderEvents(type: widget.type),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
/// Tab menu
|
/// Tab menu
|
||||||
BottomCenter(
|
Align(
|
||||||
|
alignment: _useNavRail ? Alignment.centerLeft : Alignment.bottomCenter,
|
||||||
child: ValueListenableBuilder<bool>(
|
child: ValueListenableBuilder<bool>(
|
||||||
valueListenable: _detailsHasScrolled,
|
valueListenable: _detailsHasScrolled,
|
||||||
builder: (_, value, ___) => MeasurableWidget(
|
builder: (_, value, ___) => MeasurableWidget(
|
||||||
onChange: _handleTabMenuSized,
|
onChange: _handleTabMenuSized,
|
||||||
child: WonderDetailsTabMenu(
|
|
||||||
tabController: _tabController,
|
/// Animate the menu in when the axis changes
|
||||||
wonderType: wonder.type,
|
child: Animate(
|
||||||
showBg: showTabBarBg,
|
key: ValueKey(_useNavRail),
|
||||||
|
effects: [
|
||||||
|
FadeEffect(begin: 0, delay: $styles.times.fast),
|
||||||
|
SlideEffect(begin: _useNavRail ? Offset(-.2, 0) : Offset(0, .2)),
|
||||||
|
],
|
||||||
|
child: WonderDetailsTabMenu(
|
||||||
|
tabController: _tabController,
|
||||||
|
wonderType: wonder.type,
|
||||||
|
showBg: showTabBarBg,
|
||||||
|
axis: _useNavRail ? Axis.vertical : Axis.horizontal),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -68,49 +68,52 @@ class WonderEvents extends StatelessWidget {
|
|||||||
|
|
||||||
/// Landscape layout is a row, with the WonderImage on left and EventsList on the right
|
/// Landscape layout is a row, with the WonderImage on left and EventsList on the right
|
||||||
Widget _buildTwoColumn(BuildContext context) {
|
Widget _buildTwoColumn(BuildContext context) {
|
||||||
return Row(
|
return Padding(
|
||||||
children: [
|
padding: EdgeInsets.all($styles.insets.lg),
|
||||||
/// WonderImage w/ Timeline btn
|
child: Row(
|
||||||
Expanded(
|
children: [
|
||||||
child: CenteredBox(
|
/// WonderImage w/ Timeline btn
|
||||||
width: $styles.sizes.maxContentWidth3,
|
Expanded(
|
||||||
child: Column(
|
child: CenteredBox(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
width: $styles.sizes.maxContentWidth3,
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
children: [
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
Gap($styles.insets.lg),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Expanded(
|
children: [
|
||||||
child: Center(
|
Gap($styles.insets.lg),
|
||||||
child: Column(
|
Expanded(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Center(
|
||||||
children: [
|
child: Column(
|
||||||
_WonderImageWithTimeline(data: _data, height: min(500, context.heightPx - 300)),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Gap($styles.insets.lg),
|
children: [
|
||||||
SizedBox(width: 400, child: _TimelineBtn(type: type)),
|
_WonderImageWithTimeline(data: _data, height: min(500, context.heightPx - 300)),
|
||||||
],
|
Gap($styles.insets.lg),
|
||||||
|
SizedBox(width: 400, child: _TimelineBtn(type: type)),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Gap($styles.insets.lg),
|
||||||
Gap($styles.insets.lg),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
/// EventsList
|
/// EventsList
|
||||||
Expanded(
|
Expanded(
|
||||||
child: CenteredBox(
|
child: CenteredBox(
|
||||||
width: $styles.sizes.maxContentWidth2,
|
width: $styles.sizes.maxContentWidth2,
|
||||||
child: _EventsList(
|
child: _EventsList(
|
||||||
data: _data,
|
data: _data,
|
||||||
topHeight: 100,
|
topHeight: 100,
|
||||||
blurOnScroll: false,
|
blurOnScroll: false,
|
||||||
onScroll: _handleScroll,
|
onScroll: _handleScroll,
|
||||||
initialScrollOffset: _scrollPos,
|
initialScrollOffset: _scrollPos,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user