mirror of
https://github.com/flutter/flutter.git
synced 2026-02-05 03:09:43 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
4530 lines
146 KiB
Dart
4530 lines
146 KiB
Dart
// 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.
|
|
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'dart:async';
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../widgets/semantics_tester.dart';
|
|
|
|
void main() {
|
|
final theme = ThemeData();
|
|
|
|
testWidgets('Switch can toggle on tap', (WidgetTester tester) async {
|
|
final Key switchKey = UniqueKey();
|
|
var value = false;
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
key: switchKey,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(value, isFalse);
|
|
await tester.tap(find.byKey(switchKey));
|
|
expect(value, isTrue);
|
|
});
|
|
|
|
testWidgets('Switch size is configurable by ThemeData.materialTapTargetSize', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final bool material3 = theme.useMaterial3;
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.padded),
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: true,
|
|
onChanged: (bool newValue) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// switch width = trackWidth - 2 * trackRadius + _kSwitchMinSize
|
|
// M2 width = 33 - 2 * 7 + 40
|
|
// M3 width = 52 - 2 * 16 + 40
|
|
expect(
|
|
tester.getSize(find.byType(Switch)),
|
|
material3 ? const Size(60.0, 48.0) : const Size(59.0, 48.0),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme.copyWith(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: true,
|
|
onChanged: (bool newValue) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSize(find.byType(Switch)),
|
|
material3 ? const Size(60.0, 40.0) : const Size(59.0, 40.0),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch does not get distorted upon changing constraints with parent', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const double maxWidth = 300;
|
|
const double maxHeight = 100;
|
|
|
|
const boundaryKey = ValueKey<String>('switch container');
|
|
|
|
Widget buildSwitch({required double width, required double height}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Scaffold(
|
|
body: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: SizedBox(
|
|
width: maxWidth,
|
|
height: maxHeight,
|
|
child: RepaintBoundary(
|
|
key: boundaryKey,
|
|
child: SizedBox(
|
|
width: width,
|
|
height: height,
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: true,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(width: maxWidth, height: maxHeight));
|
|
await expectLater(find.byKey(boundaryKey), matchesGoldenFile('m2_switch_test.big.on.png'));
|
|
|
|
await tester.pumpWidget(buildSwitch(width: 20, height: 10));
|
|
await expectLater(find.byKey(boundaryKey), matchesGoldenFile('m2_switch_test.small.on.png'));
|
|
});
|
|
|
|
testWidgets('Material3 - Switch does not get distorted upon changing constraints with parent', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const double maxWidth = 300;
|
|
const double maxHeight = 100;
|
|
|
|
const boundaryKey = ValueKey<String>('switch container');
|
|
|
|
Widget buildSwitch({required double width, required double height}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: SizedBox(
|
|
width: maxWidth,
|
|
height: maxHeight,
|
|
child: RepaintBoundary(
|
|
key: boundaryKey,
|
|
child: SizedBox(
|
|
width: width,
|
|
height: height,
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: true,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(width: maxWidth, height: maxHeight));
|
|
await expectLater(find.byKey(boundaryKey), matchesGoldenFile('m3_switch_test.big.on.png'));
|
|
|
|
await tester.pumpWidget(buildSwitch(width: 20, height: 10));
|
|
await expectLater(find.byKey(boundaryKey), matchesGoldenFile('m3_switch_test.small.on.png'));
|
|
});
|
|
|
|
testWidgets('Switch can drag (LTR)', (WidgetTester tester) async {
|
|
var value = false;
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(value, isFalse);
|
|
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
|
|
expect(value, isFalse);
|
|
|
|
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
|
|
|
|
expect(value, isTrue);
|
|
|
|
await tester.pump();
|
|
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
|
|
|
|
expect(value, isTrue);
|
|
|
|
await tester.pump();
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
|
|
expect(value, isFalse);
|
|
});
|
|
|
|
testWidgets('Switch can drag with dragStartBehavior', (WidgetTester tester) async {
|
|
var value = false;
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(value, isFalse);
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
expect(value, isFalse);
|
|
|
|
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
|
|
expect(value, isTrue);
|
|
await tester.pump();
|
|
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
|
|
expect(value, isTrue);
|
|
await tester.pump();
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
expect(value, isFalse);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final Rect switchRect = tester.getRect(find.byType(Switch));
|
|
|
|
TestGesture gesture = await tester.startGesture(switchRect.center);
|
|
// We have to execute the drag in two frames because the first update will
|
|
// just set the start position.
|
|
await gesture.moveBy(const Offset(20.0, 0.0));
|
|
await gesture.moveBy(const Offset(20.0, 0.0));
|
|
expect(value, isFalse);
|
|
await gesture.up();
|
|
expect(value, isTrue);
|
|
await tester.pump();
|
|
|
|
gesture = await tester.startGesture(switchRect.center);
|
|
await gesture.moveBy(const Offset(20.0, 0.0));
|
|
await gesture.moveBy(const Offset(20.0, 0.0));
|
|
expect(value, isTrue);
|
|
await gesture.up();
|
|
expect(value, isTrue);
|
|
await tester.pump();
|
|
|
|
gesture = await tester.startGesture(switchRect.center);
|
|
await gesture.moveBy(const Offset(-20.0, 0.0));
|
|
await gesture.moveBy(const Offset(-20.0, 0.0));
|
|
expect(value, isTrue);
|
|
await gesture.up();
|
|
expect(value, isFalse);
|
|
});
|
|
|
|
testWidgets('Switch can drag (RTL)', (WidgetTester tester) async {
|
|
var value = false;
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
|
|
|
|
expect(value, isFalse);
|
|
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
|
|
expect(value, isTrue);
|
|
|
|
await tester.pump();
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
|
|
expect(value, isTrue);
|
|
|
|
await tester.pump();
|
|
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
|
|
|
|
expect(value, isFalse);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch has default colors when enabled', (WidgetTester tester) async {
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x52000000), // Black with 32% opacity
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: Colors.grey.shade50),
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
await tester.pump();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x802196f3),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: const Color(0xff2196f3)),
|
|
reason: 'Active enabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Switch has default colors when enabled', (WidgetTester tester) async {
|
|
final theme = ThemeData();
|
|
final ColorScheme colors = theme.colorScheme;
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..save()
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.surfaceContainerHighest,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(
|
|
style: PaintingStyle.stroke,
|
|
color: colors.outline,
|
|
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(color: colors.outline), // thumb color
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
await tester.pump();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..save()
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.primary,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: colors.onPrimary), // thumb color
|
|
reason: 'Active enabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Switch.adaptive(Cupertino) has default colors when enabled', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData();
|
|
final ColorScheme colors = theme.colorScheme;
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch.adaptive(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..save()
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.surfaceContainerHighest,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(
|
|
style: PaintingStyle.stroke,
|
|
color: colors.outline,
|
|
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(color: colors.outline), // thumb color
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
await tester.pump();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..save()
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.primary,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: colors.onPrimary), // thumb color
|
|
reason: 'Active enabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch has default colors when disabled', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: const Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(child: Center(child: Switch(value: false, onChanged: null))),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: Colors.black12,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: Colors.grey.shade400),
|
|
reason: 'Inactive disabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: const Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(child: Center(child: Switch(value: true, onChanged: null))),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: Colors.black12,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: Colors.grey.shade400),
|
|
reason: 'Active disabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Inactive Switch has default colors when disabled', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
final ColorScheme colors = themeData.colorScheme;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: themeData,
|
|
home: const Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(child: Center(child: Switch(value: false, onChanged: null))),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..save()
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.surfaceContainerHighest.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(
|
|
style: PaintingStyle.stroke,
|
|
color: colors.onSurface.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(
|
|
color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface),
|
|
), // thumb color
|
|
reason: 'Inactive disabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Active Switch has default colors when disabled', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
final ColorScheme colors = themeData.colorScheme;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: themeData,
|
|
home: const Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(child: Center(child: Switch(value: true, onChanged: null))),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..save()
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.onSurface.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: colors.surface), // thumb color
|
|
reason: 'Active disabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch default overlayColor resolves hovered/focused state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
Finder findSwitch() {
|
|
return find.byWidgetPredicate((Widget widget) => widget is Switch);
|
|
}
|
|
|
|
MaterialInkController? getSwitchMaterial(WidgetTester tester) {
|
|
return Material.of(tester.element(findSwitch()));
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Scaffold(
|
|
body: Switch(focusNode: focusNode, value: true, onChanged: (_) {}),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Focused.
|
|
focusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getSwitchMaterial(tester), paints..circle(color: theme.focusColor));
|
|
|
|
// On both hovered and focused, the overlay color should show hovered overlay color.
|
|
final Offset center = tester.getCenter(find.byType(Switch));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getSwitchMaterial(tester), paints..circle(color: theme.hoverColor));
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Material3 - Switch default overlayColor resolves hovered/focused state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData();
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
|
|
Finder findSwitch() {
|
|
return find.byWidgetPredicate((Widget widget) => widget is Switch);
|
|
}
|
|
|
|
MaterialInkController? getSwitchMaterial(WidgetTester tester) {
|
|
return Material.of(tester.element(findSwitch()));
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Switch(focusNode: focusNode, value: true, onChanged: (_) {}),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Focused.
|
|
focusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
getSwitchMaterial(tester),
|
|
paints..circle(color: theme.colorScheme.primary.withOpacity(0.1)),
|
|
);
|
|
|
|
// On both hovered and focused, the overlay color should show hovered overlay color.
|
|
final Offset center = tester.getCenter(find.byType(Switch));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
getSwitchMaterial(tester),
|
|
paints..circle(color: theme.colorScheme.primary.withOpacity(0.08)),
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Material2 - Switch can be set color', (WidgetTester tester) async {
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
activeColor: Colors.red[500],
|
|
activeTrackColor: Colors.green[500],
|
|
inactiveThumbColor: Colors.yellow[500],
|
|
inactiveTrackColor: Colors.blue[500],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: Colors.blue[500],
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: Colors.yellow[500]),
|
|
);
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
await tester.pump();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: Colors.green[500],
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: Colors.red[500]),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Switch can be set color', (WidgetTester tester) async {
|
|
final themeData = ThemeData();
|
|
final ColorScheme colors = themeData.colorScheme;
|
|
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: themeData,
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
activeColor: Colors.red[500],
|
|
activeTrackColor: Colors.green[500],
|
|
inactiveThumbColor: Colors.yellow[500],
|
|
inactiveTrackColor: Colors.blue[500],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: Colors.blue[500],
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(style: PaintingStyle.stroke, color: colors.outline)
|
|
..rrect(color: Colors.yellow[500]), // thumb color
|
|
);
|
|
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
|
|
await tester.pump();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: Colors.green[500],
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: Colors.red[500]), // thumb color
|
|
);
|
|
});
|
|
|
|
testWidgets('Drag ends after animation completes', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/17773
|
|
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(value, isFalse);
|
|
|
|
final Rect switchRect = tester.getRect(find.byType(Switch));
|
|
final TestGesture gesture = await tester.startGesture(switchRect.centerLeft);
|
|
await tester.pump();
|
|
await gesture.moveBy(Offset(switchRect.width, 0.0));
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
|
|
expect(value, isTrue);
|
|
expect(tester.hasRunningAnimations, false);
|
|
});
|
|
|
|
testWidgets('can veto switch dragging result', (WidgetTester tester) async {
|
|
var value = false;
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = value || newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Move a little to the right, not past the middle.
|
|
TestGesture gesture = await tester.startGesture(tester.getRect(find.byType(Switch)).center);
|
|
await gesture.moveBy(const Offset(kTouchSlop + 0.1, 0.0));
|
|
await tester.pump();
|
|
await gesture.moveBy(const Offset(-kTouchSlop + 5.1, 0.0));
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pump();
|
|
expect(value, isFalse);
|
|
final ToggleableStateMixin state = tester.state<ToggleableStateMixin>(
|
|
find.descendant(
|
|
of: find.byType(Switch),
|
|
matching: find.byWidgetPredicate(
|
|
(Widget widget) => widget.runtimeType.toString() == '_MaterialSwitch',
|
|
),
|
|
),
|
|
);
|
|
expect(state.position.value, lessThan(0.5));
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(value, isFalse);
|
|
expect(state.position.value, 0);
|
|
|
|
// Move past the middle.
|
|
gesture = await tester.startGesture(tester.getRect(find.byType(Switch)).center);
|
|
await gesture.moveBy(const Offset(kTouchSlop + 0.1, 0.0));
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pump();
|
|
expect(value, isTrue);
|
|
expect(state.position.value, greaterThan(0.5));
|
|
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(value, isTrue);
|
|
expect(state.position.value, 1.0);
|
|
|
|
// Now move back to the left, the revert animation should play.
|
|
gesture = await tester.startGesture(tester.getRect(find.byType(Switch)).center);
|
|
await gesture.moveBy(const Offset(-kTouchSlop - 0.1, 0.0));
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pump();
|
|
expect(value, isTrue);
|
|
expect(state.position.value, lessThan(0.5));
|
|
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(value, isTrue);
|
|
expect(state.position.value, 1.0);
|
|
});
|
|
|
|
testWidgets('switch has semantic events', (WidgetTester tester) async {
|
|
dynamic semanticEvent;
|
|
var value = false;
|
|
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
|
|
SystemChannels.accessibility,
|
|
(dynamic message) async {
|
|
semanticEvent = message;
|
|
},
|
|
);
|
|
final semanticsTester = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.byType(Switch));
|
|
final RenderObject object = tester.firstRenderObject(find.byType(Switch));
|
|
|
|
expect(value, true);
|
|
expect(semanticEvent, <String, dynamic>{
|
|
'type': 'tap',
|
|
'nodeId': object.debugSemantics!.id,
|
|
'data': <String, dynamic>{},
|
|
});
|
|
expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true);
|
|
|
|
semanticsTester.dispose();
|
|
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
|
|
SystemChannels.accessibility,
|
|
null,
|
|
);
|
|
});
|
|
|
|
testWidgets('switch sends semantic events from parent if fully merged', (
|
|
WidgetTester tester,
|
|
) async {
|
|
dynamic semanticEvent;
|
|
var value = false;
|
|
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
|
|
SystemChannels.accessibility,
|
|
(dynamic message) async {
|
|
semanticEvent = message;
|
|
},
|
|
);
|
|
final semanticsTester = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
void onChanged(bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
|
|
return Material(
|
|
child: MergeSemantics(
|
|
child: ListTile(
|
|
leading: const Text('test'),
|
|
onTap: () {
|
|
onChanged(!value);
|
|
},
|
|
trailing: Switch(value: value, onChanged: onChanged),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.byType(MergeSemantics));
|
|
final RenderObject object = tester.firstRenderObject(find.byType(MergeSemantics));
|
|
|
|
expect(value, true);
|
|
expect(semanticEvent, <String, dynamic>{
|
|
'type': 'tap',
|
|
'nodeId': object.debugSemantics!.id,
|
|
'data': <String, dynamic>{},
|
|
});
|
|
expect(object.debugSemantics!.getSemanticsData().hasAction(SemanticsAction.tap), true);
|
|
|
|
semanticsTester.dispose();
|
|
tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(
|
|
SystemChannels.accessibility,
|
|
null,
|
|
);
|
|
});
|
|
|
|
testWidgets('Switch.adaptive', (WidgetTester tester) async {
|
|
var value = false;
|
|
const activeTrackColor = Color(0xffff1200);
|
|
const inactiveTrackColor = Color(0xffff12ff);
|
|
const thumbColor = Color(0xffffff00);
|
|
const focusColor = Color(0xff00ff00);
|
|
|
|
Widget buildFrame(TargetPlatform platform) {
|
|
return MaterialApp(
|
|
theme: ThemeData(platform: platform),
|
|
home: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch.adaptive(
|
|
value: value,
|
|
activeColor: activeTrackColor,
|
|
inactiveTrackColor: inactiveTrackColor,
|
|
thumbColor: const MaterialStatePropertyAll<Color?>(thumbColor),
|
|
focusColor: focusColor,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
|
value = false;
|
|
await tester.pumpWidget(buildFrame(platform));
|
|
expect(find.byType(Switch), findsOneWidget, reason: 'on ${platform.name}');
|
|
expect(find.byType(CupertinoSwitch), findsNothing);
|
|
|
|
final Switch adaptiveSwitch = tester.widget(find.byType(Switch));
|
|
expect(adaptiveSwitch.activeColor, activeTrackColor, reason: 'on ${platform.name}');
|
|
expect(adaptiveSwitch.inactiveTrackColor, inactiveTrackColor, reason: 'on ${platform.name}');
|
|
expect(
|
|
adaptiveSwitch.thumbColor?.resolve(<WidgetState>{}),
|
|
thumbColor,
|
|
reason: 'on ${platform.name}',
|
|
);
|
|
expect(adaptiveSwitch.focusColor, focusColor, reason: 'on ${platform.name}');
|
|
|
|
expect(value, isFalse, reason: 'on ${platform.name}');
|
|
await tester.tap(find.byType(Switch));
|
|
expect(value, isTrue, reason: 'on ${platform.name}');
|
|
}
|
|
|
|
for (final 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(value, isFalse, reason: 'on ${platform.name}');
|
|
await tester.tap(find.byType(Switch));
|
|
expect(value, isTrue, reason: 'on ${platform.name}');
|
|
}
|
|
});
|
|
|
|
testWidgets('Switch.adaptive default mouse cursor(Cupertino)', (WidgetTester tester) async {
|
|
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
|
await tester.pumpWidget(buildAdaptiveSwitch(platform: platform, value: false));
|
|
final Size switchSize = tester.getSize(find.byType(Switch));
|
|
expect(switchSize, const Size(60.0, 48.0));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
pointer: 1,
|
|
);
|
|
await gesture.addPointer(location: tester.getCenter(find.byType(Switch)));
|
|
addTearDown(gesture.removePointer);
|
|
await tester.pump();
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
|
);
|
|
|
|
await tester.pumpWidget(buildAdaptiveSwitch(platform: platform));
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
|
|
);
|
|
|
|
// Test disabled switch.
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(platform: platform, enabled: false, value: false),
|
|
);
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.basic,
|
|
);
|
|
await gesture.removePointer(location: tester.getCenter(find.byType(Switch)));
|
|
await tester.pump();
|
|
}
|
|
});
|
|
|
|
testWidgets('Switch.adaptive default thumb/track color and size(Cupertino)', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Color thumbColor = Colors.white;
|
|
const inactiveTrackColor = Color.fromARGB(40, 120, 120, 128); // Default inactive track color.
|
|
const activeTrackColor = Color.fromARGB(255, 52, 199, 89); // Default active track color.
|
|
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
|
// Switches have same sizes on both platform but they are more compact on macOS.
|
|
final trackRRect = platform == TargetPlatform.iOS
|
|
? RRect.fromLTRBR(4.5, 8.5, 55.5, 39.5, const Radius.circular(15.5))
|
|
: RRect.fromLTRBR(4.5, 4.5, 55.5, 35.5, const Radius.circular(15.5));
|
|
final inactiveThumbRRect = platform == TargetPlatform.iOS
|
|
? RRect.fromLTRBR(6.0, 10.0, 34.0, 38.0, const Radius.circular(14.0))
|
|
: RRect.fromLTRBR(6.0, 6.0, 34.0, 34.0, const Radius.circular(14.0));
|
|
final activeThumbRRect = platform == TargetPlatform.iOS
|
|
? RRect.fromLTRBR(26.0, 10.0, 54.0, 38.0, const Radius.circular(14.0))
|
|
: RRect.fromLTRBR(26.0, 6.0, 54.0, 34.0, const Radius.circular(14.0));
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(buildAdaptiveSwitch(platform: platform, value: false));
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: inactiveTrackColor,
|
|
rrect: trackRRect,
|
|
) // Default cupertino inactive track color
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: thumbColor, rrect: inactiveThumbRRect),
|
|
reason: 'Inactive enabled switch should have default track and thumb color',
|
|
);
|
|
expect(find.byType(Opacity), findsOneWidget);
|
|
expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 1.0);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(buildAdaptiveSwitch(platform: platform));
|
|
await tester.pump();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: activeTrackColor,
|
|
rrect: trackRRect,
|
|
) // Default cupertino active track color
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: thumbColor, rrect: activeThumbRRect),
|
|
reason: 'Active enabled switch should have default track and thumb color',
|
|
);
|
|
expect(find.byType(Opacity), findsOneWidget);
|
|
expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 1.0);
|
|
|
|
// Test disabled switch.
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(platform: platform, enabled: false, value: false),
|
|
);
|
|
await tester.pump();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: inactiveTrackColor,
|
|
rrect: trackRRect,
|
|
) // Default cupertino inactive track color
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: thumbColor, rrect: inactiveThumbRRect),
|
|
reason: 'Inactive disabled switch should have default track and thumb color',
|
|
);
|
|
expect(find.byType(Opacity), findsOneWidget);
|
|
expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 0.5);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(buildAdaptiveSwitch(platform: platform, enabled: false));
|
|
await tester.pump();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: activeTrackColor,
|
|
rrect: trackRRect,
|
|
) // Default cupertino active track color
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: thumbColor, rrect: activeThumbRRect),
|
|
reason: 'Active disabled switch should have default track and thumb color',
|
|
);
|
|
expect(find.byType(Opacity), findsOneWidget);
|
|
expect(tester.widget<Opacity>(find.byType(Opacity)).opacity, 0.5);
|
|
}
|
|
});
|
|
|
|
testWidgets('Default Switch.adaptive are not affected by '
|
|
'ThemeData.switchThemeData on iOS/macOS', (WidgetTester tester) async {
|
|
const Color defaultThumbColor = Colors.white;
|
|
const defaultInactiveTrackColor = Color.fromARGB(40, 120, 120, 128);
|
|
const defaultActiveTrackColor = Color.fromARGB(255, 52, 199, 89);
|
|
const Color updatedThumbColor = Colors.red;
|
|
const Color updatedTrackColor = Colors.green;
|
|
const overallSwitchTheme = SwitchThemeData(
|
|
thumbColor: MaterialStatePropertyAll<Color>(updatedThumbColor),
|
|
trackColor: MaterialStatePropertyAll<Color>(updatedTrackColor),
|
|
);
|
|
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(platform: platform, overallSwitchThemeData: overallSwitchTheme),
|
|
);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: defaultActiveTrackColor)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: defaultThumbColor),
|
|
reason: 'Active enabled switch should still have default track and thumb color',
|
|
);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(
|
|
platform: platform,
|
|
value: false,
|
|
overallSwitchThemeData: overallSwitchTheme,
|
|
),
|
|
);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: defaultInactiveTrackColor)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: defaultThumbColor),
|
|
reason: 'Inactive enabled switch should have default track and thumb color',
|
|
);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(
|
|
platform: platform,
|
|
enabled: false,
|
|
value: false,
|
|
overallSwitchThemeData: overallSwitchTheme,
|
|
),
|
|
);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: defaultInactiveTrackColor)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: defaultThumbColor),
|
|
reason: 'Inactive disabled switch should have default track and thumb color',
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(
|
|
platform: TargetPlatform.android,
|
|
overallSwitchThemeData: overallSwitchTheme,
|
|
),
|
|
);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: Color(updatedTrackColor.value))
|
|
..rrect()
|
|
..rrect(color: Color(updatedThumbColor.value)),
|
|
reason: 'Switch.adaptive is affected by SwitchTheme on other platforms',
|
|
);
|
|
});
|
|
|
|
testWidgets('Default Switch.adaptive are not affected by '
|
|
'SwitchThemeData on iOS/macOS', (WidgetTester tester) async {
|
|
const Color defaultThumbColor = Colors.white;
|
|
const defaultInactiveTrackColor = Color.fromARGB(40, 120, 120, 128);
|
|
const defaultActiveTrackColor = Color.fromARGB(255, 52, 199, 89);
|
|
const Color updatedThumbColor = Colors.red;
|
|
const Color updatedTrackColor = Colors.green;
|
|
const switchTheme = SwitchThemeData(
|
|
thumbColor: MaterialStatePropertyAll<Color>(updatedThumbColor),
|
|
trackColor: MaterialStatePropertyAll<Color>(updatedTrackColor),
|
|
);
|
|
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(platform: platform, switchThemeData: switchTheme),
|
|
);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: defaultActiveTrackColor)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: defaultThumbColor),
|
|
reason: 'Active enabled switch should still have default track and thumb color',
|
|
);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(platform: platform, value: false, switchThemeData: switchTheme),
|
|
);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: defaultInactiveTrackColor)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: defaultThumbColor),
|
|
reason: 'Inactive enabled switch should have default track and thumb color',
|
|
);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(
|
|
platform: platform,
|
|
enabled: false,
|
|
value: false,
|
|
switchThemeData: switchTheme,
|
|
),
|
|
);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: defaultInactiveTrackColor)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000)) // Thumb border color(only cupertino)
|
|
..rrect(color: defaultThumbColor),
|
|
reason: 'Inactive disabled switch should have default track and thumb color',
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(platform: TargetPlatform.android, switchThemeData: switchTheme),
|
|
);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: Color(updatedTrackColor.value))
|
|
..rrect()
|
|
..rrect(color: Color(updatedThumbColor.value)),
|
|
reason: 'Switch.adaptive is affected by SwitchTheme on other platforms',
|
|
);
|
|
});
|
|
|
|
testWidgets('Override default adaptive SwitchThemeData on iOS/macOS', (
|
|
WidgetTester tester,
|
|
) async {
|
|
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(
|
|
platform: platform,
|
|
switchThemeData: const SwitchThemeData(
|
|
thumbColor: MaterialStatePropertyAll<Color>(Colors.yellow),
|
|
trackColor: MaterialStatePropertyAll<Color>(Colors.brown),
|
|
),
|
|
switchThemeAdaptation: const _SwitchThemeAdaptation(),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: Color(Colors.deepPurple.value))
|
|
..rrect()
|
|
..rrect()
|
|
..rrect()
|
|
..rrect()
|
|
..rrect(color: Color(Colors.lightGreen.value)),
|
|
);
|
|
}
|
|
|
|
// Other platforms should not be affected by the adaptive switch theme.
|
|
for (final platform in <TargetPlatform>[
|
|
TargetPlatform.android,
|
|
TargetPlatform.fuchsia,
|
|
TargetPlatform.linux,
|
|
TargetPlatform.windows,
|
|
]) {
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(
|
|
platform: platform,
|
|
switchThemeData: const SwitchThemeData(
|
|
thumbColor: MaterialStatePropertyAll<Color>(Colors.yellow),
|
|
trackColor: MaterialStatePropertyAll<Color>(Colors.brown),
|
|
),
|
|
switchThemeAdaptation: const _SwitchThemeAdaptation(),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: Color(Colors.brown.value))
|
|
..rrect()
|
|
..rrect(color: Color(Colors.yellow.value)),
|
|
);
|
|
}
|
|
});
|
|
|
|
testWidgets('Switch.adaptive default focus color(Cupertino)', (WidgetTester tester) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
final node = FocusNode();
|
|
addTearDown(node.dispose);
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(platform: TargetPlatform.macOS, autofocus: true, focusNode: node),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(node.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: const Color(0xff34c759)) // Track color
|
|
..rrect()
|
|
..rrect(
|
|
color: const Color(0xcc6ef28f),
|
|
strokeWidth: 3.5,
|
|
style: PaintingStyle.stroke,
|
|
) // Focused outline
|
|
..rrect()
|
|
..rrect()
|
|
..rrect()
|
|
..rrect(color: const Color(0xffffffff)), // Thumb color
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
buildAdaptiveSwitch(
|
|
platform: TargetPlatform.macOS,
|
|
autofocus: true,
|
|
focusNode: node,
|
|
focusColor: Colors.red,
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(node.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: const Color(0xff34c759)) // Track color
|
|
..rrect()
|
|
..rrect(
|
|
color: Color(Colors.red.value),
|
|
strokeWidth: 3.5,
|
|
style: PaintingStyle.stroke,
|
|
) // Focused outline
|
|
..rrect()
|
|
..rrect()
|
|
..rrect()
|
|
..rrect(color: const Color(0xffffffff)), // Thumb color
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch is focusable and has correct focus color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
var value = true;
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Switch(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
focusColor: Colors.orange[500],
|
|
autofocus: true,
|
|
focusNode: focusNode,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x802196f3),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..circle(color: Colors.orange[500])
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: const Color(0xff2196f3)),
|
|
);
|
|
|
|
// Check the false value.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x52000000),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..circle(color: Colors.orange[500])
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: const Color(0xfffafafa)),
|
|
);
|
|
|
|
// Check what happens when disabled.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isFalse);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x1f000000),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: const Color(0xffbdbdbd)),
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Material3 - Switch is focusable and has correct focus color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
final ColorScheme colors = themeData.colorScheme;
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
var value = true;
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: themeData,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Switch(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
focusColor: Colors.orange[500],
|
|
autofocus: true,
|
|
focusNode: focusNode,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
|
|
// active, enabled switch
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.primary,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..circle(color: Colors.orange[500]),
|
|
);
|
|
|
|
// Check the false value: inactive enabled switch
|
|
value = false;
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.surfaceContainerHighest,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(
|
|
style: PaintingStyle.stroke,
|
|
color: colors.outline,
|
|
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
|
|
)
|
|
..circle(color: Colors.orange[500]),
|
|
);
|
|
|
|
// Check what happens when disabled: inactive disabled switch.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isFalse);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.surfaceContainerHighest.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(
|
|
style: PaintingStyle.stroke,
|
|
color: colors.onSurface.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)),
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Switch with splash radius set', (WidgetTester tester) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const double splashRadius = 30;
|
|
Widget buildApp() {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
value: true,
|
|
onChanged: (bool newValue) {},
|
|
focusColor: Colors.orange[500],
|
|
autofocus: true,
|
|
splashRadius: splashRadius,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..circle(color: Colors.orange[500], radius: splashRadius),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch can be hovered and has correct hover color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
var value = true;
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Switch(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
hoverColor: Colors.orange[500],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x802196f3),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: const Color(0xff2196f3)),
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x802196f3),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..circle(color: Colors.orange[500])
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: const Color(0xff2196f3)),
|
|
);
|
|
|
|
// Check what happens when disabled.
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x1f000000),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: const Color(0xffbdbdbd)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Switch can be hovered and has correct hover color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
final ColorScheme colors = themeData.colorScheme;
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
var value = true;
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: themeData,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Switch(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
hoverColor: Colors.orange[500],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// active enabled switch
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: colors.primary,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: colors.onPrimary),
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: colors.primary,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..circle(color: Colors.orange[500]),
|
|
);
|
|
|
|
// Check what happens for disabled active switch
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: colors.onSurface.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: colors.surface.withOpacity(1.0)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Switch can be toggled by keyboard shortcuts', (WidgetTester tester) async {
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
var value = true;
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Switch(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
focusColor: Colors.orange[500],
|
|
autofocus: true,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
|
await tester.pumpAndSettle();
|
|
// On web, switches don't respond to the enter key.
|
|
expect(value, kIsWeb ? isTrue : isFalse);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
|
await tester.pumpAndSettle();
|
|
expect(value, isTrue);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
|
await tester.pumpAndSettle();
|
|
expect(value, isFalse);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.space);
|
|
await tester.pumpAndSettle();
|
|
expect(value, isTrue);
|
|
});
|
|
|
|
testWidgets('Switch changes mouse cursor when hovered', (WidgetTester tester) async {
|
|
// Test Switch.adaptive() constructor
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.forbidden,
|
|
child: Switch.adaptive(
|
|
mouseCursor: SystemMouseCursors.text,
|
|
value: true,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
pointer: 1,
|
|
);
|
|
await gesture.addPointer(location: tester.getCenter(find.byType(Switch)));
|
|
addTearDown(gesture.removePointer);
|
|
|
|
await tester.pump();
|
|
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.text,
|
|
);
|
|
|
|
// Test Switch() constructor
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.forbidden,
|
|
child: Switch(mouseCursor: SystemMouseCursors.text, value: true, onChanged: (_) {}),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.text,
|
|
);
|
|
|
|
// Test default cursor
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.forbidden,
|
|
child: Switch(value: true, onChanged: (_) {}),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.click,
|
|
);
|
|
|
|
// Test default cursor when disabled
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: const Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: Material(
|
|
child: MouseRegion(
|
|
cursor: SystemMouseCursors.forbidden,
|
|
child: Switch(value: true, onChanged: null),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
|
|
SystemMouseCursors.basic,
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Material switch should not recreate its render object when disabled', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/61247.
|
|
var value = true;
|
|
var enabled = true;
|
|
late StateSetter stateSetter;
|
|
await tester.pumpWidget(
|
|
Theme(
|
|
data: theme,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
stateSetter = setState;
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
value: value,
|
|
onChanged: !enabled
|
|
? null
|
|
: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final ToggleableStateMixin oldSwitchState = tester.state(
|
|
find.byWidgetPredicate((Widget widget) => widget.runtimeType.toString() == '_MaterialSwitch'),
|
|
);
|
|
|
|
stateSetter(() {
|
|
value = false;
|
|
});
|
|
await tester.pump();
|
|
// Disable the switch when the implicit animation begins.
|
|
stateSetter(() {
|
|
enabled = false;
|
|
});
|
|
await tester.pump();
|
|
|
|
final ToggleableStateMixin updatedSwitchState = tester.state(
|
|
find.byWidgetPredicate((Widget widget) => widget.runtimeType.toString() == '_MaterialSwitch'),
|
|
);
|
|
|
|
expect(updatedSwitchState.isInteractive, false);
|
|
expect(updatedSwitchState, oldSwitchState);
|
|
expect(updatedSwitchState.position.isCompleted, false);
|
|
expect(updatedSwitchState.position.isDismissed, false);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch thumb color resolves in active/enabled states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const activeEnabledThumbColor = Color(0xFF000001);
|
|
const activeDisabledThumbColor = Color(0xFF000002);
|
|
const inactiveEnabledThumbColor = Color(0xFF000003);
|
|
const inactiveDisabledThumbColor = Color(0xFF000004);
|
|
|
|
Color getThumbColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeDisabledThumbColor;
|
|
}
|
|
return inactiveDisabledThumbColor;
|
|
}
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeEnabledThumbColor;
|
|
}
|
|
return inactiveEnabledThumbColor;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> thumbColor = WidgetStateColor.resolveWith(getThumbColor);
|
|
|
|
Widget buildSwitch({required bool enabled, required bool active}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
thumbColor: thumbColor,
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: Colors.black12,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: inactiveDisabledThumbColor),
|
|
reason: 'Inactive disabled switch should default track and custom thumb color',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: Colors.black12,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: activeDisabledThumbColor),
|
|
reason: 'Active disabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x52000000), // Black with 32% opacity,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: inactiveEnabledThumbColor),
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: Colors.black12,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: inactiveDisabledThumbColor),
|
|
reason: 'Inactive disabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Switch thumb color resolves in active/enabled states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
final ColorScheme colors = themeData.colorScheme;
|
|
const activeEnabledThumbColor = Color(0xFF000001);
|
|
const activeDisabledThumbColor = Color(0xFF000002);
|
|
const inactiveEnabledThumbColor = Color(0xFF000003);
|
|
const inactiveDisabledThumbColor = Color(0xFF000004);
|
|
|
|
Color getThumbColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeDisabledThumbColor;
|
|
}
|
|
return inactiveDisabledThumbColor;
|
|
}
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeEnabledThumbColor;
|
|
}
|
|
return inactiveEnabledThumbColor;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> thumbColor = WidgetStateColor.resolveWith(getThumbColor);
|
|
|
|
Widget buildSwitch({required bool enabled, required bool active}) {
|
|
return Theme(
|
|
data: themeData,
|
|
child: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
thumbColor: thumbColor,
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.surfaceContainerHighest.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(
|
|
style: PaintingStyle.stroke,
|
|
color: colors.onSurface.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect(color: inactiveDisabledThumbColor),
|
|
reason: 'Inactive disabled switch should default track and custom thumb color',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.onSurface.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: activeDisabledThumbColor),
|
|
reason: 'Active disabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.surfaceContainerHighest,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: inactiveEnabledThumbColor),
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.primary,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: activeEnabledThumbColor),
|
|
reason: 'Active enabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch thumb color resolves in hovered/focused states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const hoveredThumbColor = Color(0xFF000001);
|
|
const focusedThumbColor = Color(0xFF000002);
|
|
|
|
Color getThumbColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return hoveredThumbColor;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return focusedThumbColor;
|
|
}
|
|
return Colors.transparent;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> thumbColor = WidgetStateColor.resolveWith(getThumbColor);
|
|
|
|
Widget buildSwitch() {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
value: true,
|
|
thumbColor: thumbColor,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x802196f3),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..circle() // Radial reaction
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: focusedThumbColor),
|
|
reason: 'Inactive disabled switch should default track and custom thumb color',
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: const Color(0x802196f3),
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..circle()
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: hoveredThumbColor),
|
|
reason: 'Inactive disabled switch should default track and custom thumb color',
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Material3 - Switch thumb color resolves in hovered/focused states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
final ColorScheme colors = themeData.colorScheme;
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const hoveredThumbColor = Color(0xFF000001);
|
|
const focusedThumbColor = Color(0xFF000002);
|
|
|
|
Color getThumbColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return hoveredThumbColor;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return focusedThumbColor;
|
|
}
|
|
return Colors.transparent;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> thumbColor = WidgetStateColor.resolveWith(getThumbColor);
|
|
|
|
Widget buildSwitch() {
|
|
return MaterialApp(
|
|
theme: themeData,
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
value: true,
|
|
thumbColor: thumbColor,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.primary,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..circle(color: colors.primary.withOpacity(0.1))
|
|
..rrect(color: focusedThumbColor),
|
|
reason: 'active enabled switch should default track and custom thumb color',
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: colors.primary,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..circle(color: colors.primary.withOpacity(0.08))
|
|
..rrect(color: hoveredThumbColor),
|
|
reason: 'active enabled switch should default track and custom thumb color',
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Material2 - Track color resolves in active/enabled states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const activeEnabledTrackColor = Color(0xFF000001);
|
|
const activeDisabledTrackColor = Color(0xFF000002);
|
|
const inactiveEnabledTrackColor = Color(0xFF000003);
|
|
const inactiveDisabledTrackColor = Color(0xFF000004);
|
|
|
|
Color getTrackColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeDisabledTrackColor;
|
|
}
|
|
return inactiveDisabledTrackColor;
|
|
}
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeEnabledTrackColor;
|
|
}
|
|
return inactiveEnabledTrackColor;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> trackColor = WidgetStateColor.resolveWith(getTrackColor);
|
|
|
|
Widget buildSwitch({required bool enabled, required bool active}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
trackColor: trackColor,
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: inactiveDisabledTrackColor,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
),
|
|
reason: 'Inactive disabled switch track should use this value',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: activeDisabledTrackColor,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
),
|
|
reason: 'Active disabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: inactiveEnabledTrackColor,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
),
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: inactiveDisabledTrackColor,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
),
|
|
reason: 'Inactive disabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Track color resolves in active/enabled states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
const activeEnabledTrackColor = Color(0xFF000001);
|
|
const activeDisabledTrackColor = Color(0xFF000002);
|
|
const inactiveEnabledTrackColor = Color(0xFF000003);
|
|
const inactiveDisabledTrackColor = Color(0xFF000004);
|
|
|
|
Color getTrackColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeDisabledTrackColor;
|
|
}
|
|
return inactiveDisabledTrackColor;
|
|
}
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeEnabledTrackColor;
|
|
}
|
|
return inactiveEnabledTrackColor;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> trackColor = WidgetStateColor.resolveWith(getTrackColor);
|
|
|
|
Widget buildSwitch({required bool enabled, required bool active}) {
|
|
return Theme(
|
|
data: themeData,
|
|
child: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
trackColor: trackColor,
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: inactiveDisabledTrackColor,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
),
|
|
reason: 'Inactive disabled switch track should use this value',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: activeDisabledTrackColor,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
),
|
|
reason: 'Active disabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: inactiveEnabledTrackColor,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
),
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: activeEnabledTrackColor,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
),
|
|
reason: 'Active enabled switch should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch track color resolves in hovered/focused states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const hoveredTrackColor = Color(0xFF000001);
|
|
const focusedTrackColor = Color(0xFF000002);
|
|
|
|
Color getTrackColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return hoveredTrackColor;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return focusedTrackColor;
|
|
}
|
|
return Colors.transparent;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> trackColor = WidgetStateColor.resolveWith(getTrackColor);
|
|
|
|
Widget buildSwitch() {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
value: true,
|
|
trackColor: trackColor,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: focusedTrackColor,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
),
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: hoveredTrackColor,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
),
|
|
reason: 'Inactive enabled switch should match these colors',
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Material3 - Switch track color resolves in hovered/focused states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final themeData = ThemeData();
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const hoveredTrackColor = Color(0xFF000001);
|
|
const focusedTrackColor = Color(0xFF000002);
|
|
|
|
Color getTrackColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return hoveredTrackColor;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return focusedTrackColor;
|
|
}
|
|
return Colors.transparent;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> trackColor = WidgetStateColor.resolveWith(getTrackColor);
|
|
|
|
Widget buildSwitch() {
|
|
return Theme(
|
|
data: themeData,
|
|
child: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
value: true,
|
|
trackColor: trackColor,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: focusedTrackColor,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
),
|
|
reason: 'Active enabled switch should match these colors',
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints..rrect(
|
|
color: hoveredTrackColor,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
),
|
|
reason: 'Active enabled switch should match these colors',
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Material2 - Switch thumb color is blended against surface color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60);
|
|
final theme = ThemeData.light(useMaterial3: false);
|
|
|
|
Color getThumbColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
return activeDisabledThumbColor;
|
|
}
|
|
return Colors.black;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> thumbColor = WidgetStateColor.resolveWith(getThumbColor);
|
|
|
|
Widget buildSwitch({required bool enabled, required bool active}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
thumbColor: thumbColor,
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
|
|
|
|
final Color expectedThumbColor = Color.alphaBlend(
|
|
activeDisabledThumbColor,
|
|
theme.colorScheme.surface,
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: Colors.black12,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: expectedThumbColor),
|
|
reason: 'Active disabled thumb color should be blended on top of surface color',
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Switch thumb color is blended against surface color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60);
|
|
final theme = ThemeData();
|
|
final ColorScheme colors = theme.colorScheme;
|
|
|
|
Color getThumbColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
return activeDisabledThumbColor;
|
|
}
|
|
return Colors.black;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> thumbColor = WidgetStateColor.resolveWith(getThumbColor);
|
|
|
|
Widget buildSwitch({required bool enabled, required bool active}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Theme(
|
|
data: theme,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
thumbColor: thumbColor,
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
|
|
|
|
final Color expectedThumbColor = Color.alphaBlend(
|
|
activeDisabledThumbColor,
|
|
theme.colorScheme.surface,
|
|
);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: colors.onSurface.withOpacity(0.12),
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: expectedThumbColor),
|
|
reason: 'Active disabled thumb color should be blended on top of surface color',
|
|
);
|
|
});
|
|
|
|
testWidgets('Switch overlay color resolves in active/pressed/focused/hovered states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
|
|
const activeThumbColor = Color(0xFF000000);
|
|
const inactiveThumbColor = Color(0xFF000010);
|
|
const activePressedOverlayColor = Color(0xFF000001);
|
|
const inactivePressedOverlayColor = Color(0xFF000002);
|
|
const hoverOverlayColor = Color(0xFF000003);
|
|
const focusOverlayColor = Color(0xFF000004);
|
|
const hoverColor = Color(0xFF000005);
|
|
const focusColor = Color(0xFF000006);
|
|
|
|
Color? getOverlayColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.pressed)) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activePressedOverlayColor;
|
|
}
|
|
return inactivePressedOverlayColor;
|
|
}
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return hoverOverlayColor;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return focusOverlayColor;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const splashRadius = 24.0;
|
|
|
|
Widget buildSwitch({bool active = false, bool focused = false, bool useOverlay = true}) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Switch(
|
|
focusNode: focusNode,
|
|
autofocus: focused,
|
|
value: active,
|
|
onChanged: (_) {},
|
|
thumbColor: WidgetStateProperty.resolveWith<Color>((Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeThumbColor;
|
|
}
|
|
return inactiveThumbColor;
|
|
}),
|
|
overlayColor: useOverlay ? WidgetStateProperty.resolveWith(getOverlayColor) : null,
|
|
hoverColor: hoverColor,
|
|
focusColor: focusColor,
|
|
splashRadius: splashRadius,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// test inactive Switch, and overlayColor is set to null.
|
|
await tester.pumpWidget(buildSwitch(useOverlay: false));
|
|
await tester.press(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..circle(color: inactiveThumbColor.withAlpha(kRadialReactionAlpha), radius: splashRadius),
|
|
reason: 'Default inactive pressed Switch should have overlay color from thumbColor',
|
|
);
|
|
|
|
// test active Switch, and overlayColor is set to null.
|
|
await tester.pumpWidget(buildSwitch(active: true, useOverlay: false));
|
|
await tester.press(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..circle(color: activeThumbColor.withAlpha(kRadialReactionAlpha), radius: splashRadius),
|
|
reason: 'Default active pressed Switch should have overlay color from thumbColor',
|
|
);
|
|
|
|
// test inactive Switch with an overlayColor
|
|
await tester.pumpWidget(buildSwitch());
|
|
await tester.press(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..circle(color: inactivePressedOverlayColor, radius: splashRadius),
|
|
reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor',
|
|
);
|
|
|
|
// test active Switch with an overlayColor
|
|
await tester.pumpWidget(buildSwitch(active: true));
|
|
await tester.press(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..circle(color: activePressedOverlayColor, radius: splashRadius),
|
|
reason: 'Active pressed Switch should have overlay color: $activePressedOverlayColor',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(focused: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..circle(color: focusOverlayColor, radius: splashRadius),
|
|
reason: 'Focused Switch should use overlay color $focusOverlayColor over $focusColor',
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..circle(color: hoverOverlayColor, radius: splashRadius),
|
|
reason: 'Hovered Switch should use overlay color $hoverOverlayColor over $hoverColor',
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Do not crash when widget disappears while pointer is down', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildSwitch(bool show) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(child: show ? Switch(value: true, onChanged: (_) {}) : Container()),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(true));
|
|
final Offset center = tester.getCenter(find.byType(Switch));
|
|
// Put a pointer down on the screen.
|
|
final TestGesture gesture = await tester.startGesture(center);
|
|
await tester.pump();
|
|
// While the pointer is down, the widget disappears.
|
|
await tester.pumpWidget(buildSwitch(false));
|
|
expect(find.byType(Switch), findsNothing);
|
|
// Release pointer after widget disappeared.
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('disabled switch shows tooltip', (WidgetTester tester) async {
|
|
const longPressTooltip = 'long press tooltip';
|
|
const tapTooltip = 'tap tooltip';
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Material(
|
|
child: Tooltip(message: longPressTooltip, child: Switch(onChanged: null, value: true)),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Default tooltip shows up after long pressed.
|
|
final Finder tooltip0 = find.byType(Tooltip);
|
|
expect(find.text(longPressTooltip), findsNothing);
|
|
|
|
await tester.tap(tooltip0);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(longPressTooltip), findsNothing);
|
|
|
|
final TestGesture gestureLongPress = await tester.startGesture(tester.getCenter(tooltip0));
|
|
await tester.pump();
|
|
await tester.pump(kLongPressTimeout);
|
|
await gestureLongPress.up();
|
|
await tester.pump();
|
|
|
|
expect(find.text(longPressTooltip), findsOneWidget);
|
|
|
|
// Tooltip shows up after tapping when set triggerMode to TooltipTriggerMode.tap.
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Material(
|
|
child: Tooltip(
|
|
triggerMode: TooltipTriggerMode.tap,
|
|
message: tapTooltip,
|
|
child: Switch(onChanged: null, value: true),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pump(const Duration(days: 1));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text(tapTooltip), findsNothing);
|
|
expect(find.text(longPressTooltip), findsNothing);
|
|
|
|
final Finder tooltip1 = find.byType(Tooltip);
|
|
await tester.tap(tooltip1);
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text(tapTooltip), findsOneWidget);
|
|
});
|
|
|
|
group('with image', () {
|
|
late ui.Image image;
|
|
|
|
setUp(() async {
|
|
image = await createTestImage(width: 100, height: 100);
|
|
});
|
|
|
|
testWidgets('thumb image shows up', (WidgetTester tester) async {
|
|
imageCache.clear();
|
|
final provider1 = _TestImageProvider();
|
|
final provider2 = _TestImageProvider();
|
|
|
|
expect(provider1.loadCallCount, 0);
|
|
expect(provider2.loadCallCount, 0);
|
|
|
|
var value1 = true;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(platform: TargetPlatform.android),
|
|
home: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Switch(
|
|
activeThumbImage: provider1,
|
|
inactiveThumbImage: provider2,
|
|
value: value1,
|
|
onChanged: (bool val) {
|
|
setState(() {
|
|
value1 = val;
|
|
});
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(provider1.loadCallCount, 1);
|
|
expect(provider2.loadCallCount, 0);
|
|
expect(imageCache.liveImageCount, 1);
|
|
await tester.tap(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
expect(provider1.loadCallCount, 1);
|
|
expect(provider2.loadCallCount, 1);
|
|
expect(imageCache.liveImageCount, 2);
|
|
});
|
|
|
|
testWidgets('do not crash when imageProvider completes after Switch is disposed', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final imageProvider = DelayedImageProvider(image);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(value: true, onChanged: null, inactiveThumbImage: imageProvider),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(Switch), findsOneWidget);
|
|
|
|
// Dispose the switch by taking down the tree.
|
|
await tester.pumpWidget(Container());
|
|
expect(find.byType(Switch), findsNothing);
|
|
|
|
imageProvider.complete();
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('do not crash when previous imageProvider completes after Switch is disposed', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final imageProvider1 = DelayedImageProvider(image);
|
|
final imageProvider2 = DelayedImageProvider(image);
|
|
|
|
Future<void> buildSwitch(ImageProvider imageProvider) {
|
|
return tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(value: true, onChanged: null, inactiveThumbImage: imageProvider),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await buildSwitch(imageProvider1);
|
|
expect(find.byType(Switch), findsOneWidget);
|
|
// Replace the ImageProvider.
|
|
await buildSwitch(imageProvider2);
|
|
expect(find.byType(Switch), findsOneWidget);
|
|
|
|
// Dispose the switch by taking down the tree.
|
|
await tester.pumpWidget(Container());
|
|
expect(find.byType(Switch), findsNothing);
|
|
|
|
// Completing the replaced ImageProvider shouldn't crash.
|
|
imageProvider1.complete();
|
|
expect(tester.takeException(), isNull);
|
|
|
|
imageProvider2.complete();
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
});
|
|
|
|
group('Switch M3 only tests', () {
|
|
testWidgets('M3 Switch has a 300-millisecond animation in total', (WidgetTester tester) async {
|
|
final theme = ThemeData();
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(value, isFalse);
|
|
|
|
final Rect switchRect = tester.getRect(find.byType(Switch));
|
|
final TestGesture gesture = await tester.startGesture(switchRect.centerLeft);
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 200)); // M2 animation duration
|
|
expect(tester.hasRunningAnimations, true);
|
|
await tester.pump(const Duration(milliseconds: 101));
|
|
expect(tester.hasRunningAnimations, false);
|
|
});
|
|
|
|
testWidgets('M3 Switch has a stadium shape in the middle of the track', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(colorSchemeSeed: Colors.deepPurple);
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(value, isFalse);
|
|
|
|
final Rect switchRect = tester.getRect(find.byType(Switch));
|
|
final TestGesture gesture = await tester.startGesture(switchRect.centerLeft);
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pump();
|
|
// After 33 milliseconds, the switch thumb moves to the middle
|
|
// and has a stadium shape with a size of (34x22).
|
|
await tester.pump(const Duration(milliseconds: 33));
|
|
expect(tester.hasRunningAnimations, true);
|
|
|
|
await expectLater(find.byType(Switch), matchesGoldenFile('switch_test.m3.transition.png'));
|
|
});
|
|
|
|
testWidgets('M3 Switch thumb bounces in the end of the animation', (WidgetTester tester) async {
|
|
final theme = ThemeData();
|
|
var value = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
value: value,
|
|
onChanged: (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
},
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(value, isFalse);
|
|
|
|
final Rect switchRect = tester.getRect(find.byType(Switch));
|
|
final TestGesture gesture = await tester.startGesture(switchRect.centerLeft);
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pump();
|
|
// The value on y axis is greater than 1 when t > 0.375
|
|
// 300 * 0.375 = 112.5
|
|
await tester.pump(const Duration(milliseconds: 113));
|
|
final ToggleableStateMixin state = tester.state<ToggleableStateMixin>(
|
|
find.descendant(
|
|
of: find.byType(Switch),
|
|
matching: find.byWidgetPredicate(
|
|
(Widget widget) => widget.runtimeType.toString() == '_MaterialSwitch',
|
|
),
|
|
),
|
|
);
|
|
expect(tester.hasRunningAnimations, true);
|
|
expect(state.position.value, greaterThan(1));
|
|
});
|
|
|
|
testWidgets('Switch thumb shows correct pressed color - M3', (WidgetTester tester) async {
|
|
final themeData = ThemeData();
|
|
final ColorScheme colors = themeData.colorScheme;
|
|
Widget buildApp({bool enabled = true, bool value = true}) {
|
|
return MaterialApp(
|
|
theme: themeData,
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Switch(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.press(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: colors.primary, // track color
|
|
style: PaintingStyle.fill,
|
|
)
|
|
..rrect(
|
|
color: Colors.transparent, // track outline color
|
|
style: PaintingStyle.stroke,
|
|
)
|
|
..rrect(
|
|
color: colors.primaryContainer,
|
|
rrect: RRect.fromLTRBR(26.0, 10.0, 54.0, 38.0, const Radius.circular(14.0)),
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(buildApp(value: false));
|
|
await tester.press(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: colors.surfaceContainerHighest, // track color
|
|
style: PaintingStyle.fill,
|
|
)
|
|
..rrect(
|
|
color: colors.outline, // track outline color
|
|
style: PaintingStyle.stroke,
|
|
)
|
|
..rrect(color: colors.onSurfaceVariant),
|
|
);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.press(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: colors.onSurface.withOpacity(0.12), // track color
|
|
style: PaintingStyle.fill,
|
|
)
|
|
..rrect(
|
|
color: Colors.transparent, // track outline color
|
|
style: PaintingStyle.stroke,
|
|
)
|
|
..rrect(color: colors.surface.withOpacity(1.0)),
|
|
);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpWidget(buildApp(enabled: false, value: false));
|
|
await tester.press(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: colors.surfaceContainerHighest.withOpacity(0.12), // track color
|
|
style: PaintingStyle.fill,
|
|
)
|
|
..rrect(
|
|
color: colors.onSurface.withOpacity(0.12), // track outline color
|
|
style: PaintingStyle.stroke,
|
|
)
|
|
..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)),
|
|
);
|
|
}, variant: TargetPlatformVariant.mobile());
|
|
|
|
testWidgets('Track outline color resolves in active/enabled states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const activeEnabledTrackOutlineColor = Color(0xFF000001);
|
|
const activeDisabledTrackOutlineColor = Color(0xFF000002);
|
|
const inactiveEnabledTrackOutlineColor = Color(0xFF000003);
|
|
const inactiveDisabledTrackOutlineColor = Color(0xFF000004);
|
|
|
|
Color getTrackOutlineColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeDisabledTrackOutlineColor;
|
|
}
|
|
return inactiveDisabledTrackOutlineColor;
|
|
}
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeEnabledTrackOutlineColor;
|
|
}
|
|
return inactiveEnabledTrackOutlineColor;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> trackOutlineColor = WidgetStateColor.resolveWith(
|
|
getTrackOutlineColor,
|
|
);
|
|
|
|
Widget buildSwitch({required bool enabled, required bool active}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
trackOutlineColor: trackOutlineColor,
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(color: inactiveDisabledTrackOutlineColor, style: PaintingStyle.stroke),
|
|
reason: 'Inactive disabled switch track outline should use this value',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(color: activeDisabledTrackOutlineColor, style: PaintingStyle.stroke),
|
|
reason: 'Active disabled switch track outline should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(color: inactiveEnabledTrackOutlineColor),
|
|
reason: 'Inactive enabled switch track outline should match these colors',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(color: activeEnabledTrackOutlineColor),
|
|
reason: 'Active enabled switch track outline should match these colors',
|
|
);
|
|
});
|
|
|
|
testWidgets('Switch track outline color resolves in hovered/focused states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const hoveredTrackOutlineColor = Color(0xFF000001);
|
|
const focusedTrackOutlineColor = Color(0xFF000002);
|
|
|
|
Color getTrackOutlineColor(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return hoveredTrackOutlineColor;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return focusedTrackOutlineColor;
|
|
}
|
|
return Colors.transparent;
|
|
}
|
|
|
|
final WidgetStateProperty<Color> trackOutlineColor = WidgetStateColor.resolveWith(
|
|
getTrackOutlineColor,
|
|
);
|
|
|
|
Widget buildSwitch() {
|
|
return Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
value: true,
|
|
trackOutlineColor: trackOutlineColor,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(color: focusedTrackOutlineColor, style: PaintingStyle.stroke),
|
|
reason: 'Active enabled switch track outline should match this color',
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(color: hoveredTrackOutlineColor, style: PaintingStyle.stroke),
|
|
reason: 'Active enabled switch track outline should match this color',
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Track outline width resolves in active/enabled states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const activeEnabledTrackOutlineWidth = 1.0;
|
|
const activeDisabledTrackOutlineWidth = 2.0;
|
|
const inactiveEnabledTrackOutlineWidth = 3.0;
|
|
const inactiveDisabledTrackOutlineWidth = 4.0;
|
|
|
|
double getTrackOutlineWidth(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.disabled)) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeDisabledTrackOutlineWidth;
|
|
}
|
|
return inactiveDisabledTrackOutlineWidth;
|
|
}
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeEnabledTrackOutlineWidth;
|
|
}
|
|
return inactiveEnabledTrackOutlineWidth;
|
|
}
|
|
|
|
final WidgetStateProperty<double> trackOutlineWidth = WidgetStateProperty.resolveWith(
|
|
getTrackOutlineWidth,
|
|
);
|
|
|
|
Widget buildSwitch({required bool enabled, required bool active}) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
trackOutlineWidth: trackOutlineWidth,
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(strokeWidth: inactiveDisabledTrackOutlineWidth, style: PaintingStyle.stroke),
|
|
reason: 'Inactive disabled switch track outline width should be 4.0',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(strokeWidth: activeDisabledTrackOutlineWidth, style: PaintingStyle.stroke),
|
|
reason: 'Active disabled switch track outline width should be 2.0',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(strokeWidth: inactiveEnabledTrackOutlineWidth, style: PaintingStyle.stroke),
|
|
reason: 'Inactive enabled switch track outline width should be 3.0',
|
|
);
|
|
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(strokeWidth: activeEnabledTrackOutlineWidth, style: PaintingStyle.stroke),
|
|
reason: 'Active enabled switch track outline width should be 1.0',
|
|
);
|
|
});
|
|
|
|
testWidgets('Switch track outline width resolves in hovered/focused states', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const hoveredTrackOutlineWidth = 4.0;
|
|
const focusedTrackOutlineWidth = 6.0;
|
|
|
|
double getTrackOutlineWidth(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return hoveredTrackOutlineWidth;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return focusedTrackOutlineWidth;
|
|
}
|
|
return 8.0;
|
|
}
|
|
|
|
final WidgetStateProperty<double> trackOutlineWidth = WidgetStateProperty.resolveWith(
|
|
getTrackOutlineWidth,
|
|
);
|
|
|
|
Widget buildSwitch() {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
value: true,
|
|
trackOutlineWidth: trackOutlineWidth,
|
|
onChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch());
|
|
await tester.pumpAndSettle();
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(strokeWidth: focusedTrackOutlineWidth, style: PaintingStyle.stroke),
|
|
reason: 'Active enabled switch track outline width should be 6.0',
|
|
);
|
|
|
|
// Start hovering
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await gesture.addPointer();
|
|
addTearDown(gesture.removePointer);
|
|
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill)
|
|
..rrect(strokeWidth: hoveredTrackOutlineWidth, style: PaintingStyle.stroke),
|
|
reason: 'Active enabled switch track outline width should be 4.0',
|
|
);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Switch can set icon - M3', (WidgetTester tester) async {
|
|
final themeData = ThemeData(
|
|
colorSchemeSeed: const Color(0xff6750a4),
|
|
brightness: Brightness.light,
|
|
);
|
|
|
|
WidgetStateProperty<Icon?> thumbIcon(Icon? activeIcon, Icon? inactiveIcon) {
|
|
return WidgetStateProperty.resolveWith<Icon?>((Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return activeIcon;
|
|
}
|
|
return inactiveIcon;
|
|
});
|
|
}
|
|
|
|
Widget buildSwitch({
|
|
required bool enabled,
|
|
required bool active,
|
|
Icon? activeIcon,
|
|
Icon? inactiveIcon,
|
|
}) {
|
|
return Theme(
|
|
data: themeData,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
thumbIcon: thumbIcon(activeIcon, inactiveIcon),
|
|
value: active,
|
|
onChanged: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// active icon shows when switch is on.
|
|
await tester.pumpWidget(
|
|
buildSwitch(enabled: true, active: true, activeIcon: const Icon(Icons.close)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..rrect()
|
|
..paragraph(offset: const Offset(32.0, 16.0)),
|
|
);
|
|
|
|
// inactive icon shows when switch is off.
|
|
await tester.pumpWidget(
|
|
buildSwitch(enabled: true, active: false, inactiveIcon: const Icon(Icons.close)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..rrect()
|
|
..rrect()
|
|
..paragraph(offset: const Offset(12.0, 16.0)),
|
|
);
|
|
|
|
// active icon doesn't show when switch is off.
|
|
await tester.pumpWidget(
|
|
buildSwitch(enabled: true, active: false, activeIcon: const Icon(Icons.check)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..rrect()
|
|
..rrect(),
|
|
);
|
|
|
|
// inactive icon doesn't show when switch is on.
|
|
await tester.pumpWidget(
|
|
buildSwitch(enabled: true, active: true, inactiveIcon: const Icon(Icons.check)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..rrect()
|
|
..restore(),
|
|
);
|
|
|
|
// without icon
|
|
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect()
|
|
..rrect()
|
|
..rrect()
|
|
..restore(),
|
|
);
|
|
});
|
|
});
|
|
|
|
testWidgets('Switch.adaptive(Cupertino) is focusable and has correct focus color', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch.adaptive');
|
|
addTearDown(focusNode.dispose);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
var value = true;
|
|
const focusColor = Color(0xffff0000);
|
|
|
|
Widget buildApp({bool enabled = true}) {
|
|
return MaterialApp(
|
|
theme: ThemeData(platform: TargetPlatform.iOS),
|
|
home: Material(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Center(
|
|
child: Switch.adaptive(
|
|
value: value,
|
|
onChanged: enabled
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
focusColor: focusColor,
|
|
focusNode: focusNode,
|
|
autofocus: true,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
find.byType(Switch),
|
|
paints
|
|
..rrect(color: const Color(0xff34c759))
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: focusColor)
|
|
..clipRRect()
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000))
|
|
..rrect(color: const Color(0xffffffff)),
|
|
);
|
|
|
|
// Check the false value.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
find.byType(Switch),
|
|
paints
|
|
..rrect(color: const Color(0x28787880))
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: focusColor)
|
|
..clipRRect()
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000))
|
|
..rrect(color: const Color(0xffffffff)),
|
|
);
|
|
|
|
// Check what happens when disabled.
|
|
value = false;
|
|
await tester.pumpWidget(buildApp(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(focusNode.hasPrimaryFocus, isFalse);
|
|
expect(
|
|
find.byType(Switch),
|
|
paints
|
|
..rrect(color: const Color(0x28787880))
|
|
..clipRRect()
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000))
|
|
..rrect(color: const Color(0xffffffff)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Switch.onFocusChange callback', (WidgetTester tester) async {
|
|
final focusNode = FocusNode(debugLabel: 'Switch');
|
|
var focused = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(
|
|
value: true,
|
|
focusNode: focusNode,
|
|
onFocusChange: (bool value) {
|
|
focused = value;
|
|
},
|
|
onChanged: (bool newValue) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
focusNode.requestFocus();
|
|
await tester.pump();
|
|
expect(focused, isTrue);
|
|
expect(focusNode.hasFocus, isTrue);
|
|
|
|
focusNode.unfocus();
|
|
await tester.pump();
|
|
expect(focused, isFalse);
|
|
expect(focusNode.hasFocus, isFalse);
|
|
|
|
focusNode.dispose();
|
|
});
|
|
|
|
testWidgets('Switch.padding is respected', (WidgetTester tester) async {
|
|
Widget buildSwitch({EdgeInsets? padding}) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Switch(padding: padding, value: true, onChanged: (_) {}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSwitch());
|
|
|
|
expect(tester.getSize(find.byType(Switch)), const Size(60.0, 48.0));
|
|
|
|
await tester.pumpWidget(buildSwitch(padding: EdgeInsets.zero));
|
|
|
|
expect(tester.getSize(find.byType(Switch)), const Size(52.0, 48.0));
|
|
|
|
await tester.pumpWidget(buildSwitch(padding: const EdgeInsets.all(4.0)));
|
|
|
|
expect(tester.getSize(find.byType(Switch)), const Size(60.0, 56.0));
|
|
});
|
|
|
|
testWidgets('Material2 - Switch activeThumbColor', (WidgetTester tester) async {
|
|
const activeColor = Color(0xffff0000);
|
|
const activeThumbColor = Color(0xff00ff00);
|
|
const activeTrackColor = Color(0xff0000ff);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: true,
|
|
onChanged: (_) {},
|
|
activeColor: activeColor,
|
|
activeThumbColor: activeThumbColor,
|
|
activeTrackColor: activeTrackColor,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.widget<Switch>(find.byType(Switch)).activeThumbColor, activeThumbColor);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
color: activeTrackColor,
|
|
rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)),
|
|
)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: activeThumbColor),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Switch activeThumbColor', (WidgetTester tester) async {
|
|
const activeColor = Color(0xffff0000);
|
|
const activeThumbColor = Color(0xff00ff00);
|
|
const activeTrackColor = Color(0xff0000ff);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: true,
|
|
onChanged: (_) {},
|
|
activeColor: activeColor,
|
|
activeThumbColor: activeThumbColor,
|
|
activeTrackColor: activeTrackColor,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.widget<Switch>(find.byType(Switch)).activeThumbColor, activeThumbColor);
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(
|
|
style: PaintingStyle.fill,
|
|
color: activeTrackColor,
|
|
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
|
|
)
|
|
..rrect()
|
|
..rrect(color: activeThumbColor),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material2 - Switch.adaptive activeThumbColor', (WidgetTester tester) async {
|
|
const activeColor = Color(0xffff0000);
|
|
const activeThumbColor = Color(0xff00ff00);
|
|
const activeTrackColor = Color(0xff0000ff);
|
|
|
|
Widget buildFrame(TargetPlatform platform) {
|
|
return MaterialApp(
|
|
key: UniqueKey(),
|
|
theme: ThemeData(platform: platform, useMaterial3: false),
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch.adaptive(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: true,
|
|
onChanged: (_) {},
|
|
activeColor: activeColor,
|
|
activeThumbColor: activeThumbColor,
|
|
activeTrackColor: activeTrackColor,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
|
final trackRRect = platform == TargetPlatform.iOS
|
|
? RRect.fromLTRBR(4.0, 8.5, 55.0, 39.5, const Radius.circular(15.5))
|
|
: RRect.fromLTRBR(4.0, 4.5, 55.0, 35.5, const Radius.circular(15.5));
|
|
await tester.pumpWidget(buildFrame(platform));
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: activeTrackColor, rrect: trackRRect)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000))
|
|
..rrect(color: activeThumbColor),
|
|
);
|
|
}
|
|
|
|
for (final platform in <TargetPlatform>[
|
|
TargetPlatform.android,
|
|
TargetPlatform.fuchsia,
|
|
TargetPlatform.linux,
|
|
TargetPlatform.windows,
|
|
]) {
|
|
final trackRRect = platform == TargetPlatform.fuchsia || platform == TargetPlatform.android
|
|
? RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0))
|
|
: RRect.fromLTRBR(13.0, 13.0, 46.0, 27.0, const Radius.circular(7.0));
|
|
await tester.pumpWidget(buildFrame(platform));
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: activeTrackColor, rrect: trackRRect)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x33000000))
|
|
..rrect(color: const Color(0x24000000))
|
|
..rrect(color: const Color(0x1f000000))
|
|
..rrect(color: activeThumbColor),
|
|
);
|
|
}
|
|
});
|
|
|
|
testWidgets('Material3 - Switch.adaptive activeThumbColor', (WidgetTester tester) async {
|
|
const activeColor = Color(0xffff0000);
|
|
const activeThumbColor = Color(0xff00ff00);
|
|
const activeTrackColor = Color(0xff0000ff);
|
|
|
|
Widget buildFrame(TargetPlatform platform) {
|
|
return MaterialApp(
|
|
key: UniqueKey(),
|
|
theme: ThemeData(platform: platform),
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: Switch.adaptive(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
value: true,
|
|
onChanged: (_) {},
|
|
activeColor: activeColor,
|
|
activeThumbColor: activeThumbColor,
|
|
activeTrackColor: activeTrackColor,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
|
|
final trackRRect = platform == TargetPlatform.iOS
|
|
? RRect.fromLTRBR(4.5, 8.5, 55.5, 39.5, const Radius.circular(15.5))
|
|
: RRect.fromLTRBR(4.5, 4.5, 55.5, 35.5, const Radius.circular(15.5));
|
|
await tester.pumpWidget(buildFrame(platform));
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(color: activeTrackColor, rrect: trackRRect)
|
|
..rrect(color: const Color(0x00000000))
|
|
..rrect(color: const Color(0x26000000))
|
|
..rrect(color: const Color(0x0f000000))
|
|
..rrect(color: const Color(0x0a000000))
|
|
..rrect(color: activeThumbColor),
|
|
);
|
|
}
|
|
|
|
for (final platform in <TargetPlatform>[
|
|
TargetPlatform.android,
|
|
TargetPlatform.fuchsia,
|
|
TargetPlatform.linux,
|
|
TargetPlatform.windows,
|
|
]) {
|
|
final trackRRect = platform == TargetPlatform.fuchsia || platform == TargetPlatform.android
|
|
? RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0))
|
|
: RRect.fromLTRBR(4.0, 4.0, 56.0, 36.0, const Radius.circular(16.0));
|
|
|
|
await tester.pumpWidget(buildFrame(platform));
|
|
|
|
expect(
|
|
Material.of(tester.element(find.byType(Switch))),
|
|
paints
|
|
..rrect(style: PaintingStyle.fill, color: activeTrackColor, rrect: trackRRect)
|
|
..rrect()
|
|
..rrect(color: activeThumbColor),
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
class DelayedImageProvider extends ImageProvider<DelayedImageProvider> {
|
|
DelayedImageProvider(this.image);
|
|
|
|
final ui.Image image;
|
|
|
|
final Completer<ImageInfo> _completer = Completer<ImageInfo>();
|
|
|
|
@override
|
|
Future<DelayedImageProvider> obtainKey(ImageConfiguration configuration) {
|
|
return SynchronousFuture<DelayedImageProvider>(this);
|
|
}
|
|
|
|
@override
|
|
ImageStreamCompleter loadImage(DelayedImageProvider key, ImageDecoderCallback decode) {
|
|
return OneFrameImageStreamCompleter(_completer.future);
|
|
}
|
|
|
|
Future<void> complete() async {
|
|
_completer.complete(ImageInfo(image: image));
|
|
}
|
|
|
|
@override
|
|
String toString() => '${describeIdentity(this)}()';
|
|
}
|
|
|
|
class _TestImageProvider extends ImageProvider<Object> {
|
|
_TestImageProvider({ImageStreamCompleter? streamCompleter}) {
|
|
_streamCompleter = streamCompleter ?? OneFrameImageStreamCompleter(_completer.future);
|
|
}
|
|
|
|
final Completer<ImageInfo> _completer = Completer<ImageInfo>();
|
|
late ImageStreamCompleter _streamCompleter;
|
|
|
|
bool get loadCalled => _loadCallCount > 0;
|
|
int get loadCallCount => _loadCallCount;
|
|
int _loadCallCount = 0;
|
|
|
|
@override
|
|
Future<Object> obtainKey(ImageConfiguration configuration) {
|
|
return SynchronousFuture<_TestImageProvider>(this);
|
|
}
|
|
|
|
@override
|
|
void resolveStreamForKey(
|
|
ImageConfiguration configuration,
|
|
ImageStream stream,
|
|
Object key,
|
|
ImageErrorListener handleError,
|
|
) {
|
|
super.resolveStreamForKey(configuration, stream, key, handleError);
|
|
}
|
|
|
|
@override
|
|
ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) {
|
|
_loadCallCount += 1;
|
|
return _streamCompleter;
|
|
}
|
|
|
|
void complete(ui.Image image) {
|
|
_completer.complete(ImageInfo(image: image));
|
|
}
|
|
|
|
void fail(Object exception, StackTrace? stackTrace) {
|
|
_completer.completeError(exception, stackTrace);
|
|
}
|
|
|
|
@override
|
|
String toString() => '${describeIdentity(this)}()';
|
|
}
|
|
|
|
Widget buildAdaptiveSwitch({
|
|
required TargetPlatform platform,
|
|
bool enabled = true,
|
|
bool value = true,
|
|
bool autofocus = false,
|
|
FocusNode? focusNode,
|
|
Color? focusColor,
|
|
SwitchThemeData? overallSwitchThemeData,
|
|
SwitchThemeData? switchThemeData,
|
|
Adaptation<SwitchThemeData>? switchThemeAdaptation,
|
|
}) {
|
|
final Widget adaptiveSwitch = Switch.adaptive(
|
|
focusNode: focusNode,
|
|
autofocus: autofocus,
|
|
focusColor: focusColor,
|
|
value: value,
|
|
onChanged: enabled ? (_) {} : null,
|
|
);
|
|
|
|
return MaterialApp(
|
|
theme: ThemeData(
|
|
platform: platform,
|
|
switchTheme: overallSwitchThemeData,
|
|
adaptations: switchThemeAdaptation == null
|
|
? null
|
|
: <Adaptation<Object>>[switchThemeAdaptation],
|
|
),
|
|
home: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Material(
|
|
child: Center(
|
|
child: switchThemeData == null
|
|
? adaptiveSwitch
|
|
: SwitchTheme(data: switchThemeData, child: adaptiveSwitch),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
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.windows:
|
|
case TargetPlatform.linux:
|
|
return defaultValue;
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.macOS:
|
|
return const SwitchThemeData(
|
|
thumbColor: MaterialStatePropertyAll<Color>(Colors.lightGreen),
|
|
trackColor: MaterialStatePropertyAll<Color>(Colors.deepPurple),
|
|
);
|
|
}
|
|
}
|
|
}
|