Unverified Commit 1e696d30 authored by Pierre-Louis's avatar Pierre-Louis Committed by GitHub

Support theming `CupertinoSwitch`s (#116510)

* Introduce flag to maximally apply CupertinoTheme

* add missing docs

* add tests

* fix docs

* fix test
parent 921f0776
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Flutter code sample for [Switch].
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(const SwitchApp());
class SwitchApp extends StatelessWidget {
const SwitchApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(useMaterial3: true).copyWith(
// Use the ambient [CupetinoThemeData] to style all widgets which would
// otherwise use iOS defaults.
cupertinoOverrideTheme: const CupertinoThemeData(applyThemeToAll: true),
),
home: Scaffold(
appBar: AppBar(title: const Text('Switch Sample')),
body: const Center(
child: SwitchExample(),
),
),
);
}
}
class SwitchExample extends StatefulWidget {
const SwitchExample({super.key});
@override
State<SwitchExample> createState() => _SwitchExampleState();
}
class _SwitchExampleState extends State<SwitchExample> {
bool light = true;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Switch.adaptive(
value: light,
onChanged: (bool value) {
setState(() {
light = value;
});
},
),
Switch.adaptive(
// Don't use the ambient [CupetinoThemeData] to style this switch.
applyCupertinoTheme: false,
value: light,
onChanged: (bool value) {
setState(() {
light = value;
});
},
),
],
);
}
}
......@@ -3,7 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/switch/switch.0.dart' as example;
import 'package:flutter_api_samples/material/switch/switch.1.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/switch/switch.3.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Can toggle switch', (WidgetTester tester) async {
await tester.pumpWidget(
const example.SwitchApp(),
);
final Finder switchFinder = find.byType(Switch).first;
Switch materialSwitch = tester.widget<Switch>(switchFinder);
expect(materialSwitch.value, true);
await tester.tap(switchFinder);
await tester.pumpAndSettle();
materialSwitch = tester.widget<Switch>(switchFinder);
expect(materialSwitch.value, false);
});
}
......@@ -14,6 +14,7 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'theme.dart';
import 'thumb_painter.dart';
// Examples can assume:
......@@ -72,6 +73,7 @@ class CupertinoSwitch extends StatefulWidget {
this.activeColor,
this.trackColor,
this.thumbColor,
this.applyTheme,
this.dragStartBehavior = DragStartBehavior.start,
}) : assert(value != null),
assert(dragStartBehavior != null);
......@@ -105,13 +107,15 @@ class CupertinoSwitch extends StatefulWidget {
/// ```
final ValueChanged<bool>? onChanged;
/// The color to use when this switch is on.
/// The color to use for the track when the switch is on.
///
/// Defaults to [CupertinoColors.systemGreen] when null and ignores
/// the [CupertinoTheme] in accordance to native iOS behavior.
/// If null and [applyTheme] is false, defaults to [CupertinoColors.systemGreen]
/// in accordance to native iOS behavior. Otherwise, defaults to
/// [CupertinoThemeData.primaryColor].
final Color? activeColor;
/// The color to use for the background when the switch is off.
/// The color to use for the track when the switch is off.
///
/// Defaults to [CupertinoColors.secondarySystemFill] when null.
final Color? trackColor;
......@@ -121,6 +125,16 @@ class CupertinoSwitch extends StatefulWidget {
/// Defaults to [CupertinoColors.white] when null.
final Color? thumbColor;
/// {@template flutter.cupertino.CupertinoSwitch.applyTheme}
/// Whether to apply the ambient [CupertinoThemeData].
///
/// If true, the track uses [CupertinoThemeData.primaryColor] for the track
/// when the switch is on.
///
/// Defaults to [CupertinoThemeData.applyThemeToAll].
/// {@endtemplate}
final bool? applyTheme;
/// {@template flutter.cupertino.CupertinoSwitch.dragStartBehavior}
/// Determines the way that drag start behavior is handled.
///
......@@ -310,6 +324,7 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
@override
Widget build(BuildContext context) {
final CupertinoThemeData theme = CupertinoTheme.of(context);
if (needsPositionAnimation) {
_resumePositionAnimation();
}
......@@ -320,7 +335,9 @@ class _CupertinoSwitchState extends State<CupertinoSwitch> with TickerProviderSt
child: _CupertinoSwitchRenderObjectWidget(
value: widget.value,
activeColor: CupertinoDynamicColor.resolve(
widget.activeColor ?? CupertinoColors.systemGreen,
widget.activeColor
?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null)
?? CupertinoColors.systemGreen,
context,
),
trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),
......
......@@ -22,6 +22,7 @@ const _CupertinoThemeDefaults _kDefaultTheme = _CupertinoThemeDefaults(
// Values extracted from navigation bar. For toolbar or tabbar the dark color is 0xF0161616.
),
CupertinoColors.systemBackground,
false,
_CupertinoTextThemeDefaults(CupertinoColors.label, CupertinoColors.inactiveGray),
);
......@@ -172,6 +173,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
bool? applyThemeToAll,
}) : this.raw(
brightness,
primaryColor,
......@@ -179,6 +181,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme,
barBackgroundColor,
scaffoldBackgroundColor,
applyThemeToAll,
);
/// Same as the default constructor but with positional arguments to avoid
......@@ -193,6 +196,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
bool? applyThemeToAll,
) : this._rawWithDefaults(
brightness,
primaryColor,
......@@ -200,6 +204,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme,
barBackgroundColor,
scaffoldBackgroundColor,
applyThemeToAll,
_kDefaultTheme,
);
......@@ -210,6 +215,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
bool? applyThemeToAll,
this._defaults,
) : super(
brightness: brightness,
......@@ -218,6 +224,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme: textTheme,
barBackgroundColor: barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor,
applyThemeToAll: applyThemeToAll,
);
final _CupertinoThemeDefaults _defaults;
......@@ -239,6 +246,9 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
@override
Color get scaffoldBackgroundColor => super.scaffoldBackgroundColor ?? _defaults.scaffoldBackgroundColor;
@override
bool get applyThemeToAll => super.applyThemeToAll ?? _defaults.applyThemeToAll;
@override
NoDefaultCupertinoThemeData noDefault() {
return NoDefaultCupertinoThemeData(
......@@ -248,6 +258,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme: super.textTheme,
barBackgroundColor: super.barBackgroundColor,
scaffoldBackgroundColor: super.scaffoldBackgroundColor,
applyThemeToAll: super.applyThemeToAll,
);
}
......@@ -262,6 +273,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
super.textTheme?.resolveFrom(context),
convertColor(super.barBackgroundColor),
convertColor(super.scaffoldBackgroundColor),
applyThemeToAll,
_defaults.resolveFrom(context, super.textTheme == null),
);
}
......@@ -274,6 +286,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
bool? applyThemeToAll,
}) {
return CupertinoThemeData._rawWithDefaults(
brightness ?? super.brightness,
......@@ -282,6 +295,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
textTheme ?? super.textTheme,
barBackgroundColor ?? super.barBackgroundColor,
scaffoldBackgroundColor ?? super.scaffoldBackgroundColor,
applyThemeToAll ?? super.applyThemeToAll,
_defaults,
);
}
......@@ -295,6 +309,7 @@ class CupertinoThemeData extends NoDefaultCupertinoThemeData with Diagnosticable
properties.add(createCupertinoColorProperty('primaryContrastingColor', primaryContrastingColor, defaultValue: defaultData.primaryContrastingColor));
properties.add(createCupertinoColorProperty('barBackgroundColor', barBackgroundColor, defaultValue: defaultData.barBackgroundColor));
properties.add(createCupertinoColorProperty('scaffoldBackgroundColor', scaffoldBackgroundColor, defaultValue: defaultData.scaffoldBackgroundColor));
properties.add(DiagnosticsProperty<bool>('applyThemeToAll', applyThemeToAll, defaultValue: defaultData.applyThemeToAll));
textTheme.debugFillProperties(properties);
}
}
......@@ -322,6 +337,7 @@ class NoDefaultCupertinoThemeData {
this.textTheme,
this.barBackgroundColor,
this.scaffoldBackgroundColor,
this.applyThemeToAll,
});
/// The brightness override for Cupertino descendants.
......@@ -389,6 +405,22 @@ class NoDefaultCupertinoThemeData {
/// Defaults to [CupertinoColors.systemBackground].
final Color? scaffoldBackgroundColor;
/// Flag to apply this theme to all descendant Cupertino widgets.
///
/// Certain Cupertino widgets previously didn't use theming, matching past
/// versions of iOS. For example, [CupertinoSwitch]s always used
/// [CupertinoColors.systemGreen] when active.
///
/// Today, however, these widgets can indeed be themed on iOS. Moreover on
/// macOS, the accent color is reflected in these widgets. Turning this flag
/// on ensures that descendant Cupertino widgets will be themed accordingly.
///
/// This flag currently applies to the following widgets:
/// - [CupertinoSwitch] & [Switch.adaptive]
///
/// Defaults to false.
final bool? applyThemeToAll;
/// Returns an instance of the theme data whose property getters only return
/// the construction time specifications with no derived values.
///
......@@ -412,6 +444,7 @@ class NoDefaultCupertinoThemeData {
textTheme: textTheme?.resolveFrom(context),
barBackgroundColor: convertColor(barBackgroundColor),
scaffoldBackgroundColor: convertColor(scaffoldBackgroundColor),
applyThemeToAll: applyThemeToAll,
);
}
......@@ -428,6 +461,7 @@ class NoDefaultCupertinoThemeData {
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor ,
Color? scaffoldBackgroundColor,
bool? applyThemeToAll,
}) {
return NoDefaultCupertinoThemeData(
brightness: brightness ?? this.brightness,
......@@ -436,6 +470,7 @@ class NoDefaultCupertinoThemeData {
textTheme: textTheme ?? this.textTheme,
barBackgroundColor: barBackgroundColor ?? this.barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor ?? this.scaffoldBackgroundColor,
applyThemeToAll: applyThemeToAll ?? this.applyThemeToAll,
);
}
}
......@@ -448,6 +483,7 @@ class _CupertinoThemeDefaults {
this.primaryContrastingColor,
this.barBackgroundColor,
this.scaffoldBackgroundColor,
this.applyThemeToAll,
this.textThemeDefaults,
);
......@@ -456,6 +492,7 @@ class _CupertinoThemeDefaults {
final Color primaryContrastingColor;
final Color barBackgroundColor;
final Color scaffoldBackgroundColor;
final bool applyThemeToAll;
final _CupertinoTextThemeDefaults textThemeDefaults;
_CupertinoThemeDefaults resolveFrom(BuildContext context, bool resolveTextTheme) {
......@@ -467,6 +504,7 @@ class _CupertinoThemeDefaults {
convertColor(primaryContrastingColor),
convertColor(barBackgroundColor),
convertColor(scaffoldBackgroundColor),
applyThemeToAll,
resolveTextTheme ? textThemeDefaults.resolveFrom(context) : textThemeDefaults,
);
}
......
......@@ -117,6 +117,7 @@ class Switch extends StatelessWidget {
this.onFocusChange,
this.autofocus = false,
}) : _switchType = _SwitchType.material,
applyCupertinoTheme = false,
assert(dragStartBehavior != null),
assert(activeThumbImage != null || onActiveThumbImageError == null),
assert(inactiveThumbImage != null || onInactiveThumbImageError == null);
......@@ -161,6 +162,7 @@ class Switch extends StatelessWidget {
this.focusNode,
this.onFocusChange,
this.autofocus = false,
this.applyCupertinoTheme,
}) : assert(autofocus != null),
assert(activeThumbImage != null || onActiveThumbImageError == null),
assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
......@@ -381,6 +383,9 @@ class Switch extends StatelessWidget {
final _SwitchType _switchType;
/// {@macro flutter.cupertino.CupertinoSwitch.applyTheme}
final bool? applyCupertinoTheme;
/// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
final DragStartBehavior dragStartBehavior;
......@@ -495,6 +500,7 @@ class Switch extends StatelessWidget {
onChanged: onChanged,
activeColor: activeColor,
trackColor: inactiveTrackColor,
applyTheme: applyCupertinoTheme,
),
),
);
......
......@@ -2703,6 +2703,7 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
_cupertinoOverrideTheme.textTheme,
_cupertinoOverrideTheme.barBackgroundColor,
_cupertinoOverrideTheme.scaffoldBackgroundColor,
_cupertinoOverrideTheme.applyThemeToAll,
);
final ThemeData _materialTheme;
......@@ -2738,6 +2739,7 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
CupertinoTextThemeData? textTheme,
Color? barBackgroundColor,
Color? scaffoldBackgroundColor,
bool? applyThemeToAll,
}) {
return MaterialBasedCupertinoThemeData._(
_materialTheme,
......@@ -2748,6 +2750,7 @@ class MaterialBasedCupertinoThemeData extends CupertinoThemeData {
textTheme: textTheme,
barBackgroundColor: barBackgroundColor,
scaffoldBackgroundColor: scaffoldBackgroundColor,
applyThemeToAll: applyThemeToAll,
),
);
}
......
......@@ -764,6 +764,66 @@ void main() {
);
});
testWidgets('Switch can apply the ambient theme and be opted out', (WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
CupertinoTheme(
data: const CupertinoThemeData(primaryColor: Colors.amber, applyThemeToAll: true),
child: Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Center(
child: RepaintBoundary(
child: Column(
children: <Widget>[
CupertinoSwitch(
key: switchKey,
value: value,
dragStartBehavior: DragStartBehavior.down,
applyTheme: true,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
CupertinoSwitch(
value: value,
dragStartBehavior: DragStartBehavior.down,
applyTheme: false,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
],
),
),
);
},
),
),
),
);
await expectLater(
find.byType(Column),
matchesGoldenFile('switch.tap.off.themed.png'),
);
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
await tester.pumpAndSettle();
await expectLater(
find.byType(Column),
matchesGoldenFile('switch.tap.on.themed.png'),
);
});
testWidgets('Hovering over Cupertino switch updates cursor to clickable on Web', (WidgetTester tester) async {
const bool value = false;
// Disabled CupertinoSwitch does not update cursor on Web.
......
......@@ -52,6 +52,7 @@ void main() {
expect(theme.brightness, isNull);
expect(theme.primaryColor, CupertinoColors.activeBlue);
expect(theme.textTheme.textStyle.fontSize, 17.0);
expect(theme.applyThemeToAll, false);
});
testWidgets('Theme attributes cascade', (WidgetTester tester) async {
......@@ -122,10 +123,12 @@ void main() {
(WidgetTester tester) async {
const CupertinoThemeData originalTheme = CupertinoThemeData(
brightness: Brightness.dark,
applyThemeToAll: true,
);
final CupertinoThemeData theme = await testTheme(tester, originalTheme.copyWith(
primaryColor: CupertinoColors.systemGreen,
applyThemeToAll: false,
));
expect(theme.brightness, Brightness.dark);
......@@ -133,6 +136,8 @@ void main() {
// Now check calculated derivatives.
expect(theme.textTheme.actionTextStyle.color, isSameColorAs(CupertinoColors.systemGreen.darkColor));
expect(theme.scaffoldBackgroundColor, isSameColorAs(CupertinoColors.black));
expect(theme.applyThemeToAll, false);
},
);
......@@ -181,6 +186,7 @@ void main() {
'primaryContrastingColor',
'barBackgroundColor',
'scaffoldBackgroundColor',
'applyThemeToAll',
'textStyle',
'actionTextStyle',
'tabLabelTextStyle',
......
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