@@ -74,6 +74,7 @@ class CupertinoSwitch extends StatefulWidget {
7474 this .trackColor,
7575 this .thumbColor,
7676 this .applyTheme,
77+ this .focusColor,
7778 this .dragStartBehavior = DragStartBehavior .start,
7879 }) : assert (value != null ),
7980 assert (dragStartBehavior != null );
@@ -125,6 +126,11 @@ class CupertinoSwitch extends StatefulWidget {
125126 /// Defaults to [CupertinoColors.white] when null.
126127 final Color ? thumbColor;
127128
129+ /// The color to use for the focus highlight for keyboard interactions.
130+ ///
131+ /// Defaults to a a slightly transparent [activeColor] .
132+ final Color ? focusColor;
133+
128134 /// {@template flutter.cupertino.CupertinoSwitch.applyTheme}
129135 /// Whether to apply the ambient [CupertinoThemeData] .
130136 ///
@@ -178,8 +184,14 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
178184 late AnimationController _reactionController;
179185 late Animation <double > _reaction;
180186
187+ late bool isFocused;
188+
181189 bool get isInteractive => widget.onChanged != null ;
182190
191+ late final Map <Type , Action <Intent >> _actionMap = < Type , Action <Intent >> {
192+ ActivateIntent : CallbackAction <ActivateIntent >(onInvoke: _handleTap),
193+ };
194+
183195 // A non-null boolean value that changes to true at the end of a drag if the
184196 // switch must be animated to the position indicated by the widget's value.
185197 bool needsPositionAnimation = false ;
@@ -188,6 +200,8 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
188200 void initState () {
189201 super .initState ();
190202
203+ isFocused = false ;
204+
191205 _tap = TapGestureRecognizer ()
192206 ..onTapDown = _handleTapDown
193207 ..onTapUp = _handleTapUp
@@ -253,7 +267,7 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
253267 _reactionController.forward ();
254268 }
255269
256- void _handleTap () {
270+ void _handleTap ([ Intent ? _] ) {
257271 if (isInteractive) {
258272 widget.onChanged !(! widget.value);
259273 _emitVibration ();
@@ -322,29 +336,49 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
322336 }
323337 }
324338
339+ void _onShowFocusHighlight (bool showHighlight) {
340+ setState (() { isFocused = showHighlight; });
341+ }
342+
325343 @override
326344 Widget build (BuildContext context) {
327345 final CupertinoThemeData theme = CupertinoTheme .of (context);
346+ final Color activeColor = CupertinoDynamicColor .resolve (
347+ widget.activeColor
348+ ?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null )
349+ ?? CupertinoColors .systemGreen,
350+ context,
351+ );
328352 if (needsPositionAnimation) {
329353 _resumePositionAnimation ();
330354 }
331355 return MouseRegion (
332356 cursor: isInteractive && kIsWeb ? SystemMouseCursors .click : MouseCursor .defer,
333357 child: Opacity (
334358 opacity: widget.onChanged == null ? _kCupertinoSwitchDisabledOpacity : 1.0 ,
335- child: _CupertinoSwitchRenderObjectWidget (
336- value: widget.value,
337- activeColor: CupertinoDynamicColor .resolve (
338- widget.activeColor
339- ?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null )
340- ?? CupertinoColors .systemGreen,
341- context,
359+ child: FocusableActionDetector (
360+ onShowFocusHighlight: _onShowFocusHighlight,
361+ actions: _actionMap,
362+ enabled: isInteractive,
363+ child: _CupertinoSwitchRenderObjectWidget (
364+ value: widget.value,
365+ activeColor: activeColor,
366+ trackColor: CupertinoDynamicColor .resolve (widget.trackColor ?? CupertinoColors .secondarySystemFill, context),
367+ thumbColor: CupertinoDynamicColor .resolve (widget.thumbColor ?? CupertinoColors .white, context),
368+ // Opacity, lightness, and saturation values were aproximated with
369+ // color pickers on the switches in the macOS settings.
370+ focusColor: CupertinoDynamicColor .resolve (
371+ widget.focusColor ??
372+ HSLColor
373+ .fromColor (activeColor.withOpacity (0.80 ))
374+ .withLightness (0.69 ).withSaturation (0.835 )
375+ .toColor (),
376+ context),
377+ onChanged: widget.onChanged,
378+ textDirection: Directionality .of (context),
379+ isFocused: isFocused,
380+ state: this ,
342381 ),
343- trackColor: CupertinoDynamicColor .resolve (widget.trackColor ?? CupertinoColors .secondarySystemFill, context),
344- thumbColor: CupertinoDynamicColor .resolve (widget.thumbColor ?? CupertinoColors .white, context),
345- onChanged: widget.onChanged,
346- textDirection: Directionality .of (context),
347- state: this ,
348382 ),
349383 ),
350384 );
@@ -367,18 +401,22 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
367401 required this .activeColor,
368402 required this .trackColor,
369403 required this .thumbColor,
404+ required this .focusColor,
370405 required this .onChanged,
371406 required this .textDirection,
407+ required this .isFocused,
372408 required this .state,
373409 });
374410
375411 final bool value;
376412 final Color activeColor;
377413 final Color trackColor;
378414 final Color thumbColor;
415+ final Color focusColor;
379416 final ValueChanged <bool >? onChanged;
380417 final _CupertinoSwitchState state;
381418 final TextDirection textDirection;
419+ final bool isFocused;
382420
383421 @override
384422 _RenderCupertinoSwitch createRenderObject (BuildContext context) {
@@ -387,8 +425,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
387425 activeColor: activeColor,
388426 trackColor: trackColor,
389427 thumbColor: thumbColor,
428+ focusColor: focusColor,
390429 onChanged: onChanged,
391430 textDirection: textDirection,
431+ isFocused: isFocused,
392432 state: state,
393433 );
394434 }
@@ -401,8 +441,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
401441 ..activeColor = activeColor
402442 ..trackColor = trackColor
403443 ..thumbColor = thumbColor
444+ ..focusColor = focusColor
404445 ..onChanged = onChanged
405- ..textDirection = textDirection;
446+ ..textDirection = textDirection
447+ ..isFocused = isFocused;
406448 }
407449}
408450
@@ -426,18 +468,22 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
426468 required Color activeColor,
427469 required Color trackColor,
428470 required Color thumbColor,
471+ required Color focusColor,
429472 ValueChanged <bool >? onChanged,
430473 required TextDirection textDirection,
474+ required bool isFocused,
431475 required _CupertinoSwitchState state,
432476 }) : assert (value != null ),
433477 assert (activeColor != null ),
434478 assert (state != null ),
435479 _value = value,
436480 _activeColor = activeColor,
437481 _trackColor = trackColor,
482+ _focusColor = focusColor,
438483 _thumbPainter = CupertinoThumbPainter .switchThumb (color: thumbColor),
439484 _onChanged = onChanged,
440485 _textDirection = textDirection,
486+ _isFocused = isFocused,
441487 _state = state,
442488 super (additionalConstraints: const BoxConstraints .tightFor (width: _kSwitchWidth, height: _kSwitchHeight)) {
443489 state.position.addListener (markNeedsPaint);
@@ -490,6 +536,17 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
490536 markNeedsPaint ();
491537 }
492538
539+ Color get focusColor => _focusColor;
540+ Color _focusColor;
541+ set focusColor (Color value) {
542+ assert (value != null );
543+ if (value == _focusColor) {
544+ return ;
545+ }
546+ _focusColor = value;
547+ markNeedsPaint ();
548+ }
549+
493550 ValueChanged <bool >? get onChanged => _onChanged;
494551 ValueChanged <bool >? _onChanged;
495552 set onChanged (ValueChanged <bool >? value) {
@@ -515,6 +572,17 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
515572 markNeedsPaint ();
516573 }
517574
575+ bool get isFocused => _isFocused;
576+ bool _isFocused;
577+ set isFocused (bool value) {
578+ assert (value != null );
579+ if (value == _isFocused) {
580+ return ;
581+ }
582+ _isFocused = value;
583+ markNeedsPaint ();
584+ }
585+
518586 bool get isInteractive => onChanged != null ;
519587
520588 @override
@@ -570,6 +638,18 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
570638 final RRect trackRRect = RRect .fromRectAndRadius (trackRect, const Radius .circular (_kTrackRadius));
571639 canvas.drawRRect (trackRRect, paint);
572640
641+ if (_isFocused) {
642+ // Paints a border around the switch in the focus color.
643+ final RRect borderTrackRRect = trackRRect.inflate (1.75 );
644+
645+ final Paint borderPaint = Paint ()
646+ ..color = focusColor
647+ ..style = PaintingStyle .stroke
648+ ..strokeWidth = 3.5 ;
649+
650+ canvas.drawRRect (borderTrackRRect, borderPaint);
651+ }
652+
573653 final double currentThumbExtension = CupertinoThumbPainter .extension * currentReactionValue;
574654 final double thumbLeft = lerpDouble (
575655 trackRect.left + _kTrackInnerStart - CupertinoThumbPainter .radius,
0 commit comments