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'; ...@@ -9,14 +9,19 @@ import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
const double _kDefaultIndicatorRadius = 10.0; 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: /// See also:
/// ///
/// * <https://developer.apple.com/ios/human-interface-guidelines/controls/progress-indicators/#activity-indicators> /// * <https://developer.apple.com/ios/human-interface-guidelines/controls/progress-indicators/#activity-indicators>
class CupertinoActivityIndicator extends StatefulWidget { class CupertinoActivityIndicator extends StatefulWidget {
/// Creates an iOS-style activity indicator. /// Creates an iOS-style activity indicator that spins clockwise.
const CupertinoActivityIndicator({ const CupertinoActivityIndicator({
Key key, Key key,
this.animating = true, this.animating = true,
...@@ -81,6 +86,7 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator> ...@@ -81,6 +86,7 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
child: CustomPaint( child: CustomPaint(
painter: _CupertinoActivityIndicatorPainter( painter: _CupertinoActivityIndicatorPainter(
position: _controller, position: _controller,
activeColor: CupertinoDynamicColor.resolve(_kActiveTickColor, context),
radius: widget.radius, radius: widget.radius,
), ),
), ),
...@@ -91,25 +97,25 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator> ...@@ -91,25 +97,25 @@ class _CupertinoActivityIndicatorState extends State<CupertinoActivityIndicator>
const double _kTwoPI = math.pi * 2.0; const double _kTwoPI = math.pi * 2.0;
const int _kTickCount = 12; const int _kTickCount = 12;
const int _kHalfTickCount = _kTickCount ~/ 2; const int _kHalfTickCount = _kTickCount ~/ 2;
const Color _kTickColor = CupertinoColors.lightBackgroundGray;
const Color _kActiveTickColor = Color(0xFF9D9D9D);
class _CupertinoActivityIndicatorPainter extends CustomPainter { class _CupertinoActivityIndicatorPainter extends CustomPainter {
_CupertinoActivityIndicatorPainter({ _CupertinoActivityIndicatorPainter({
this.position, @required this.position,
@required this.activeColor,
double radius, double radius,
}) : tickFundamentalRRect = RRect.fromLTRBXY( }) : tickFundamentalRRect = RRect.fromLTRBXY(
-radius, -radius,
1.0 * radius / _kDefaultIndicatorRadius, 1.0 * radius / _kDefaultIndicatorRadius,
-radius / 2.0, -radius / 2.0,
-1.0 * radius / _kDefaultIndicatorRadius, -1.0 * radius / _kDefaultIndicatorRadius,
1.0, 1.0,
1.0, 1.0,
), ),
super(repaint: position); super(repaint: position);
final Animation<double> position; final Animation<double> position;
final RRect tickFundamentalRRect; final RRect tickFundamentalRRect;
final Color activeColor;
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
...@@ -122,7 +128,7 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter { ...@@ -122,7 +128,7 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
for (int i = 0; i < _kTickCount; ++ i) { for (int i = 0; i < _kTickCount; ++ i) {
final double t = (((i + activeTick) % _kTickCount) / _kHalfTickCount).clamp(0.0, 1.0); 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.drawRRect(tickFundamentalRRect, paint);
canvas.rotate(-_kTwoPI / _kTickCount); canvas.rotate(-_kTwoPI / _kTickCount);
} }
...@@ -132,6 +138,6 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter { ...@@ -132,6 +138,6 @@ class _CupertinoActivityIndicatorPainter extends CustomPainter {
@override @override
bool shouldRepaint(_CupertinoActivityIndicatorPainter oldPainter) { bool shouldRepaint(_CupertinoActivityIndicatorPainter oldPainter) {
return oldPainter.position != position; return oldPainter.position != position || oldPainter.activeColor != activeColor;
} }
} }
...@@ -275,41 +275,45 @@ class _CupertinoAppState extends State<CupertinoApp> { ...@@ -275,41 +275,45 @@ class _CupertinoAppState extends State<CupertinoApp> {
data: effectiveThemeData, data: effectiveThemeData,
child: CupertinoSystemColors( child: CupertinoSystemColors(
data: CupertinoSystemColors.of(context, useFallbackValues: true), data: CupertinoSystemColors.of(context, useFallbackValues: true),
child: WidgetsApp( child: Builder(
key: GlobalObjectKey(this), builder: (BuildContext context) {
navigatorKey: widget.navigatorKey, return WidgetsApp(
navigatorObservers: _navigatorObservers, key: GlobalObjectKey(this),
pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) => navigatorKey: widget.navigatorKey,
CupertinoPageRoute<T>(settings: settings, builder: builder), navigatorObservers: _navigatorObservers,
home: widget.home, pageRouteBuilder: <T>(RouteSettings settings, WidgetBuilder builder) =>
routes: widget.routes, CupertinoPageRoute<T>(settings: settings, builder: builder),
initialRoute: widget.initialRoute, home: widget.home,
onGenerateRoute: widget.onGenerateRoute, routes: widget.routes,
onUnknownRoute: widget.onUnknownRoute, initialRoute: widget.initialRoute,
builder: widget.builder, onGenerateRoute: widget.onGenerateRoute,
title: widget.title, onUnknownRoute: widget.onUnknownRoute,
onGenerateTitle: widget.onGenerateTitle, builder: widget.builder,
textStyle: effectiveThemeData.textTheme.textStyle, title: widget.title,
color: widget.color ?? CupertinoColors.activeBlue, onGenerateTitle: widget.onGenerateTitle,
locale: widget.locale, textStyle: effectiveThemeData.textTheme.textStyle,
localizationsDelegates: _localizationsDelegates, color: CupertinoDynamicColor.resolve(widget.color ?? effectiveThemeData.primaryColor, context),
localeResolutionCallback: widget.localeResolutionCallback, locale: widget.locale,
localeListResolutionCallback: widget.localeListResolutionCallback, localizationsDelegates: _localizationsDelegates,
supportedLocales: widget.supportedLocales, localeResolutionCallback: widget.localeResolutionCallback,
showPerformanceOverlay: widget.showPerformanceOverlay, localeListResolutionCallback: widget.localeListResolutionCallback,
checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages, supportedLocales: widget.supportedLocales,
checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers, showPerformanceOverlay: widget.showPerformanceOverlay,
showSemanticsDebugger: widget.showSemanticsDebugger, checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner, checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) { showSemanticsDebugger: widget.showSemanticsDebugger,
return CupertinoButton.filled( debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
child: const Icon( inspectorSelectButtonBuilder: (BuildContext context, VoidCallback onPressed) {
CupertinoIcons.search, return CupertinoButton.filled(
size: 28.0, child: const Icon(
color: CupertinoColors.white, CupertinoIcons.search,
), size: 28.0,
padding: EdgeInsets.zero, color: CupertinoColors.white,
onPressed: onPressed, ),
padding: EdgeInsets.zero,
onPressed: onPressed,
);
},
); );
}, },
), ),
......
...@@ -326,9 +326,11 @@ class CupertinoDynamicColor extends Color { ...@@ -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 already a concrete [Color], it will be returned as is.
/// If the given color is a [CupertinoDynamicColor], but the given [BuildContext] /// If the given color is a [CupertinoDynamicColor], but the given [BuildContext]
/// lacks the dependencies essential to the color resolution, an exception will /// lacks the dependencies required to the color resolution, the default trait
/// be thrown, unless [nullOk] is set to true. /// value will be used ([Brightness.light] platform brightness, normal contrast,
static Color resolve(Color resolvable, BuildContext context, { bool nullOk = false }) { /// [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(resolvable != null);
assert(context != null); assert(context != null);
return (resolvable is CupertinoDynamicColor) return (resolvable is CupertinoDynamicColor)
...@@ -380,8 +382,11 @@ class CupertinoDynamicColor extends Color { ...@@ -380,8 +382,11 @@ class CupertinoDynamicColor extends Color {
/// from the previous [CupertinoTheme.of] call, in an effort to determine the /// from the previous [CupertinoTheme.of] call, in an effort to determine the
/// brightness value. /// brightness value.
/// ///
/// If any of the required dependecies are missing from the given context, an exception /// If any of the required dependecies are missing from the given context, the
/// will be thrown unless [nullOk] is set to `true`. /// 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 }) { CupertinoDynamicColor resolveFrom(BuildContext context, { bool nullOk = false }) {
final Brightness brightness = _isPlatformBrightnessDependent final Brightness brightness = _isPlatformBrightnessDependent
? CupertinoTheme.brightnessOf(context, nullOk: nullOk) ?? Brightness.light ? CupertinoTheme.brightnessOf(context, nullOk: nullOk) ?? Brightness.light
......
...@@ -6,20 +6,52 @@ import 'package:flutter/cupertino.dart'; ...@@ -6,20 +6,52 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() { void main() {
testWidgets('Activity indicator animate property works', (WidgetTester tester) async { 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)); 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)); expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
await tester.pumpWidget(Container()); await tester.pumpWidget(Container());
await tester.pumpWidget(const Center(child: CupertinoActivityIndicator(animating: false))); await tester.pumpWidget(buildCupertinoActivityIndicator(false));
expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
await tester.pumpWidget(const Center(child: CupertinoActivityIndicator())); await tester.pumpWidget(buildCupertinoActivityIndicator());
expect(SchedulerBinding.instance.transientCallbackCount, equals(1)); 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() { ...@@ -62,4 +62,26 @@ void main() {
expect(find.text('Select All'), findsOneWidget); expect(find.text('Select All'), findsOneWidget);
expect(find.text('Thu Oct 4 '), 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 { ...@@ -19,7 +19,7 @@ class DependentWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Color resolved = CupertinoDynamicColor.resolve(color, context); final Color resolved = CupertinoDynamicColor.resolve(color, context, nullOk: false);
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration(color: resolved), decoration: BoxDecoration(color: resolved),
child: const SizedBox.expand(), 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