Skip to content

Commit b9e3aef

Browse files
authored
Add FocusNode, onFocusChange, and autofocus to CupertinoSwitch (#126684)
fixes flutter/flutter#126679 ### Description Control cupertino widgets like `CupertinoCheckbox` and `CupertinoRadio` have `focusNode` and `autofocus`, while these are missing in `CupertinoSwitch`. This is blocking flutter/flutter#126637 (`switch.dart` currently maintains its focus node for `CupertinoSwitch` when using `Switch.adaptive` which causes a bug)
1 parent 6ffcc9e commit b9e3aef

2 files changed

Lines changed: 126 additions & 0 deletions

File tree

packages/flutter/lib/src/cupertino/switch.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ class CupertinoSwitch extends StatefulWidget {
7575
this.thumbColor,
7676
this.applyTheme,
7777
this.focusColor,
78+
this.focusNode,
79+
this.onFocusChange,
80+
this.autofocus = false,
7881
this.dragStartBehavior = DragStartBehavior.start,
7982
});
8083

@@ -130,6 +133,15 @@ class CupertinoSwitch extends StatefulWidget {
130133
/// Defaults to a slightly transparent [activeColor].
131134
final Color? focusColor;
132135

136+
/// {@macro flutter.widgets.Focus.focusNode}
137+
final FocusNode? focusNode;
138+
139+
/// {@macro flutter.material.inkwell.onFocusChange}
140+
final ValueChanged<bool>? onFocusChange;
141+
142+
/// {@macro flutter.widgets.Focus.autofocus}
143+
final bool autofocus;
144+
133145
/// {@template flutter.cupertino.CupertinoSwitch.applyTheme}
134146
/// Whether to apply the ambient [CupertinoThemeData].
135147
///
@@ -356,6 +368,9 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
356368
onShowFocusHighlight: _onShowFocusHighlight,
357369
actions: _actionMap,
358370
enabled: isInteractive,
371+
focusNode: widget.focusNode,
372+
onFocusChange: widget.onFocusChange,
373+
autofocus: widget.autofocus,
359374
child: _CupertinoSwitchRenderObjectWidget(
360375
value: widget.value,
361376
activeColor: activeColor,

packages/flutter/test/cupertino/switch_test.dart

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,4 +913,115 @@ void main() {
913913
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
914914
);
915915
});
916+
917+
testWidgets('CupertinoSwitch is focusable and has correct focus color', (WidgetTester tester) async {
918+
final FocusNode focusNode = FocusNode(debugLabel: 'CupertinoSwitch');
919+
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
920+
bool value = true;
921+
const Color focusColor = Color(0xffff0000);
922+
923+
Widget buildApp({bool enabled = true}) {
924+
return Directionality(
925+
textDirection: TextDirection.ltr,
926+
child: StatefulBuilder(
927+
builder: (BuildContext context, StateSetter setState) {
928+
return Center(
929+
child: CupertinoSwitch(
930+
value: value,
931+
onChanged: enabled ? (bool newValue) {
932+
setState(() {
933+
value = newValue;
934+
});
935+
} : null,
936+
focusColor: focusColor,
937+
focusNode: focusNode,
938+
autofocus: true,
939+
),
940+
);
941+
},
942+
),
943+
);
944+
}
945+
946+
await tester.pumpWidget(buildApp());
947+
await tester.pumpAndSettle();
948+
949+
expect(focusNode.hasPrimaryFocus, isTrue);
950+
expect(
951+
find.byType(CupertinoSwitch),
952+
paints
953+
..rrect(color: const Color(0xff34c759))
954+
..rrect(color: focusColor)
955+
..clipRRect()
956+
..rrect(color: const Color(0x26000000))
957+
..rrect(color: const Color(0x0f000000))
958+
..rrect(color: const Color(0x0a000000))
959+
..rrect(color: const Color(0xffffffff)),
960+
);
961+
962+
// Check the false value.
963+
value = false;
964+
await tester.pumpWidget(buildApp());
965+
await tester.pumpAndSettle();
966+
967+
expect(focusNode.hasPrimaryFocus, isTrue);
968+
expect(
969+
find.byType(CupertinoSwitch),
970+
paints
971+
..rrect(color: const Color(0x28787880))
972+
..rrect(color: focusColor)
973+
..clipRRect()
974+
..rrect(color: const Color(0x26000000))
975+
..rrect(color: const Color(0x0f000000))
976+
..rrect(color: const Color(0x0a000000))
977+
..rrect(color: const Color(0xffffffff)),
978+
);
979+
980+
// Check what happens when disabled.
981+
value = false;
982+
await tester.pumpWidget(buildApp(enabled: false));
983+
await tester.pumpAndSettle();
984+
985+
expect(focusNode.hasPrimaryFocus, isFalse);
986+
expect(
987+
find.byType(CupertinoSwitch),
988+
paints
989+
..rrect(color: const Color(0x28787880))
990+
..clipRRect()
991+
..rrect(color: const Color(0x26000000))
992+
..rrect(color: const Color(0x0f000000))
993+
..rrect(color: const Color(0x0a000000))
994+
..rrect(color: const Color(0xffffffff)),
995+
);
996+
});
997+
998+
testWidgets('CupertinoSwitch.onFocusChange callback', (WidgetTester tester) async {
999+
final FocusNode focusNode = FocusNode(debugLabel: 'CupertinoSwitch');
1000+
bool focused = false;
1001+
await tester.pumpWidget(
1002+
Directionality(
1003+
textDirection: TextDirection.ltr,
1004+
child: Center(
1005+
child: CupertinoSwitch(
1006+
value: true,
1007+
focusNode: focusNode,
1008+
onFocusChange: (bool value) {
1009+
focused = value;
1010+
},
1011+
onChanged:(bool newValue) {},
1012+
),
1013+
),
1014+
),
1015+
);
1016+
1017+
focusNode.requestFocus();
1018+
await tester.pump();
1019+
expect(focused, isTrue);
1020+
expect(focusNode.hasFocus, isTrue);
1021+
1022+
focusNode.unfocus();
1023+
await tester.pump();
1024+
expect(focused, isFalse);
1025+
expect(focusNode.hasFocus, isFalse);
1026+
});
9161027
}

0 commit comments

Comments
 (0)