Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add focus detector to CupertinoSwitch #118345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Generated file, do not edit.
#

list(APPEND FLUTTER_PLUGIN_LIST
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
)

set(PLUGIN_BUNDLED_LIBRARIES)

foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)

foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
23 changes: 23 additions & 0 deletions examples/flutter_view/windows/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Generated file, do not edit.
#

list(APPEND FLUTTER_PLUGIN_LIST
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
)

set(PLUGIN_BUNDLED_LIBRARIES)

foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)

foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
23 changes: 23 additions & 0 deletions examples/platform_view/windows/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# Generated file, do not edit.
#

list(APPEND FLUTTER_PLUGIN_LIST
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
)

set(PLUGIN_BUNDLED_LIBRARIES)

foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)

foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
108 changes: 94 additions & 14 deletions packages/flutter/lib/src/cupertino/switch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class CupertinoSwitch extends StatefulWidget {
this.trackColor,
this.thumbColor,
this.applyTheme,
this.focusColor,
this.dragStartBehavior = DragStartBehavior.start,
}) : assert(value != null),
assert(dragStartBehavior != null);
Expand Down Expand Up @@ -125,6 +126,11 @@ class CupertinoSwitch extends StatefulWidget {
/// Defaults to [CupertinoColors.white] when null.
final Color? thumbColor;

/// The color to use for the focus highlight for keyboard interactions.
///
/// Defaults to a a slightly transparent [activeColor].
final Color? focusColor;

/// {@template flutter.cupertino.CupertinoSwitch.applyTheme}
/// Whether to apply the ambient [CupertinoThemeData].
///
Expand Down Expand Up @@ -178,8 +184,14 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
late AnimationController _reactionController;
late Animation<double> _reaction;

late bool isFocused;

bool get isInteractive => widget.onChanged != null;

late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _handleTap),
};

// A non-null boolean value that changes to true at the end of a drag if the
// switch must be animated to the position indicated by the widget's value.
bool needsPositionAnimation = false;
Expand All @@ -188,6 +200,8 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
void initState() {
super.initState();

isFocused = false;

_tap = TapGestureRecognizer()
..onTapDown = _handleTapDown
..onTapUp = _handleTapUp
Expand Down Expand Up @@ -253,7 +267,7 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
_reactionController.forward();
}

void _handleTap() {
void _handleTap([Intent? _]) {
if (isInteractive) {
widget.onChanged!(!widget.value);
_emitVibration();
Expand Down Expand Up @@ -322,29 +336,49 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
}
}

void _onShowFocusHighlight(bool showHighlight) {
setState(() { isFocused = showHighlight; });
}

@override
Widget build(BuildContext context) {
final CupertinoThemeData theme = CupertinoTheme.of(context);
final Color activeColor = CupertinoDynamicColor.resolve(
widget.activeColor
?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null)
?? CupertinoColors.systemGreen,
context,
);
if (needsPositionAnimation) {
_resumePositionAnimation();
}
return MouseRegion(
cursor: isInteractive && kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
child: Opacity(
opacity: widget.onChanged == null ? _kCupertinoSwitchDisabledOpacity : 1.0,
child: _CupertinoSwitchRenderObjectWidget(
value: widget.value,
activeColor: CupertinoDynamicColor.resolve(
widget.activeColor
?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null)
?? CupertinoColors.systemGreen,
context,
child: FocusableActionDetector(
onShowFocusHighlight: _onShowFocusHighlight,
actions: _actionMap,
enabled: isInteractive,
child: _CupertinoSwitchRenderObjectWidget(
value: widget.value,
activeColor: activeColor,
trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),
thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor ?? CupertinoColors.white, context),
// Opacity, lightness, and saturation values were aproximated with
// color pickers on the switches in the macOS settings.
focusColor: CupertinoDynamicColor.resolve(
widget.focusColor ??
HSLColor
.fromColor(activeColor.withOpacity(0.80))
.withLightness(0.69).withSaturation(0.835)
.toColor(),
context),
onChanged: widget.onChanged,
textDirection: Directionality.of(context),
isFocused: isFocused,
state: this,
),
trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),
thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor ?? CupertinoColors.white, context),
onChanged: widget.onChanged,
textDirection: Directionality.of(context),
state: this,
),
),
);
Expand All @@ -367,18 +401,22 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
required this.activeColor,
required this.trackColor,
required this.thumbColor,
required this.focusColor,
required this.onChanged,
required this.textDirection,
required this.isFocused,
required this.state,
});

final bool value;
final Color activeColor;
final Color trackColor;
final Color thumbColor;
final Color focusColor;
final ValueChanged<bool>? onChanged;
final _CupertinoSwitchState state;
final TextDirection textDirection;
final bool isFocused;

@override
_RenderCupertinoSwitch createRenderObject(BuildContext context) {
Expand All @@ -387,8 +425,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
activeColor: activeColor,
trackColor: trackColor,
thumbColor: thumbColor,
focusColor: focusColor,
onChanged: onChanged,
textDirection: textDirection,
isFocused: isFocused,
state: state,
);
}
Expand All @@ -401,8 +441,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget {
..activeColor = activeColor
..trackColor = trackColor
..thumbColor = thumbColor
..focusColor = focusColor
..onChanged = onChanged
..textDirection = textDirection;
..textDirection = textDirection
..isFocused = isFocused;
}
}

Expand All @@ -426,18 +468,22 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
required Color activeColor,
required Color trackColor,
required Color thumbColor,
required Color focusColor,
ValueChanged<bool>? onChanged,
required TextDirection textDirection,
required bool isFocused,
required _CupertinoSwitchState state,
}) : assert(value != null),
assert(activeColor != null),
assert(state != null),
_value = value,
_activeColor = activeColor,
_trackColor = trackColor,
_focusColor = focusColor,
_thumbPainter = CupertinoThumbPainter.switchThumb(color: thumbColor),
_onChanged = onChanged,
_textDirection = textDirection,
_isFocused = isFocused,
_state = state,
super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) {
state.position.addListener(markNeedsPaint);
Expand Down Expand Up @@ -490,6 +536,17 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
markNeedsPaint();
}

Color get focusColor => _focusColor;
Color _focusColor;
set focusColor(Color value) {
assert(value != null);
if (value == _focusColor) {
return;
}
_focusColor = value;
markNeedsPaint();
}

ValueChanged<bool>? get onChanged => _onChanged;
ValueChanged<bool>? _onChanged;
set onChanged(ValueChanged<bool>? value) {
Expand All @@ -515,6 +572,17 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
markNeedsPaint();
}

bool get isFocused => _isFocused;
bool _isFocused;
set isFocused(bool value) {
assert(value != null);
if(value == _isFocused) {
return;
}
_isFocused = value;
markNeedsPaint();
}

bool get isInteractive => onChanged != null;

@override
Expand Down Expand Up @@ -570,6 +638,18 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
final RRect trackRRect = RRect.fromRectAndRadius(trackRect, const Radius.circular(_kTrackRadius));
canvas.drawRRect(trackRRect, paint);

if(_isFocused) {
// Paints a border around the switch in the focus color.
final RRect borderTrackRRect = trackRRect.inflate(1.75);

final Paint borderPaint = Paint()
..color = focusColor
..style = PaintingStyle.stroke
..strokeWidth = 3.5;

canvas.drawRRect(borderTrackRRect, borderPaint);
}

final double currentThumbExtension = CupertinoThumbPainter.extension * currentReactionValue;
final double thumbLeft = lerpDouble(
trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius,
Expand Down
33 changes: 33 additions & 0 deletions packages/flutter/test/cupertino/switch_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,39 @@ void main() {
expect(value, isTrue);
});

testWidgets('CupertinoSwitch can be toggled by keyboard shortcuts', (WidgetTester tester) async {
bool value = true;
Widget buildApp({bool enabled = true}) {
return CupertinoApp(
home: CupertinoPageScaffold(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return CupertinoSwitch(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(value, isTrue);
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(value, isFalse);
await tester.sendKeyEvent(LogicalKeyboardKey.space);
await tester.pumpAndSettle();
expect(value, isTrue);
});

testWidgets('Switch emits light haptic vibration on tap', (WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
Expand Down