-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add test for wrong behavior * fix #1 * reorganize tests * fix assertion error caught by tests * properly unregister listeners * version bump * bump version used by example * fix format * simplify OffscreenFocusExclusionBuilder
- Loading branch information
Showing
7 changed files
with
194 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
## 1.0.1 | ||
|
||
* fix focus traversal | ||
|
||
## 1.0.0 | ||
|
||
Initial release, please refer to the readme and the example for available functionality. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import 'package:flutter/material.dart'; | ||
|
||
/// Only allow focus of the visible widget and block focus during animation by | ||
/// wrapping children in [ExcludeFocus]. | ||
class OffscreenFocusExclusionBuilder extends StatelessWidget { | ||
/// Only allow focus of the visible widget and block focus during animation. | ||
const OffscreenFocusExclusionBuilder({ | ||
super.key, | ||
required this.controller, | ||
required this.children, | ||
required this.builder, | ||
}); | ||
|
||
/// This widget's selection and animation state. | ||
final TabController controller; | ||
|
||
/// One widget per tab. | ||
/// | ||
/// Its length must match the length of the [TabBar.tabs] | ||
/// list, as well as the [controller]'s [TabController.length]. | ||
final List<Widget> children; | ||
|
||
/// Child builder, takes the wrapped children. | ||
final Widget Function(List<Widget> children) builder; | ||
|
||
@override | ||
Widget build(BuildContext context) => ListenableBuilder( | ||
listenable: controller, | ||
builder: (BuildContext context, Widget? _child) { | ||
if (controller.indexIsChanging) | ||
return ExcludeFocus(child: builder(children)); | ||
return builder([ | ||
for (int i = 0; i < children.length; i++) | ||
ExcludeFocus( | ||
excluding: i != controller.index, | ||
child: children[i], | ||
) | ||
]); | ||
}, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:inline_tab_view/inline_tab_view.dart'; | ||
import 'package:inline_tab_view/src/offscreen_focus_exclusion_builder.dart'; | ||
|
||
void main() { | ||
testWidgets('hidden children are not focusable', (tester) async { | ||
final controller = TabController(length: 2, vsync: const TestVSync()); | ||
addTearDown(controller.dispose); | ||
|
||
final leadingFocus = FocusNode(); | ||
addTearDown(leadingFocus.dispose); | ||
final trailingFocus = FocusNode(); | ||
addTearDown(trailingFocus.dispose); | ||
final tab1Wid1Focus = FocusNode(); | ||
addTearDown(tab1Wid1Focus.dispose); | ||
final tab1Wid2Focus = FocusNode(); | ||
addTearDown(tab1Wid2Focus.dispose); | ||
final tab1Wid3Focus = FocusNode(); | ||
addTearDown(tab1Wid3Focus.dispose); | ||
final tab2Wid1Focus = FocusNode(); | ||
addTearDown(tab2Wid1Focus.dispose); | ||
final tab2Wid2Focus = FocusNode(); | ||
addTearDown(tab2Wid2Focus.dispose); | ||
final tab2Wid3Focus = FocusNode(); | ||
addTearDown(tab2Wid3Focus.dispose); | ||
|
||
await tester.pumpWidget(MaterialApp( | ||
home: Column( | ||
children: [ | ||
Focus( | ||
key: Key('leading'), | ||
focusNode: leadingFocus, | ||
child: SizedBox.square(dimension: 10)), | ||
InlineTabView( | ||
controller: controller, | ||
children: [ | ||
Column( | ||
children: [ | ||
Focus( | ||
key: Key('Tab 1 - 1'), | ||
focusNode: tab1Wid1Focus, | ||
child: SizedBox.square(dimension: 10)), | ||
Focus( | ||
key: Key('Tab 1 - 2'), | ||
focusNode: tab1Wid2Focus, | ||
child: SizedBox.square(dimension: 10)), | ||
Focus( | ||
key: Key('Tab 1 - 3'), | ||
focusNode: tab1Wid3Focus, | ||
child: SizedBox.square(dimension: 10)), | ||
], | ||
), | ||
Column( | ||
children: [ | ||
Focus( | ||
key: Key('Tab 2 - 1'), | ||
focusNode: tab2Wid1Focus, | ||
child: SizedBox.square(dimension: 10)), | ||
Focus( | ||
key: Key('Tab 2 - 2'), | ||
focusNode: tab2Wid2Focus, | ||
child: SizedBox.square(dimension: 10)), | ||
Focus( | ||
key: Key('Tab 2 - 3'), | ||
focusNode: tab2Wid3Focus, | ||
child: SizedBox.square(dimension: 10)), | ||
], | ||
) | ||
], | ||
), | ||
Focus( | ||
key: Key('trailing'), | ||
focusNode: trailingFocus, | ||
child: SizedBox.square(dimension: 10)), | ||
], | ||
), | ||
)); | ||
expect(find.byType(OffscreenFocusExclusionBuilder), findsOneWidget); | ||
|
||
tab1Wid1Focus.requestFocus(); | ||
await tester.pumpAndSettle(); | ||
expect(leadingFocus.hasFocus, false); | ||
expect(tab1Wid1Focus.hasFocus, true); | ||
expect(tab1Wid2Focus.hasFocus, false); | ||
expect(tab1Wid3Focus.hasFocus, false); | ||
expect(tab2Wid1Focus.hasFocus, false); | ||
expect(tab2Wid2Focus.hasFocus, false); | ||
expect(tab2Wid3Focus.hasFocus, false); | ||
expect(trailingFocus.hasFocus, false); | ||
|
||
// it doesn't (shouldn't) matter which node is used to request the next focus. | ||
leadingFocus.nextFocus(); | ||
await tester.pumpAndSettle(); | ||
expect(leadingFocus.hasFocus, false); | ||
expect(tab1Wid1Focus.hasFocus, false); | ||
expect(tab1Wid2Focus.hasFocus, true); | ||
expect(tab1Wid3Focus.hasFocus, false); | ||
expect(tab2Wid1Focus.hasFocus, false); | ||
expect(tab2Wid2Focus.hasFocus, false); | ||
expect(tab2Wid3Focus.hasFocus, false); | ||
expect(trailingFocus.hasFocus, false); | ||
|
||
leadingFocus.nextFocus(); | ||
await tester.pumpAndSettle(); | ||
expect(leadingFocus.hasFocus, false); | ||
expect(tab1Wid1Focus.hasFocus, false); | ||
expect(tab1Wid2Focus.hasFocus, false); | ||
expect(tab1Wid3Focus.hasFocus, true); | ||
expect(tab2Wid1Focus.hasFocus, false); | ||
expect(tab2Wid2Focus.hasFocus, false); | ||
expect(tab2Wid3Focus.hasFocus, false); | ||
expect(trailingFocus.hasFocus, false); | ||
|
||
leadingFocus.nextFocus(); | ||
await tester.pumpAndSettle(); | ||
expect(leadingFocus.hasFocus, false); | ||
expect(tab1Wid1Focus.hasFocus, false); | ||
expect(tab1Wid2Focus.hasFocus, false); | ||
expect(tab1Wid3Focus.hasFocus, false); | ||
expect(tab2Wid1Focus.hasFocus, false); | ||
expect(tab2Wid2Focus.hasFocus, false); | ||
expect(tab2Wid3Focus.hasFocus, false); | ||
expect(trailingFocus.hasFocus, true); | ||
|
||
leadingFocus.previousFocus(); | ||
leadingFocus.previousFocus(); | ||
leadingFocus.previousFocus(); | ||
leadingFocus.previousFocus(); | ||
await tester.pumpAndSettle(); | ||
expect(leadingFocus.hasFocus, true); | ||
expect(tab1Wid1Focus.hasFocus, false); | ||
expect(tab1Wid2Focus.hasFocus, false); | ||
expect(tab1Wid3Focus.hasFocus, false); | ||
expect(tab2Wid1Focus.hasFocus, false); | ||
expect(tab2Wid2Focus.hasFocus, false); | ||
expect(tab2Wid3Focus.hasFocus, false); | ||
expect(trailingFocus.hasFocus, false); | ||
}); | ||
} |