[Gallery] Crane focus (#254)

* Add debugLabel to HighlightFocus

* Remove decrated property

* Add focus to DestinationCards

* Add index property to Forms

* Move BackLayer stuff to its own file

* Add focus

* Revert deprecated change

* Address feedback
pull/257/head
Pierre-Louis 5 years ago committed by GitHub
parent a89c9a4747
commit 7f783273c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,6 +15,7 @@ class HighlightFocus extends StatefulWidget {
this.highlightColor, this.highlightColor,
this.borderColor, this.borderColor,
this.hasFocus = true, this.hasFocus = true,
this.debugLabel,
}); });
/// [onPressed] is called when you press space, enter, or numpad-enter /// [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. /// Set to false if you want the child to skip focus.
final bool hasFocus; final bool hasFocus;
final String debugLabel;
@override @override
_HighlightFocusState createState() => _HighlightFocusState(); _HighlightFocusState createState() => _HighlightFocusState();
} }
@ -67,6 +70,7 @@ class _HighlightFocusState extends State<HighlightFocus> {
return Focus( return Focus(
canRequestFocus: widget.hasFocus, canRequestFocus: widget.hasFocus,
debugLabel: widget.debugLabel,
onFocusChange: (newValue) { onFocusChange: (newValue) {
setState(() { setState(() {
isFocused = newValue; isFocused = newValue;

@ -41,10 +41,10 @@ class _CraneAppState extends State<CraneApp> {
home: ApplyTextOptions( home: ApplyTextOptions(
child: Backdrop( child: Backdrop(
frontLayer: Container(), frontLayer: Container(),
backLayer: [ backLayerItems: [
FlyForm(), FlyForm(index: 0),
SleepForm(), SleepForm(index: 1),
EatForm(), EatForm(index: 2),
], ],
frontTitle: Text('CRANE'), frontTitle: Text('CRANE'),
backTitle: Text('MENU'), backTitle: Text('MENU'),

@ -13,6 +13,7 @@ import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart'; import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/layout/adaptive.dart'; import 'package:gallery/layout/adaptive.dart';
import 'package:gallery/studies/crane/border_tab_indicator.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/colors.dart';
import 'package:gallery/studies/crane/item_cards.dart'; import 'package:gallery/studies/crane/item_cards.dart';
@ -32,26 +33,29 @@ class _FrontLayer extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context); final isDesktop = isDisplayDesktop(context);
return PhysicalShape( return DefaultFocusTraversal(
elevation: 16, policy: ReadingOrderTraversalPolicy(),
color: cranePrimaryWhite, child: PhysicalShape(
clipper: ShapeBorderClipper( elevation: 16,
shape: RoundedRectangleBorder( color: cranePrimaryWhite,
borderRadius: BorderRadius.only( clipper: ShapeBorderClipper(
topLeft: Radius.circular(frontLayerBorderRadius), shape: RoundedRectangleBorder(
topRight: Radius.circular(frontLayerBorderRadius), borderRadius: BorderRadius.only(
topLeft: Radius.circular(frontLayerBorderRadius),
topRight: Radius.circular(frontLayerBorderRadius),
),
), ),
), ),
), child: ListView(
child: ListView( padding: isDesktop
padding: isDesktop ? EdgeInsets.symmetric(horizontal: 120, vertical: 22)
? EdgeInsets.symmetric(horizontal: 120, vertical: 22) : EdgeInsets.all(20),
: EdgeInsets.all(20), children: [
children: [ Text(title, style: Theme.of(context).textTheme.subtitle),
Text(title, style: Theme.of(context).textTheme.subtitle), SizedBox(height: 20),
SizedBox(height: 20), ItemCards(index: index),
ItemCards(index: index), ],
], ),
), ),
); );
} }
@ -65,17 +69,17 @@ class _FrontLayer extends StatelessWidget {
/// front or back layer is showing. /// front or back layer is showing.
class Backdrop extends StatefulWidget { class Backdrop extends StatefulWidget {
final Widget frontLayer; final Widget frontLayer;
final List<Widget> backLayer; final List<BackLayerItem> backLayerItems;
final Widget frontTitle; final Widget frontTitle;
final Widget backTitle; final Widget backTitle;
const Backdrop({ const Backdrop({
@required this.frontLayer, @required this.frontLayer,
@required this.backLayer, @required this.backLayerItems,
@required this.frontTitle, @required this.frontTitle,
@required this.backTitle, @required this.backTitle,
}) : assert(frontLayer != null), }) : assert(frontLayer != null),
assert(backLayer != null), assert(backLayerItems != null),
assert(frontTitle != null), assert(frontTitle != null),
assert(backTitle != null); assert(backTitle != null);
@ -125,61 +129,68 @@ class _BackdropState extends State<Backdrop> with TickerProviderStateMixin {
color: cranePurple800, color: cranePurple800,
child: Padding( child: Padding(
padding: EdgeInsets.only(top: 12), padding: EdgeInsets.only(top: 12),
child: Scaffold( child: DefaultFocusTraversal(
backgroundColor: cranePurple800, policy: ReadingOrderTraversalPolicy(),
appBar: AppBar( child: Scaffold(
brightness: Brightness.dark, backgroundColor: cranePurple800,
elevation: 0, appBar: AppBar(
titleSpacing: 0, brightness: Brightness.dark,
flexibleSpace: CraneAppBar( elevation: 0,
tabController: _tabController, titleSpacing: 0,
tabHandler: _handleTabs, flexibleSpace: CraneAppBar(
),
),
body: Stack(
children: [
BackLayer(
tabController: _tabController, tabController: _tabController,
backLayers: widget.backLayer, tabHandler: _handleTabs,
), ),
Container( ),
margin: EdgeInsets.only( body: FocusScope(
top: isDesktop child: Stack(
? 60 + 20 * textScaleFactor / 2 children: [
: 175 + 140 * textScaleFactor / 2, BackLayer(
), tabController: _tabController,
child: TabBarView( backLayerItems: widget.backLayerItems,
physics: isDesktop ),
? NeverScrollableScrollPhysics() Container(
: null, // use default TabBarView physics margin: EdgeInsets.only(
controller: _tabController, top: isDesktop
children: [ ? 60 + 20 * textScaleFactor / 2
SlideTransition( : 175 + 140 * textScaleFactor / 2,
position: _flyLayerOffset,
child: _FrontLayer(
title: GalleryLocalizations.of(context).craneFlySubhead,
index: 0,
),
),
SlideTransition(
position: _sleepLayerOffset,
child: _FrontLayer(
title:
GalleryLocalizations.of(context).craneSleepSubhead,
index: 1,
),
), ),
SlideTransition( child: TabBarView(
position: _eatLayerOffset, physics: isDesktop
child: _FrontLayer( ? NeverScrollableScrollPhysics()
title: GalleryLocalizations.of(context).craneEatSubhead, : null, // use default TabBarView physics
index: 2, 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<Backdrop> with TickerProviderStateMixin {
} }
} }
class BackLayer extends StatefulWidget {
final List<Widget> backLayers;
final TabController tabController;
const BackLayer({Key key, this.backLayers, this.tabController})
: super(key: key);
@override
_BackLayerState createState() => _BackLayerState();
}
class _BackLayerState extends State<BackLayer> {
@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 { class CraneAppBar extends StatefulWidget {
final Function(int) tabHandler; final Function(int) tabHandler;
final TabController tabController; final TabController tabController;

@ -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<BackLayerItem> backLayerItems;
final TabController tabController;
const BackLayer({Key key, this.backLayerItems, this.tabController})
: super(key: key);
@override
_BackLayerState createState() => _BackLayerState();
}
class _BackLayerState extends State<BackLayer> {
@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,
)
],
),
);
}
}

@ -5,9 +5,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart'; import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/studies/crane/backlayer.dart';
import 'package:gallery/studies/crane/header_form.dart'; import 'package:gallery/studies/crane/header_form.dart';
class EatForm extends StatefulWidget { class EatForm extends BackLayerItem {
EatForm({int index}) : super(index: index);
@override @override
_EatFormState createState() => _EatFormState(); _EatFormState createState() => _EatFormState();
} }

@ -5,9 +5,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart'; import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/studies/crane/backlayer.dart';
import 'package:gallery/studies/crane/header_form.dart'; import 'package:gallery/studies/crane/header_form.dart';
class FlyForm extends StatefulWidget { class FlyForm extends BackLayerItem {
FlyForm({int index}) : super(index: index);
@override @override
_FlyFormState createState() => _FlyFormState(); _FlyFormState createState() => _FlyFormState();
} }

@ -5,6 +5,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:gallery/layout/adaptive.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/data.dart';
import 'package:gallery/studies/crane/model/destination.dart'; import 'package:gallery/studies/crane/model/destination.dart';
@ -33,8 +34,13 @@ class _ItemCardsState extends State<ItemCards> {
return destinations return destinations
.map( .map(
(d) => RepaintBoundary( (d) => HighlightFocus(
child: _DestinationCard(destination: d), debugLabel: 'DestinationCard: ${d.destination}',
highlightColor: Colors.red.withOpacity(0.5),
onPressed: () {},
child: RepaintBoundary(
child: _DestinationCard(destination: d),
),
), ),
) )
.toList(); .toList();

@ -5,9 +5,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gallery/l10n/gallery_localizations.dart'; import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/studies/crane/backlayer.dart';
import 'package:gallery/studies/crane/header_form.dart'; import 'package:gallery/studies/crane/header_form.dart';
class SleepForm extends StatefulWidget { class SleepForm extends BackLayerItem {
SleepForm({int index}) : super(index: index);
@override @override
_SleepFormState createState() => _SleepFormState(); _SleepFormState createState() => _SleepFormState();
} }

Loading…
Cancel
Save