diff --git a/gallery/gallery/lib/layout/highlight_focus.dart b/gallery/gallery/lib/layout/highlight_focus.dart index ff4868df0..94bc412f8 100644 --- a/gallery/gallery/lib/layout/highlight_focus.dart +++ b/gallery/gallery/lib/layout/highlight_focus.dart @@ -15,6 +15,7 @@ class HighlightFocus extends StatefulWidget { this.highlightColor, this.borderColor, this.hasFocus = true, + this.debugLabel, }); /// [onPressed] is called when you press space, enter, or numpad-enter @@ -37,6 +38,8 @@ class HighlightFocus extends StatefulWidget { /// Set to false if you want the child to skip focus. final bool hasFocus; + final String debugLabel; + @override _HighlightFocusState createState() => _HighlightFocusState(); } @@ -67,6 +70,7 @@ class _HighlightFocusState extends State { return Focus( canRequestFocus: widget.hasFocus, + debugLabel: widget.debugLabel, onFocusChange: (newValue) { setState(() { isFocused = newValue; diff --git a/gallery/gallery/lib/studies/crane/app.dart b/gallery/gallery/lib/studies/crane/app.dart index 03ad2ce8b..15e85a6ca 100644 --- a/gallery/gallery/lib/studies/crane/app.dart +++ b/gallery/gallery/lib/studies/crane/app.dart @@ -41,10 +41,10 @@ class _CraneAppState extends State { home: ApplyTextOptions( child: Backdrop( frontLayer: Container(), - backLayer: [ - FlyForm(), - SleepForm(), - EatForm(), + backLayerItems: [ + FlyForm(index: 0), + SleepForm(index: 1), + EatForm(index: 2), ], frontTitle: Text('CRANE'), backTitle: Text('MENU'), diff --git a/gallery/gallery/lib/studies/crane/backdrop.dart b/gallery/gallery/lib/studies/crane/backdrop.dart index 5412bf732..c26ecf93c 100644 --- a/gallery/gallery/lib/studies/crane/backdrop.dart +++ b/gallery/gallery/lib/studies/crane/backdrop.dart @@ -13,6 +13,7 @@ import 'package:gallery/data/gallery_options.dart'; import 'package:gallery/l10n/gallery_localizations.dart'; import 'package:gallery/layout/adaptive.dart'; import 'package:gallery/studies/crane/border_tab_indicator.dart'; +import 'package:gallery/studies/crane/backlayer.dart'; import 'package:gallery/studies/crane/colors.dart'; import 'package:gallery/studies/crane/item_cards.dart'; @@ -32,26 +33,29 @@ class _FrontLayer extends StatelessWidget { Widget build(BuildContext context) { final isDesktop = isDisplayDesktop(context); - return PhysicalShape( - elevation: 16, - color: cranePrimaryWhite, - clipper: ShapeBorderClipper( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(frontLayerBorderRadius), - topRight: Radius.circular(frontLayerBorderRadius), + return DefaultFocusTraversal( + policy: ReadingOrderTraversalPolicy(), + child: PhysicalShape( + elevation: 16, + color: cranePrimaryWhite, + clipper: ShapeBorderClipper( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(frontLayerBorderRadius), + topRight: Radius.circular(frontLayerBorderRadius), + ), ), ), - ), - child: ListView( - padding: isDesktop - ? EdgeInsets.symmetric(horizontal: 120, vertical: 22) - : EdgeInsets.all(20), - children: [ - Text(title, style: Theme.of(context).textTheme.subtitle), - SizedBox(height: 20), - ItemCards(index: index), - ], + child: ListView( + padding: isDesktop + ? EdgeInsets.symmetric(horizontal: 120, vertical: 22) + : EdgeInsets.all(20), + children: [ + Text(title, style: Theme.of(context).textTheme.subtitle), + SizedBox(height: 20), + ItemCards(index: index), + ], + ), ), ); } @@ -65,17 +69,17 @@ class _FrontLayer extends StatelessWidget { /// front or back layer is showing. class Backdrop extends StatefulWidget { final Widget frontLayer; - final List backLayer; + final List backLayerItems; final Widget frontTitle; final Widget backTitle; const Backdrop({ @required this.frontLayer, - @required this.backLayer, + @required this.backLayerItems, @required this.frontTitle, @required this.backTitle, }) : assert(frontLayer != null), - assert(backLayer != null), + assert(backLayerItems != null), assert(frontTitle != null), assert(backTitle != null); @@ -125,61 +129,68 @@ class _BackdropState extends State with TickerProviderStateMixin { color: cranePurple800, child: Padding( padding: EdgeInsets.only(top: 12), - child: Scaffold( - backgroundColor: cranePurple800, - appBar: AppBar( - brightness: Brightness.dark, - elevation: 0, - titleSpacing: 0, - flexibleSpace: CraneAppBar( - tabController: _tabController, - tabHandler: _handleTabs, - ), - ), - body: Stack( - children: [ - BackLayer( + child: DefaultFocusTraversal( + policy: ReadingOrderTraversalPolicy(), + child: Scaffold( + backgroundColor: cranePurple800, + appBar: AppBar( + brightness: Brightness.dark, + elevation: 0, + titleSpacing: 0, + flexibleSpace: CraneAppBar( tabController: _tabController, - backLayers: widget.backLayer, + tabHandler: _handleTabs, ), - Container( - margin: EdgeInsets.only( - top: isDesktop - ? 60 + 20 * textScaleFactor / 2 - : 175 + 140 * textScaleFactor / 2, - ), - child: TabBarView( - physics: isDesktop - ? NeverScrollableScrollPhysics() - : null, // use default TabBarView physics - controller: _tabController, - children: [ - SlideTransition( - position: _flyLayerOffset, - child: _FrontLayer( - title: GalleryLocalizations.of(context).craneFlySubhead, - index: 0, - ), - ), - SlideTransition( - position: _sleepLayerOffset, - child: _FrontLayer( - title: - GalleryLocalizations.of(context).craneSleepSubhead, - index: 1, - ), + ), + body: FocusScope( + child: Stack( + children: [ + BackLayer( + tabController: _tabController, + backLayerItems: widget.backLayerItems, + ), + Container( + margin: EdgeInsets.only( + top: isDesktop + ? 60 + 20 * textScaleFactor / 2 + : 175 + 140 * textScaleFactor / 2, ), - SlideTransition( - position: _eatLayerOffset, - child: _FrontLayer( - title: GalleryLocalizations.of(context).craneEatSubhead, - index: 2, - ), + child: TabBarView( + physics: isDesktop + ? NeverScrollableScrollPhysics() + : null, // use default TabBarView physics + controller: _tabController, + children: [ + SlideTransition( + position: _flyLayerOffset, + child: _FrontLayer( + title: GalleryLocalizations.of(context) + .craneFlySubhead, + index: 0, + ), + ), + SlideTransition( + position: _sleepLayerOffset, + child: _FrontLayer( + title: GalleryLocalizations.of(context) + .craneSleepSubhead, + index: 1, + ), + ), + SlideTransition( + position: _eatLayerOffset, + child: _FrontLayer( + title: GalleryLocalizations.of(context) + .craneEatSubhead, + index: 2, + ), + ), + ], ), - ], - ), + ), + ], ), - ], + ), ), ), ), @@ -187,33 +198,6 @@ class _BackdropState extends State with TickerProviderStateMixin { } } -class BackLayer extends StatefulWidget { - final List backLayers; - final TabController tabController; - - const BackLayer({Key key, this.backLayers, this.tabController}) - : super(key: key); - - @override - _BackLayerState createState() => _BackLayerState(); -} - -class _BackLayerState extends State { - @override - void initState() { - super.initState(); - widget.tabController.addListener(() => setState(() {})); - } - - @override - Widget build(BuildContext context) { - return IndexedStack( - index: widget.tabController.index, - children: widget.backLayers, - ); - } -} - class CraneAppBar extends StatefulWidget { final Function(int) tabHandler; final TabController tabController; diff --git a/gallery/gallery/lib/studies/crane/backlayer.dart b/gallery/gallery/lib/studies/crane/backlayer.dart new file mode 100644 index 000000000..7e0853455 --- /dev/null +++ b/gallery/gallery/lib/studies/crane/backlayer.dart @@ -0,0 +1,49 @@ +// Copyright 2019 The Flutter team. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +abstract class BackLayerItem extends StatefulWidget { + final int index; + + BackLayerItem({Key key, this.index}) : super(key: key); +} + +class BackLayer extends StatefulWidget { + final List backLayerItems; + final TabController tabController; + + const BackLayer({Key key, this.backLayerItems, this.tabController}) + : super(key: key); + + @override + _BackLayerState createState() => _BackLayerState(); +} + +class _BackLayerState extends State { + @override + void initState() { + super.initState(); + widget.tabController.addListener(() => setState(() {})); + } + + @override + Widget build(BuildContext context) { + final tabIndex = widget.tabController.index; + return DefaultFocusTraversal( + policy: WidgetOrderFocusTraversalPolicy(), + child: IndexedStack( + index: tabIndex, + children: [ + for (BackLayerItem backLayerItem in widget.backLayerItems) + Focus( + canRequestFocus: backLayerItem.index == tabIndex, + debugLabel: 'backLayerItem: $backLayerItem', + child: backLayerItem, + ) + ], + ), + ); + } +} diff --git a/gallery/gallery/lib/studies/crane/eat_form.dart b/gallery/gallery/lib/studies/crane/eat_form.dart index 366bfe369..60a0e7e73 100644 --- a/gallery/gallery/lib/studies/crane/eat_form.dart +++ b/gallery/gallery/lib/studies/crane/eat_form.dart @@ -5,9 +5,12 @@ import 'package:flutter/material.dart'; import 'package:gallery/l10n/gallery_localizations.dart'; +import 'package:gallery/studies/crane/backlayer.dart'; import 'package:gallery/studies/crane/header_form.dart'; -class EatForm extends StatefulWidget { +class EatForm extends BackLayerItem { + EatForm({int index}) : super(index: index); + @override _EatFormState createState() => _EatFormState(); } diff --git a/gallery/gallery/lib/studies/crane/fly_form.dart b/gallery/gallery/lib/studies/crane/fly_form.dart index a17c65b71..b30814343 100644 --- a/gallery/gallery/lib/studies/crane/fly_form.dart +++ b/gallery/gallery/lib/studies/crane/fly_form.dart @@ -5,9 +5,12 @@ import 'package:flutter/material.dart'; import 'package:gallery/l10n/gallery_localizations.dart'; +import 'package:gallery/studies/crane/backlayer.dart'; import 'package:gallery/studies/crane/header_form.dart'; -class FlyForm extends StatefulWidget { +class FlyForm extends BackLayerItem { + FlyForm({int index}) : super(index: index); + @override _FlyFormState createState() => _FlyFormState(); } diff --git a/gallery/gallery/lib/studies/crane/item_cards.dart b/gallery/gallery/lib/studies/crane/item_cards.dart index b0abfea20..5158adbc3 100644 --- a/gallery/gallery/lib/studies/crane/item_cards.dart +++ b/gallery/gallery/lib/studies/crane/item_cards.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:gallery/layout/adaptive.dart'; +import 'package:gallery/layout/highlight_focus.dart'; import 'package:gallery/studies/crane/model/data.dart'; import 'package:gallery/studies/crane/model/destination.dart'; @@ -33,8 +34,13 @@ class _ItemCardsState extends State { return destinations .map( - (d) => RepaintBoundary( - child: _DestinationCard(destination: d), + (d) => HighlightFocus( + debugLabel: 'DestinationCard: ${d.destination}', + highlightColor: Colors.red.withOpacity(0.5), + onPressed: () {}, + child: RepaintBoundary( + child: _DestinationCard(destination: d), + ), ), ) .toList(); diff --git a/gallery/gallery/lib/studies/crane/sleep_form.dart b/gallery/gallery/lib/studies/crane/sleep_form.dart index e83016648..a7ade1a16 100644 --- a/gallery/gallery/lib/studies/crane/sleep_form.dart +++ b/gallery/gallery/lib/studies/crane/sleep_form.dart @@ -5,9 +5,12 @@ import 'package:flutter/material.dart'; import 'package:gallery/l10n/gallery_localizations.dart'; +import 'package:gallery/studies/crane/backlayer.dart'; import 'package:gallery/studies/crane/header_form.dart'; -class SleepForm extends StatefulWidget { +class SleepForm extends BackLayerItem { + SleepForm({int index}) : super(index: index); + @override _SleepFormState createState() => _SleepFormState(); }