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

CupertinoAlertDialog dark mode & CupertinoActionSheet fidelity (#40007)

parent 609a78fd
...@@ -30,23 +30,6 @@ const TextStyle _kActionSheetContentStyle = TextStyle( ...@@ -30,23 +30,6 @@ const TextStyle _kActionSheetContentStyle = TextStyle(
textBaseline: TextBaseline.alphabetic, textBaseline: TextBaseline.alphabetic,
); );
// This decoration is applied to the blurred backdrop to lighten the blurred
// image. Brightening is done to counteract the dark modal barrier that
// appears behind the alert. The overlay blend mode does the brightening.
// The white color doesn't paint any white, it's a placeholder and is going to be
// replaced by the resolved color of _kAlertBlurOverlayColor.
const BoxDecoration _kAlertBlurOverlayDecoration = BoxDecoration(
color: CupertinoColors.white,
backgroundBlendMode: BlendMode.overlay,
);
// Color of the overlay.
// Extracted from https://developer.apple.com/design/resources/.
const Color _kAlertBlurOverlayColor = CupertinoDynamicColor.withBrightness(
color: Color(0x66000000),
darkColor: Color(0x99000000),
);
// Translucent, very light gray that is painted on top of the blurred backdrop // Translucent, very light gray that is painted on top of the blurred backdrop
// as the action sheet's background color. // as the action sheet's background color.
// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/39272. Use // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/39272. Use
...@@ -224,13 +207,9 @@ class CupertinoActionSheet extends StatelessWidget { ...@@ -224,13 +207,9 @@ class CupertinoActionSheet extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> children = <Widget>[ final List<Widget> children = <Widget>[
Flexible(child: ClipRRect( Flexible(child: ClipRRect(
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: _kBlurAmount, sigmaY: _kBlurAmount), filter: ImageFilter.blur(sigmaX: _kBlurAmount, sigmaY: _kBlurAmount),
child: Container(
decoration: _kAlertBlurOverlayDecoration.copyWith(
color: CupertinoDynamicColor.resolve(_kAlertBlurOverlayColor, context),
),
child: _CupertinoAlertRenderWidget( child: _CupertinoAlertRenderWidget(
contentSection: Builder(builder: _buildContent), contentSection: Builder(builder: _buildContent),
actionsSection: _buildActions(), actionsSection: _buildActions(),
...@@ -238,15 +217,9 @@ class CupertinoActionSheet extends StatelessWidget { ...@@ -238,15 +217,9 @@ class CupertinoActionSheet extends StatelessWidget {
), ),
), ),
), ),
), if (cancelButton != null) _buildCancelButton(),
]; ];
if (cancelButton != null) {
children.add(
_buildCancelButton(),
);
}
final Orientation orientation = MediaQuery.of(context).orientation; final Orientation orientation = MediaQuery.of(context).orientation;
double actionSheetWidth; double actionSheetWidth;
if (orientation == Orientation.portrait) { if (orientation == Orientation.portrait) {
...@@ -420,14 +393,14 @@ class _CupertinoAlertRenderWidget extends RenderObjectWidget { ...@@ -420,14 +393,14 @@ class _CupertinoAlertRenderWidget extends RenderObjectWidget {
RenderObject createRenderObject(BuildContext context) { RenderObject createRenderObject(BuildContext context) {
return _RenderCupertinoAlert( return _RenderCupertinoAlert(
dividerThickness: _kDividerThickness / MediaQuery.of(context).devicePixelRatio, dividerThickness: _kDividerThickness / MediaQuery.of(context).devicePixelRatio,
dividerColor: _kButtonDividerColor, dividerColor: CupertinoDynamicColor.resolve(_kButtonDividerColor, context),
); );
} }
@override @override
void updateRenderObject(BuildContext context, _RenderCupertinoAlert renderObject) { void updateRenderObject(BuildContext context, _RenderCupertinoAlert renderObject) {
super.updateRenderObject(context, renderObject); super.updateRenderObject(context, renderObject);
renderObject.dividerColor = _kButtonDividerColor; renderObject.dividerColor = CupertinoDynamicColor.resolve(_kButtonDividerColor, context);
} }
@override @override
...@@ -1034,7 +1007,7 @@ class _CupertinoAlertActionsRenderWidget extends MultiChildRenderObjectWidget { ...@@ -1034,7 +1007,7 @@ class _CupertinoAlertActionsRenderWidget extends MultiChildRenderObjectWidget {
RenderObject createRenderObject(BuildContext context) { RenderObject createRenderObject(BuildContext context) {
return _RenderCupertinoAlertActions( return _RenderCupertinoAlertActions(
dividerThickness: _dividerThickness, dividerThickness: _dividerThickness,
dividerColor: _kButtonDividerColor, dividerColor: CupertinoDynamicColor.resolve(_kButtonDividerColor, context),
hasCancelButton: _hasCancelButton, hasCancelButton: _hasCancelButton,
backgroundColor: CupertinoDynamicColor.resolve(_kBackgroundColor, context), backgroundColor: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
pressedColor: CupertinoDynamicColor.resolve(_kPressedColor, context), pressedColor: CupertinoDynamicColor.resolve(_kPressedColor, context),
...@@ -1045,7 +1018,7 @@ class _CupertinoAlertActionsRenderWidget extends MultiChildRenderObjectWidget { ...@@ -1045,7 +1018,7 @@ class _CupertinoAlertActionsRenderWidget extends MultiChildRenderObjectWidget {
void updateRenderObject(BuildContext context, _RenderCupertinoAlertActions renderObject) { void updateRenderObject(BuildContext context, _RenderCupertinoAlertActions renderObject) {
renderObject renderObject
..dividerThickness = _dividerThickness ..dividerThickness = _dividerThickness
..dividerColor = _kButtonDividerColor ..dividerColor = CupertinoDynamicColor.resolve(_kButtonDividerColor, context)
..hasCancelButton = _hasCancelButton ..hasCancelButton = _hasCancelButton
..backgroundColor = CupertinoDynamicColor.resolve(_kBackgroundColor, context) ..backgroundColor = CupertinoDynamicColor.resolve(_kBackgroundColor, context)
..pressedColor = CupertinoDynamicColor.resolve(_kPressedColor, context); ..pressedColor = CupertinoDynamicColor.resolve(_kPressedColor, context);
......
...@@ -12,6 +12,8 @@ import 'package:flutter/rendering.dart'; ...@@ -12,6 +12,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/animation.dart' show Curves; import 'package:flutter/animation.dart' show Curves;
import 'colors.dart';
const double _kBackGestureWidth = 20.0; const double _kBackGestureWidth = 20.0;
const double _kMinFlingVelocity = 1.0; // Screen widths per second. const double _kMinFlingVelocity = 1.0; // Screen widths per second.
...@@ -24,7 +26,11 @@ const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds. ...@@ -24,7 +26,11 @@ const int _kMaxDroppedSwipePageForwardAnimationTime = 800; // Milliseconds.
const int _kMaxPageBackAnimationTime = 300; // Milliseconds. const int _kMaxPageBackAnimationTime = 300; // Milliseconds.
// Barrier color for a Cupertino modal barrier. // Barrier color for a Cupertino modal barrier.
const Color _kModalBarrierColor = Color(0x6604040F); // Extracted from https://developer.apple.com/design/resources/.
const Color _kModalBarrierColor = CupertinoDynamicColor.withBrightness(
color: Color(0x33000000),
darkColor: Color(0x7A000000),
);
// The duration of the transition used when a modal popup is shown. // The duration of the transition used when a modal popup is shown.
const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335); const Duration _kModalPopupTransitionDuration = Duration(milliseconds: 335);
...@@ -788,6 +794,7 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> { ...@@ -788,6 +794,7 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
_CupertinoModalPopupRoute({ _CupertinoModalPopupRoute({
this.builder, this.builder,
this.barrierLabel, this.barrierLabel,
this.barrierColor,
RouteSettings settings, RouteSettings settings,
}) : super(settings: settings); }) : super(settings: settings);
...@@ -797,7 +804,7 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> { ...@@ -797,7 +804,7 @@ class _CupertinoModalPopupRoute<T> extends PopupRoute<T> {
final String barrierLabel; final String barrierLabel;
@override @override
Color get barrierColor => _kModalBarrierColor; final Color barrierColor;
@override @override
bool get barrierDismissible => true; bool get barrierDismissible => true;
...@@ -879,6 +886,7 @@ Future<T> showCupertinoModalPopup<T>({ ...@@ -879,6 +886,7 @@ Future<T> showCupertinoModalPopup<T>({
_CupertinoModalPopupRoute<T>( _CupertinoModalPopupRoute<T>(
builder: builder, builder: builder,
barrierLabel: 'Dismiss', barrierLabel: 'Dismiss',
barrierColor: CupertinoDynamicColor.resolve(_kModalBarrierColor, context),
), ),
); );
} }
...@@ -947,7 +955,7 @@ Future<T> showCupertinoDialog<T>({ ...@@ -947,7 +955,7 @@ Future<T> showCupertinoDialog<T>({
return showGeneralDialog( return showGeneralDialog(
context: context, context: context,
barrierDismissible: false, barrierDismissible: false,
barrierColor: _kModalBarrierColor, barrierColor: CupertinoDynamicColor.resolve(_kModalBarrierColor, context),
// This transition duration was eyeballed comparing with iOS // This transition duration was eyeballed comparing with iOS
transitionDuration: const Duration(milliseconds: 250), transitionDuration: const Duration(milliseconds: 250),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
......
...@@ -7,7 +7,6 @@ import 'package:flutter/widgets.dart'; ...@@ -7,7 +7,6 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart'; import '../widgets/semantics_tester.dart';
void main() { void main() {
...@@ -113,8 +112,6 @@ void main() { ...@@ -113,8 +112,6 @@ void main() {
await tester.tap(find.text('Go')); await tester.tap(find.text('Go'));
await tester.pump(); await tester.pump();
// Draw the overlay using the light variant.
expect(find.byType(CupertinoActionSheet), paints..rect(color: const Color(0x66000000)));
expect( expect(
actionTextStyle('action').color.value, actionTextStyle('action').color.value,
const Color.fromARGB(255, 0, 122, 255).value, const Color.fromARGB(255, 0, 122, 255).value,
...@@ -123,8 +120,6 @@ void main() { ...@@ -123,8 +120,6 @@ void main() {
stateSetter(() { brightness = Brightness.dark; }); stateSetter(() { brightness = Brightness.dark; });
await tester.pump(); await tester.pump();
// Draw the overlay using the dark variant.
expect(find.byType(CupertinoActionSheet), paints..rect(color: const Color(0x99000000)));
expect( expect(
actionTextStyle('action').color.value, actionTextStyle('action').color.value,
const Color.fromARGB(255, 10, 132, 255).value, const Color.fromARGB(255, 10, 132, 255).value,
......
...@@ -63,6 +63,38 @@ void main() { ...@@ -63,6 +63,38 @@ void main() {
expect(widget.style.color.withAlpha(255), CupertinoColors.destructiveRed); expect(widget.style.color.withAlpha(255), CupertinoColors.destructiveRed);
}); });
testWidgets('Dialog dark theme', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: MediaQuery(
data: const MediaQueryData(platformBrightness: Brightness.dark),
child: CupertinoAlertDialog(
title: const Text('The Title'),
content: const Text('Content'),
actions: <Widget>[
CupertinoDialogAction(child: const Text('Cancel'), isDefaultAction: true, onPressed: () {}),
const CupertinoDialogAction(child: Text('OK')),
],
),
),
),
);
final RichText cancelText = tester.widget<RichText>(
find.descendant(of: find.text('Cancel'), matching: find.byType(RichText)),
);
expect(
cancelText.text.style.color.value,
0xFF0A84FF, // dark elevated color of systemBlue.
);
expect(
find.byType(CupertinoAlertDialog),
paints..rect(color: const Color(0xBF1E1E1E)),
);
});
testWidgets('Has semantic annotations', (WidgetTester tester) async { testWidgets('Has semantic annotations', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(const MaterialApp(home: Material( await tester.pumpWidget(const MaterialApp(home: Material(
...@@ -739,8 +771,8 @@ void main() { ...@@ -739,8 +771,8 @@ void main() {
await tester.tap(find.text('Go')); await tester.tap(find.text('Go'));
await tester.pump(); await tester.pump();
const Color normalButtonBackgroundColor = Color(0xc0ffffff); const Color normalButtonBackgroundColor = Color(0xCCF2F2F2);
const Color pressedButtonBackgroundColor = Color(0x90ffffff); const Color pressedButtonBackgroundColor = Color(0xFFE1E1E1);
final RenderBox firstButtonBox = findActionButtonRenderBoxByTitle(tester, 'Option 1'); final RenderBox firstButtonBox = findActionButtonRenderBoxByTitle(tester, 'Option 1');
final RenderBox secondButtonBox = findActionButtonRenderBoxByTitle(tester, 'Option 2'); final RenderBox secondButtonBox = findActionButtonRenderBoxByTitle(tester, 'Option 2');
final RenderBox actionsSectionBox = findScrollableActionsSectionRenderBox(tester); final RenderBox actionsSectionBox = findScrollableActionsSectionRenderBox(tester);
......
...@@ -743,6 +743,79 @@ void main() { ...@@ -743,6 +743,79 @@ void main() {
false, false,
); );
}); });
testWidgets('ModalPopup overlay dark mode', (WidgetTester tester) async {
StateSetter stateSetter;
Brightness brightness = Brightness.light;
await tester.pumpWidget(
StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
stateSetter = setter;
return CupertinoApp(
theme: CupertinoThemeData(brightness: brightness),
home: CupertinoPageScaffold(
child: Builder(builder: (BuildContext context) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
}),
),
);
},
),
);
await tester.tap(find.text('tap'));
await tester.pumpAndSettle();
expect(
tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color.value,
0x33000000,
);
stateSetter(() { brightness = Brightness.dark; });
await tester.pump();
// TODO(LongCatIsLooong): The background overlay SHOULD switch to dark color.
expect(
tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color.value,
0x33000000,
);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoPageScaffold(
child: Builder(builder: (BuildContext context) {
return GestureDetector(
onTap: () async {
await showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) => const SizedBox(),
);
},
child: const Text('tap'),
);
}),
),
),
);
await tester.tap(find.text('tap'));
await tester.pumpAndSettle();
expect(
tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color.value,
0x7A000000,
);
});
} }
class MockNavigatorObserver extends Mock implements NavigatorObserver {} class MockNavigatorObserver extends Mock implements NavigatorObserver {}
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