Unverified Commit 62addedc authored by xster's avatar xster Committed by GitHub

Fix focus switching when changing tabs in CupertinoTabScaffold (#14431)

* Fix focus switching when changing tabs in CupertinoTabScaffold

* review

* Test focuses are saved within tabs

* review
parent 681192a0
......@@ -134,7 +134,7 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
Widget build(BuildContext context) {
final List<Widget> stacked = <Widget>[];
Widget content = new _TabView(
Widget content = new _TabSwitchingView(
currentTabIndex: _currentPage,
tabNumber: widget.tabBar.items.length,
tabBuilder: widget.tabBuilder,
......@@ -197,10 +197,10 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
}
}
/// An widget laying out multiple tabs with only one active tab being built
/// A widget laying out multiple tabs with only one active tab being built
/// at a time and on stage. Off stage tabs' animations are stopped.
class _TabView extends StatefulWidget {
const _TabView({
class _TabSwitchingView extends StatefulWidget {
const _TabSwitchingView({
@required this.currentTabIndex,
@required this.tabNumber,
@required this.tabBuilder,
......@@ -213,16 +213,45 @@ class _TabView extends StatefulWidget {
final IndexedWidgetBuilder tabBuilder;
@override
_TabViewState createState() => new _TabViewState();
_TabSwitchingViewState createState() => new _TabSwitchingViewState();
}
class _TabViewState extends State<_TabView> {
class _TabSwitchingViewState extends State<_TabSwitchingView> {
List<Widget> tabs;
List<FocusScopeNode> tabFocusNodes;
@override
void initState() {
super.initState();
tabs = new List<Widget>(widget.tabNumber);
tabFocusNodes = new List<FocusScopeNode>.generate(
widget.tabNumber,
(int index) => new FocusScopeNode(),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_focusActiveTab();
}
@override
void didUpdateWidget(_TabSwitchingView oldWidget) {
super.didUpdateWidget(oldWidget);
_focusActiveTab();
}
void _focusActiveTab() {
FocusScope.of(context).setFirstFocus(tabFocusNodes[widget.currentTabIndex]);
}
@override
void dispose() {
for (FocusScopeNode focusScopeNode in tabFocusNodes) {
focusScopeNode.detach();
}
super.dispose();
}
@override
......@@ -232,14 +261,18 @@ class _TabViewState extends State<_TabView> {
children: new List<Widget>.generate(widget.tabNumber, (int index) {
final bool active = index == widget.currentTabIndex;
if (active || tabs[index] != null)
if (active || tabs[index] != null) {
tabs[index] = widget.tabBuilder(context, index);
}
return new Offstage(
offstage: !active,
child: new TickerMode(
enabled: active,
child: tabs[index] ?? new Container(),
child: new FocusScope(
node: tabFocusNodes[index],
child: tabs[index] ?? new Container(),
),
),
);
}),
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import '../painting/mocks_for_image_cache.dart';
......@@ -34,7 +35,7 @@ void main() {
onPaint: () { tabsPainted.add(index); }
)
);
}
},
);
},
);
......@@ -88,11 +89,11 @@ void main() {
settings: settings,
builder: (BuildContext context) {
return new CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
tabsBuilt.add(index);
return new Text('Page ${index + 1}');
}
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
tabsBuilt.add(index);
return new Text('Page ${index + 1}');
},
);
},
);
......@@ -119,6 +120,121 @@ void main() {
expect(find.text('Page 1'), findsOneWidget);
expect(find.text('Page 2', skipOffstage: false), isOffstage);
});
testWidgets('Last tab gets focus', (WidgetTester tester) async {
// 2 nodes for 2 tabs
final List<FocusNode> focusNodes = <FocusNode>[new FocusNode(), new FocusNode()];
await tester.pumpWidget(
new WidgetsApp(
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
return new CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return new TextField(
focusNode: focusNodes[index],
autofocus: true,
);
},
);
},
);
},
),
);
expect(focusNodes[0].hasFocus, isTrue);
await tester.tap(find.text('Tab 2'));
await tester.pump();
expect(focusNodes[0].hasFocus, isFalse);
expect(focusNodes[1].hasFocus, isTrue);
await tester.tap(find.text('Tab 1'));
await tester.pump();
expect(focusNodes[0].hasFocus, isTrue);
expect(focusNodes[1].hasFocus, isFalse);
});
testWidgets('Do not affect focus order in the route', (WidgetTester tester) async {
final List<FocusNode> focusNodes = <FocusNode>[
new FocusNode(), new FocusNode(), new FocusNode(), new FocusNode(),
];
await tester.pumpWidget(
new WidgetsApp(
color: const Color(0xFFFFFFFF),
onGenerateRoute: (RouteSettings settings) {
return new CupertinoPageRoute<Null>(
settings: settings,
builder: (BuildContext context) {
return new Material(
child: new CupertinoTabScaffold(
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return new Column(
children: <Widget>[
new TextField(
focusNode: focusNodes[index * 2],
decoration: const InputDecoration(
hintText: 'TextField 1',
),
),
new TextField(
focusNode: focusNodes[index * 2 + 1],
decoration: const InputDecoration(
hintText: 'TextField 2',
),
),
],
);
},
),
);
},
);
},
),
);
expect(
focusNodes.any((FocusNode node) => node.hasFocus),
isFalse,
);
await tester.tap(find.widgetWithText(TextField, 'TextField 2'));
expect(
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
1,
);
await tester.tap(find.text('Tab 2'));
await tester.pump();
await tester.tap(find.widgetWithText(TextField, 'TextField 1'));
expect(
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
2,
);
await tester.tap(find.text('Tab 1'));
await tester.pump();
// Upon going back to tab 1, the item it tab 1 that previously had the focus
// (TextField 2) gets it back.
expect(
focusNodes.indexOf(focusNodes.singleWhere((FocusNode node) => node.hasFocus)),
1,
);
});
}
CupertinoTabBar _buildTabBar() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment