Unverified Commit ed70f4e2 authored by Qun Cheng's avatar Qun Cheng Committed by GitHub

Adaptive `Switch` (#130425)

Currently, `Switch.factory` delegates to `CupertinoSwitch` when platform
is iOS or macOS. This PR is to:
* have the factory configure the Material `Switch` for the expected look
and feel.
* introduce `Adaptation` class to customize themes for the adaptive
components.
parent a76720e9
......@@ -126,6 +126,12 @@ class _${blockName}DefaultsM3 extends SwitchThemeData {
});
}
@override
MaterialStateProperty<MouseCursor> get mouseCursor {
return MaterialStateProperty.resolveWith((Set<MaterialState> states)
=> MaterialStateMouseCursor.clickable.resolve(states));
}
@override
MaterialStatePropertyAll<double> get trackOutlineWidth => const MaterialStatePropertyAll<double>(${getToken('md.comp.switch.track.outline.width')});
......
// 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';
/// Flutter code sample for [Switch.adaptive].
void main() => runApp(const SwitchApp());
class SwitchApp extends StatefulWidget {
const SwitchApp({super.key});
@override
State<SwitchApp> createState() => _SwitchAppState();
}
class _SwitchAppState extends State<SwitchApp> {
bool isMaterial = true;
bool isCustomized = false;
@override
Widget build(BuildContext context) {
final ThemeData theme = ThemeData(
platform: isMaterial ? TargetPlatform.android : TargetPlatform.iOS,
adaptations: <Adaptation<Object>>[
if (isCustomized) const _SwitchThemeAdaptation()
]
);
final ButtonStyle style = OutlinedButton.styleFrom(
fixedSize: const Size(220, 40),
);
return MaterialApp(
theme: theme,
home: Scaffold(
appBar: AppBar(title: const Text('Adaptive Switches')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlinedButton(
style: style,
onPressed: () {
setState(() {
isMaterial = !isMaterial;
});
},
child: isMaterial ? const Text('Show cupertino style') : const Text('Show material style'),
),
OutlinedButton(
style: style,
onPressed: () {
setState(() {
isCustomized = !isCustomized;
});
},
child: isCustomized ? const Text('Remove customization') : const Text('Add customization'),
),
const SizedBox(height: 20),
const SwitchWithLabel(label: 'enabled', enabled: true),
const SwitchWithLabel(label: 'disabled', enabled: false),
],
),
),
);
}
}
class SwitchWithLabel extends StatefulWidget {
const SwitchWithLabel({
super.key,
required this.enabled,
required this.label,
});
final bool enabled;
final String label;
@override
State<SwitchWithLabel> createState() => _SwitchWithLabelState();
}
class _SwitchWithLabelState extends State<SwitchWithLabel> {
bool active = true;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 150,
padding: const EdgeInsets.only(right: 20),
child: Text(widget.label)
),
Switch.adaptive(
value: active,
onChanged: !widget.enabled ? null : (bool value) {
setState(() {
active = value;
});
},
),
],
);
}
}
class _SwitchThemeAdaptation extends Adaptation<SwitchThemeData> {
const _SwitchThemeAdaptation();
@override
SwitchThemeData adapt(ThemeData theme, SwitchThemeData defaultValue) {
switch (theme.platform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return defaultValue;
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return SwitchThemeData(
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return Colors.yellow;
}
return null; // Use the default.
}),
trackColor: const MaterialStatePropertyAll<Color>(Colors.brown),
);
}
}
}
// 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.4.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Show adaptive switch theme', (WidgetTester tester) async {
await tester.pumpWidget(
const example.SwitchApp(),
);
// Default is material style switches
expect(find.text('Show cupertino style'), findsOneWidget);
expect(find.text('Show material style'), findsNothing);
Finder adaptiveSwitch = find.byType(Switch).first;
expect(
adaptiveSwitch,
paints
..rrect(color: const Color(0xff6750a4)) // M3 primary color.
..rrect()
..rrect(color: Colors.white), // Thumb color
);
await tester.tap(find.widgetWithText(OutlinedButton, 'Add customization'));
await tester.pumpAndSettle();
// Theme adaptation does not affect material-style switch.
adaptiveSwitch = find.byType(Switch).first;
expect(
adaptiveSwitch,
paints
..rrect(color: const Color(0xff6750a4)) // M3 primary color.
..rrect()
..rrect(color: Colors.white), // Thumb color
);
await tester.tap(find.widgetWithText(OutlinedButton, 'Show cupertino style'));
await tester.pumpAndSettle();
expect(
adaptiveSwitch,
paints
..rrect(color: const Color(0xff795548)) // Customized track color only for cupertino.
..rrect()..rrect()..rrect()..rrect()
..rrect(color: const Color(0xffffeb3b)), // Customized thumb color only for cupertino.
);
await tester.tap(find.widgetWithText(OutlinedButton, 'Remove customization'));
await tester.pumpAndSettle();
expect(
adaptiveSwitch,
paints
..rrect(color: const Color(0xff34c759)) // Cupertino system green.
..rrect()..rrect()..rrect()..rrect()
..rrect(color: Colors.white), // Thumb color
);
});
}
......@@ -73,6 +73,37 @@ export 'package:flutter/services.dart' show Brightness;
// Examples can assume:
// late BuildContext context;
/// Defines a customized theme for components with an `adaptive` factory constructor.
///
/// Currently, only [Switch.adaptive] supports this class.
class Adaptation<T> {
/// Creates an [Adaptation].
const Adaptation();
/// The adaptation's type.
Type get type => T;
/// Typically, this is overridden to return an instance of a custom component
/// ThemeData class, like [SwitchThemeData], instead of the defaultValue.
///
/// Factory constructors that support adaptations - currently only
/// [Switch.adaptive] - look for a [ThemeData.adaptations] member of the expected
/// type when computing their effective default component theme. If a matching
/// adaptation is not found, the component may choose to use a default adaptation.
/// For example, the [Switch.adaptive] component uses an empty [SwitchThemeData]
/// if a matching adaptation is not found, for the sake of backwards compatibility.
///
/// {@tool dartpad}
/// This sample shows how to create and use subclasses of [Adaptation] that
/// define adaptive [SwitchThemeData]s. The [adapt] method in this example is
/// overridden to only customize cupertino-style switches, but it can also be
/// used to customize any other platforms.
///
/// ** See code in examples/api/lib/material/switch/switch.4.dart **
/// {@end-tool}
T adapt(ThemeData theme, T defaultValue) => defaultValue;
}
/// An interface that defines custom additions to a [ThemeData] object.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=8-szcYzFVao}
......@@ -241,6 +272,7 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
Iterable<Adaptation<Object>>? adaptations,
bool? applyElevationOverlayColor,
NoDefaultCupertinoThemeData? cupertinoOverrideTheme,
Iterable<ThemeExtension<dynamic>>? extensions,
......@@ -366,6 +398,7 @@ class ThemeData with Diagnosticable {
// GENERAL CONFIGURATION
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
extensions ??= <ThemeExtension<dynamic>>[];
adaptations ??= <Adaptation<Object>>[];
inputDecorationTheme ??= const InputDecorationTheme();
platform ??= defaultTargetPlatform;
switch (platform) {
......@@ -551,6 +584,7 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
adaptationMap: _createAdaptationMap(adaptations),
applyElevationOverlayColor: applyElevationOverlayColor,
cupertinoOverrideTheme: cupertinoOverrideTheme,
extensions: _themeExtensionIterableToMap(extensions),
......@@ -658,6 +692,7 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
required this.adaptationMap,
required this.applyElevationOverlayColor,
required this.cupertinoOverrideTheme,
required this.extensions,
......@@ -871,6 +906,19 @@ class ThemeData with Diagnosticable {
/// text geometry.
factory ThemeData.fallback({bool? useMaterial3}) => ThemeData.light(useMaterial3: useMaterial3);
/// Used to obtain a particular [Adaptation] from [adaptationMap].
///
/// To get an adaptation, use `Theme.of(context).getAdaptation<MyAdaptation>()`.
Adaptation<T>? getAdaptation<T>() => adaptationMap[T] as Adaptation<T>?;
static Map<Type, Adaptation<Object>> _createAdaptationMap(Iterable<Adaptation<Object>> adaptations) {
final Map<Type, Adaptation<Object>> adaptationMap = <Type, Adaptation<Object>>{
for (final Adaptation<Object> adaptation in adaptations)
adaptation.type: adaptation
};
return adaptationMap;
}
/// The overall theme brightness.
///
/// The default [TextStyle] color for the [textTheme] is black if the
......@@ -960,6 +1008,12 @@ class ThemeData with Diagnosticable {
/// See [extensions] for an interactive example.
T? extension<T>() => extensions[T] as T?;
/// A map which contains the adaptations for the theme. The entry's key is the
/// type of the adaptation; the value is the adaptation itself.
///
/// To obtain an adaptation, use [getAdaptation].
final Map<Type, Adaptation<Object>> adaptationMap;
/// The default [InputDecoration] values for [InputDecorator], [TextField],
/// and [TextFormField] are based on this theme.
///
......@@ -1480,6 +1534,7 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
Iterable<Adaptation<Object>>? adaptations,
bool? applyElevationOverlayColor,
NoDefaultCupertinoThemeData? cupertinoOverrideTheme,
Iterable<ThemeExtension<dynamic>>? extensions,
......@@ -1612,6 +1667,7 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
adaptationMap: adaptations != null ? _createAdaptationMap(adaptations) : adaptationMap,
applyElevationOverlayColor: applyElevationOverlayColor ?? this.applyElevationOverlayColor,
cupertinoOverrideTheme: cupertinoOverrideTheme ?? this.cupertinoOverrideTheme,
extensions: (extensions != null) ? _themeExtensionIterableToMap(extensions) : this.extensions,
......@@ -1812,6 +1868,7 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
adaptationMap: t < 0.5 ? a.adaptationMap : b.adaptationMap,
applyElevationOverlayColor:t < 0.5 ? a.applyElevationOverlayColor : b.applyElevationOverlayColor,
cupertinoOverrideTheme:t < 0.5 ? a.cupertinoOverrideTheme : b.cupertinoOverrideTheme,
extensions: _lerpThemeExtensions(a, b, t),
......@@ -1917,6 +1974,7 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
mapEquals(other.adaptationMap, adaptationMap) &&
other.applyElevationOverlayColor == applyElevationOverlayColor &&
other.cupertinoOverrideTheme == cupertinoOverrideTheme &&
mapEquals(other.extensions, extensions) &&
......@@ -2018,6 +2076,8 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
...adaptationMap.keys,
...adaptationMap.values,
applyElevationOverlayColor,
cupertinoOverrideTheme,
...extensions.keys,
......@@ -2123,6 +2183,7 @@ class ThemeData with Diagnosticable {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
properties.add(IterableProperty<Adaptation<dynamic>>('adaptations', adaptationMap.values, defaultValue: defaultData.adaptationMap.values, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<bool>('applyElevationOverlayColor', applyElevationOverlayColor, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<NoDefaultCupertinoThemeData>('cupertinoOverrideTheme', cupertinoOverrideTheme, defaultValue: defaultData.cupertinoOverrideTheme, level: DiagnosticLevel.debug));
properties.add(IterableProperty<ThemeExtension<dynamic>>('extensions', extensions.values, defaultValue: defaultData.extensions.values, level: DiagnosticLevel.debug));
......
......@@ -150,6 +150,7 @@ void main() {
find.byType(Switch),
paints
..rrect(color: Colors.blue[500])
..rrect()
..rrect(color: const Color(0x33000000))
..rrect(color: const Color(0x24000000))
..rrect(color: const Color(0x1f000000))
......@@ -163,6 +164,7 @@ void main() {
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(color: Colors.green[500])
..rrect()
..rrect(color: const Color(0x33000000))
..rrect(color: const Color(0x24000000))
..rrect(color: const Color(0x1f000000))
......@@ -221,7 +223,7 @@ void main() {
);
});
testWidgetsWithLeakTracking('SwitchListTile.adaptive delegates to', (WidgetTester tester) async {
testWidgetsWithLeakTracking('SwitchListTile.adaptive only uses material switch', (WidgetTester tester) async {
bool value = false;
Widget buildFrame(TargetPlatform platform) {
......@@ -246,23 +248,15 @@ void main() {
);
}
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS,
TargetPlatform.macOS, TargetPlatform.android, TargetPlatform.fuchsia,
TargetPlatform.linux, TargetPlatform.windows ]) {
value = false;
await tester.pumpWidget(buildFrame(platform));
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(value, isFalse, reason: 'on ${platform.name}');
await tester.tap(find.byType(SwitchListTile));
expect(value, isTrue, reason: 'on ${platform.name}');
}
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows ]) {
value = false;
await tester.pumpWidget(buildFrame(platform));
await tester.pumpAndSettle(); // Finish the theme change animation.
expect(find.byType(CupertinoSwitch), findsNothing);
expect(find.byType(Switch), findsOneWidget);
expect(value, isFalse, reason: 'on ${platform.name}');
await tester.tap(find.byType(SwitchListTile));
expect(value, isTrue, reason: 'on ${platform.name}');
}
......@@ -714,14 +708,14 @@ void main() {
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect()..rrect()..rrect()..rrect()..rrect(color: inactiveDisabledThumbColor)
paints..rrect()..rrect()..rrect()..rrect()..rrect()..rrect(color: inactiveDisabledThumbColor)
);
await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect()..rrect()..rrect()..rrect()..rrect(color: activeDisabledThumbColor)
paints..rrect()..rrect()..rrect()..rrect()..rrect()..rrect(color: activeDisabledThumbColor)
);
await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: false));
......@@ -729,7 +723,7 @@ void main() {
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect()..rrect()..rrect()..rrect()..rrect(color: inactiveEnabledThumbColor)
paints..rrect()..rrect()..rrect()..rrect()..rrect()..rrect(color: inactiveEnabledThumbColor)
);
await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: true));
......@@ -737,7 +731,7 @@ void main() {
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect()..rrect()..rrect()..rrect()..rrect(color: activeEnabledThumbColor)
paints..rrect()..rrect()..rrect()..rrect()..rrect()..rrect(color: activeEnabledThumbColor)
);
});
......@@ -853,7 +847,7 @@ void main() {
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect()..rrect()..rrect()..rrect()..rrect(color: hoveredThumbColor),
paints..rrect()..rrect()..rrect()..rrect()..rrect()..rrect(color: hoveredThumbColor),
);
// On pressed state
......@@ -861,7 +855,7 @@ void main() {
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect()..rrect()..rrect()..rrect()..rrect(color: pressedThumbColor),
paints..rrect()..rrect()..rrect()..rrect()..rrect()..rrect(color: pressedThumbColor),
);
});
......@@ -1188,7 +1182,7 @@ void main() {
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
await tester.pumpWidget(buildSwitchListTile(true, platform));
await tester.pumpAndSettle();
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(find.byType(Switch), findsOneWidget);
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: const Color(0xFF2196F3)),
......@@ -1196,7 +1190,7 @@ void main() {
await tester.pumpWidget(buildSwitchListTile(false, platform));
await tester.pumpAndSettle();
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(find.byType(Switch), findsOneWidget);
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: const Color(0xFF34C759)),
......@@ -1224,7 +1218,7 @@ void main() {
for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) {
await tester.pumpWidget(buildSwitchListTile(true, platform));
await tester.pumpAndSettle();
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(find.byType(Switch), findsOneWidget);
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: const Color(0xFF6750A4)),
......@@ -1232,7 +1226,7 @@ void main() {
await tester.pumpWidget(buildSwitchListTile(false, platform));
await tester.pumpAndSettle();
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(find.byType(Switch), findsOneWidget);
expect(
Material.of(tester.element(find.byType(Switch))),
paints..rrect(color: const Color(0xFF34C759)),
......
......@@ -717,6 +717,7 @@ void main() {
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: defaultThumbColor)
);
......@@ -730,6 +731,7 @@ void main() {
..rrect()
..rrect()
..rrect()
..rrect()
..rrect(color: selectedThumbColor)
);
});
......
......@@ -724,6 +724,7 @@ void main() {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
adaptationMap: const <Type, Adaptation<Object>>{},
applyElevationOverlayColor: false,
cupertinoOverrideTheme: null,
extensions: const <Object, ThemeExtension<dynamic>>{},
......@@ -836,6 +837,9 @@ void main() {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
adaptationMap: const <Type, Adaptation<Object>>{
SwitchThemeData: SwitchThemeAdaptation(),
},
applyElevationOverlayColor: true,
cupertinoOverrideTheme: ThemeData.light().cupertinoOverrideTheme,
extensions: const <Object, ThemeExtension<dynamic>>{
......@@ -941,6 +945,7 @@ void main() {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
adaptations: otherTheme.adaptationMap.values,
applyElevationOverlayColor: otherTheme.applyElevationOverlayColor,
cupertinoOverrideTheme: otherTheme.cupertinoOverrideTheme,
extensions: otherTheme.extensions.values,
......@@ -1041,6 +1046,7 @@ void main() {
// alphabetical by symbol name.
// GENERAL CONFIGURATION
expect(themeDataCopy.adaptationMap, equals(otherTheme.adaptationMap));
expect(themeDataCopy.applyElevationOverlayColor, equals(otherTheme.applyElevationOverlayColor));
expect(themeDataCopy.cupertinoOverrideTheme, equals(otherTheme.cupertinoOverrideTheme));
expect(themeDataCopy.extensions, equals(otherTheme.extensions));
......@@ -1178,6 +1184,7 @@ void main() {
// List of properties must match the properties in ThemeData.hashCode()
final Set<String> expectedPropertyNames = <String>{
// GENERAL CONFIGURATION
'adaptations',
'applyElevationOverlayColor',
'cupertinoOverrideTheme',
'extensions',
......@@ -1285,100 +1292,139 @@ void main() {
expect(propertyNames, expectedPropertyNames);
});
group('Theme adaptationMap', () {
const Key containerKey = Key('container');
testWidgetsWithLeakTracking('can be obtained', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
adaptations: const <Adaptation<Object>>[
StringAdaptation(),
SwitchThemeAdaptation()
],
),
home: Container(key: containerKey),
),
);
final ThemeData theme = Theme.of(
tester.element(find.byKey(containerKey)),
);
final String adaptiveString = theme.getAdaptation<String>()!.adapt(theme, 'Default theme');
final SwitchThemeData adaptiveSwitchTheme = theme.getAdaptation<SwitchThemeData>()!
.adapt(theme, theme.switchTheme);
expect(adaptiveString, 'Adaptive theme.');
expect(adaptiveSwitchTheme.thumbColor?.resolve(<MaterialState>{}),
isSameColorAs(Colors.brown));
});
testWidgetsWithLeakTracking('should return null on extension not found', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
adaptations: const <Adaptation<Object>>[
StringAdaptation(),
],
);
expect(theme.extension<SwitchThemeAdaptation>(), isNull);
});
});
testWidgetsWithLeakTracking(
'ThemeData.brightness not matching ColorScheme.brightness throws a helpful error message', (WidgetTester tester) async {
AssertionError? error;
// Test `ColorScheme.light()` and `ThemeData.brightness == Brightness.dark`.
try {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: const ColorScheme.light(),
brightness: Brightness.dark,
),
home: const Placeholder(),
AssertionError? error;
// Test `ColorScheme.light()` and `ThemeData.brightness == Brightness.dark`.
try {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: const ColorScheme.light(),
brightness: Brightness.dark,
),
);
} on AssertionError catch (e) {
error = e;
} finally {
expect(error, isNotNull);
expect(error?.message, contains(
'ThemeData.brightness does not match ColorScheme.brightness. '
home: const Placeholder(),
),
);
} on AssertionError catch (e) {
error = e;
} finally {
expect(error, isNotNull);
expect(error?.message, contains(
'ThemeData.brightness does not match ColorScheme.brightness. '
'Either override ColorScheme.brightness or ThemeData.brightness to '
'match the other.'
));
}
// Test `ColorScheme.dark()` and `ThemeData.brightness == Brightness.light`.
try {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: const ColorScheme.dark(),
brightness: Brightness.light,
),
home: const Placeholder(),
));
}
// Test `ColorScheme.dark()` and `ThemeData.brightness == Brightness.light`.
try {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: const ColorScheme.dark(),
brightness: Brightness.light,
),
);
} on AssertionError catch (e) {
error = e;
} finally {
expect(error, isNotNull);
expect(error?.message, contains(
'ThemeData.brightness does not match ColorScheme.brightness. '
home: const Placeholder(),
),
);
} on AssertionError catch (e) {
error = e;
} finally {
expect(error, isNotNull);
expect(error?.message, contains(
'ThemeData.brightness does not match ColorScheme.brightness. '
'Either override ColorScheme.brightness or ThemeData.brightness to '
'match the other.'
));
}
// Test `ColorScheme.fromSeed()` and `ThemeData.brightness == Brightness.dark`.
try {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xffff0000)),
brightness: Brightness.dark,
),
home: const Placeholder(),
));
}
// Test `ColorScheme.fromSeed()` and `ThemeData.brightness == Brightness.dark`.
try {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xffff0000)),
brightness: Brightness.dark,
),
);
} on AssertionError catch (e) {
error = e;
} finally {
expect(error, isNotNull);
expect(error?.message, contains(
'ThemeData.brightness does not match ColorScheme.brightness. '
home: const Placeholder(),
),
);
} on AssertionError catch (e) {
error = e;
} finally {
expect(error, isNotNull);
expect(error?.message, contains(
'ThemeData.brightness does not match ColorScheme.brightness. '
'Either override ColorScheme.brightness or ThemeData.brightness to '
'match the other.'
));
}
// Test `ColorScheme.fromSeed()` using `Brightness.dark` and `ThemeData.brightness == Brightness.light`.
try {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xffff0000),
brightness: Brightness.dark,
),
brightness: Brightness.light,
));
}
// Test `ColorScheme.fromSeed()` using `Brightness.dark` and `ThemeData.brightness == Brightness.light`.
try {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xffff0000),
brightness: Brightness.dark,
),
home: const Placeholder(),
brightness: Brightness.light,
),
);
} on AssertionError catch (e) {
error = e;
} finally {
expect(error, isNotNull);
expect(error?.message, contains(
'ThemeData.brightness does not match ColorScheme.brightness. '
home: const Placeholder(),
),
);
} on AssertionError catch (e) {
error = e;
} finally {
expect(error, isNotNull);
expect(error?.message, contains(
'ThemeData.brightness does not match ColorScheme.brightness. '
'Either override ColorScheme.brightness or ThemeData.brightness to '
'match the other.'
));
}
));
}
});
}
......@@ -1437,3 +1483,19 @@ class MyThemeExtensionB extends ThemeExtension<MyThemeExtensionB> {
);
}
}
class SwitchThemeAdaptation extends Adaptation<SwitchThemeData> {
const SwitchThemeAdaptation();
@override
SwitchThemeData adapt(ThemeData theme, SwitchThemeData defaultValue) => const SwitchThemeData(
thumbColor: MaterialStatePropertyAll<Color>(Colors.brown),
);
}
class StringAdaptation extends Adaptation<String> {
const StringAdaptation();
@override
String adapt(ThemeData theme, String defaultValue) => 'Adaptive theme.';
}
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