Unverified Commit ad47f9ee authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

CupertinoActivityIndicator & CupertinoApp dark mode (#39289)

parent 8daa165d
......@@ -9,14 +9,19 @@ import 'package:flutter/widgets.dart';
import 'colors.dart';
const double _kDefaultIndicatorRadius = 10.0;
// Extracted from the large activity indicators in https://developer.apple.com/design/resources/.
const Color _kActiveTickColor = CupertinoDynamicColor.withBrightness(
color: Color(0x99606067),
darkColor: Color(0x99EBEBF5),
);
/// An iOS-style activity indicator.
/// An iOS-style activity indicator that spins clockwise.
///
/// See also:
///
/// * <https://developer.apple.com/ios/human-interface-guidelines/controls/progress-indicators/#activity-indicators>
class CupertinoActivityIndicator extends StatefulWidget {
/// Creates an iOS-style activity indicator.
/// Creates an iOS-style activity indicator that spins clockwise.
const CupertinoActivityIndicator({
Key key,
this.animating = true,
......@@ -81,6 +86,7 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
child: CustomPaint(
painter: _CupertinoActivityIndicatorPainter(
position: _controller,
activeColor: CupertinoDynamicColor.resolve(_kActiveTickColor, context),
radius: widget.radius,
),
),
......@@ -91,12 +97,11 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
const double _kTwoPI = math.pi * 2.0;
const int _kTickCount = 12;
const int _kHalfTickCount = _kTickCount ~/ 2;
const Color _kTickColor = CupertinoColors.lightBackgroundGray;
const Color _kActiveTickColor = Color(0xFF9D9D9D);
class _CupertinoActivityIndicatorPainter extends CustomPainter {
_CupertinoActivityIndicatorPainter({
this.position,
@required this.position,
@required this.activeColor,
double radius,
}) : tickFundamentalRRect = RRect.fromLTRBXY(
-radius,
......@@ -110,6 +115,7 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
final Animation<double> position;
final RRect tickFundamentalRRect;
final Color activeColor;
@override
void paint(Canvas canvas, Size size) {
......@@ -122,7 +128,7 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
for (int i = 0; i < _kTickCount; ++ i) {
final double t = (((i + activeTick) % _kTickCount) / _kHalfTickCount).clamp(0.0, 1.0);
paint.color = Color.lerp(_kActiveTickColor, _kTickColor, t);
paint.color = activeColor.withOpacity((t * activeColor.opacity).clamp(0, 1));
canvas.drawRRect(tickFundamentalRRect, paint);
canvas.rotate(-_kTwoPI / _kTickCount);
}
......@@ -132,6 +138,6 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
@override
bool shouldRepaint(_CupertinoActivityIndicatorPainter oldPainter) {
return oldPainter.position != position;
return oldPainter.position != position || oldPainter.activeColor != activeColor;
}
}
......@@ -275,7 +275,9 @@ class _CupertinoAppState extends State<CupertinoApp> {
data: effectiveThemeData,
child: CupertinoSystemColors(
data: CupertinoSystemColors.of(context, useFallbackValues: true),
child: WidgetsApp(
child: Builder(
builder: (BuildContext context) {
return WidgetsApp(
key: GlobalObjectKey(this),
navigatorKey: widget.navigatorKey,
navigatorObservers: _navigatorObservers,
......@@ -290,7 +292,7 @@ class _CupertinoAppState extends State<CupertinoApp> {
title: widget.title,
onGenerateTitle: widget.onGenerateTitle,
textStyle: effectiveThemeData.textTheme.textStyle,
color: widget.color ?? CupertinoColors.activeBlue,
color: CupertinoDynamicColor.resolve(widget.color ?? effectiveThemeData.primaryColor, context),
locale: widget.locale,
localizationsDelegates: _localizationsDelegates,
localeResolutionCallback: widget.localeResolutionCallback,
......@@ -312,6 +314,8 @@ class _CupertinoAppState extends State<CupertinoApp> {
onPressed: onPressed,
);
},
);
},
),
),
),
......
......@@ -326,9 +326,11 @@ class CupertinoDynamicColor extends Color {
///
/// If the given color is already a concrete [Color], it will be returned as is.
/// If the given color is a [CupertinoDynamicColor], but the given [BuildContext]
/// lacks the dependencies essential to the color resolution, an exception will
/// be thrown, unless [nullOk] is set to true.
static Color resolve(Color resolvable, BuildContext context, { bool nullOk = false }) {
/// lacks the dependencies required to the color resolution, the default trait
/// value will be used ([Brightness.light] platform brightness, normal contrast,
/// [CupertinoUserInterfaceLevelData.base] elevation level), unless [nullOk] is
/// set to false, in which case an exception will be thrown.
static Color resolve(Color resolvable, BuildContext context, { bool nullOk = true }) {
assert(resolvable != null);
assert(context != null);
return (resolvable is CupertinoDynamicColor)
......@@ -380,8 +382,11 @@ class CupertinoDynamicColor extends Color {
/// from the previous [CupertinoTheme.of] call, in an effort to determine the
/// brightness value.
///
/// If any of the required dependecies are missing from the given context, an exception
/// will be thrown unless [nullOk] is set to `true`.
/// If any of the required dependecies are missing from the given context, the
/// default value of that trait will be used ([Brightness.light] platform
/// brightness, normal contrast, [CupertinoUserInterfaceLevelData.base] elevation
/// level), unless [nullOk] is set to false, in which case an exception will be
/// thrown.
CupertinoDynamicColor resolveFrom(BuildContext context, { bool nullOk = false }) {
final Brightness brightness = _isPlatformBrightnessDependent
? CupertinoTheme.brightnessOf(context, nullOk: nullOk) ?? Brightness.light
......
......@@ -6,20 +6,52 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
testWidgets('Activity indicator animate property works', (WidgetTester tester) async {
await tester.pumpWidget(const Center(child: CupertinoActivityIndicator()));
await tester.pumpWidget(buildCupertinoActivityIndicator());
expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
await tester.pumpWidget(const Center(child: CupertinoActivityIndicator(animating: false)));
await tester.pumpWidget(buildCupertinoActivityIndicator(false));
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
await tester.pumpWidget(Container());
await tester.pumpWidget(const Center(child: CupertinoActivityIndicator(animating: false)));
await tester.pumpWidget(buildCupertinoActivityIndicator(false));
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
await tester.pumpWidget(const Center(child: CupertinoActivityIndicator()));
await tester.pumpWidget(buildCupertinoActivityIndicator());
expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
});
testWidgets('Activity indicator dark mode', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: MediaQueryData(platformBrightness: Brightness.light),
child: CupertinoActivityIndicator(),
),
);
expect(find.byType(CupertinoActivityIndicator), paints..rrect(color: const Color(0x99606067)));
await tester.pumpWidget(
const MediaQuery(
data: MediaQueryData(platformBrightness: Brightness.dark),
child: CupertinoActivityIndicator(),
),
);
expect(find.byType(CupertinoActivityIndicator), paints..rrect(color: const Color(0x99EBEBF5)));
});
}
Widget buildCupertinoActivityIndicator([ bool animating ]) {
return MediaQuery(
data: const MediaQueryData(platformBrightness: Brightness.light),
child: CupertinoActivityIndicator(
animating: animating ?? true,
),
);
}
......@@ -62,4 +62,26 @@ void main() {
expect(find.text('Select All'), findsOneWidget);
expect(find.text('Thu Oct 4 '), findsOneWidget);
});
testWidgets('Can use dynamic color', (WidgetTester tester) async {
const CupertinoDynamicColor dynamicColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFF000000),
darkColor: Color(0xFF000001),
);
await tester.pumpWidget(const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.light),
color: dynamicColor,
home: Placeholder(),
));
expect(tester.widget<Title>(find.byType(Title)).color.value, 0xFF000000);
await tester.pumpWidget(const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.dark),
color: dynamicColor,
home: Placeholder(),
));
expect(tester.widget<Title>(find.byType(Title)).color.value, 0xFF000001);
});
}
......@@ -19,7 +19,7 @@ class DependentWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final Color resolved = CupertinoDynamicColor.resolve(color, context);
final Color resolved = CupertinoDynamicColor.resolve(color, context, nullOk: false);
return DecoratedBox(
decoration: BoxDecoration(color: resolved),
child: const SizedBox.expand(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment