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

CupertinoActionSheet dark mode & fidelity (#39215)

parent d8040970
...@@ -9,14 +9,15 @@ import 'package:flutter/rendering.dart'; ...@@ -9,14 +9,15 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'interface_level.dart';
import 'scrollbar.dart'; import 'scrollbar.dart';
import 'theme.dart';
const TextStyle _kActionSheetActionStyle = TextStyle( const TextStyle _kActionSheetActionStyle = TextStyle(
fontFamily: '.SF UI Text', fontFamily: '.SF UI Text',
inherit: false, inherit: false,
fontSize: 20.0, fontSize: 20.0,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: CupertinoColors.activeBlue,
textBaseline: TextBaseline.alphabetic, textBaseline: TextBaseline.alphabetic,
); );
...@@ -32,28 +33,52 @@ const TextStyle _kActionSheetContentStyle = TextStyle( ...@@ -32,28 +33,52 @@ const TextStyle _kActionSheetContentStyle = TextStyle(
// This decoration is applied to the blurred backdrop to lighten the blurred // This decoration is applied to the blurred backdrop to lighten the blurred
// image. Brightening is done to counteract the dark modal barrier that // image. Brightening is done to counteract the dark modal barrier that
// appears behind the alert. The overlay blend mode does the brightening. // appears behind the alert. The overlay blend mode does the brightening.
// The white color doesn't paint any white, it's just the basis for the // The white color doesn't paint any white, it's a placeholder and is going to be
// overlay blend mode. // replaced by the resolved color of _kAlertBlurOverlayColor.
const BoxDecoration _kAlertBlurOverlayDecoration = BoxDecoration( const BoxDecoration _kAlertBlurOverlayDecoration = BoxDecoration(
color: CupertinoColors.white, color: CupertinoColors.white,
backgroundBlendMode: BlendMode.overlay, 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.
const Color _kBackgroundColor = Color(0xD1F8F8F8); // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/39272. Use
// System Materials once we have them.
// Extracted from https://developer.apple.com/design/resources/.
const Color _kBackgroundColor = CupertinoDynamicColor.withBrightness(
color: Color(0xC7F9F9F9),
darkColor: Color(0xC7252525),
);
// Translucent, light gray that is painted on top of the blurred backdrop as // Translucent, light gray that is painted on top of the blurred backdrop as
// the background color of a pressed button. // the background color of a pressed button.
const Color _kPressedColor = Color(0xA6E5E5EA); // Eye-balled from iOS 13 beta simulator.
const Color _kPressedColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFFE1E1E1),
darkColor: Color(0xFF2E2E2E),
);
const Color _kCancelPressedColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFFECECEC),
darkColor: Color(0xFF49494B),
);
// The gray color used for text that appears in the title area.
// Extracted from https://developer.apple.com/design/resources/.
const Color _kContentTextColor = Color(0xFF8F8F8F);
// Translucent gray that is painted on top of the blurred backdrop in the gap // Translucent gray that is painted on top of the blurred backdrop in the gap
// areas between the content section and actions section, as well as between // areas between the content section and actions section, as well as between
// buttons. // buttons.
const Color _kButtonDividerColor = Color(0x403F3F3F); // Eye-balled from iOS 13 beta simulator.
const Color _kButtonDividerColor = _kContentTextColor;
const Color _kContentTextColor = Color(0xFF8F8F8F);
const Color _kCancelButtonPressedColor = Color(0xFFEAEAEA);
const double _kBlurAmount = 20.0; const double _kBlurAmount = 20.0;
const double _kEdgeHorizontalPadding = 8.0; const double _kEdgeHorizontalPadding = 8.0;
...@@ -148,7 +173,7 @@ class CupertinoActionSheet extends StatelessWidget { ...@@ -148,7 +173,7 @@ class CupertinoActionSheet extends StatelessWidget {
/// Typically this is an [CupertinoActionSheetAction] widget. /// Typically this is an [CupertinoActionSheetAction] widget.
final Widget cancelButton; final Widget cancelButton;
Widget _buildContent() { Widget _buildContent(BuildContext context) {
final List<Widget> content = <Widget>[]; final List<Widget> content = <Widget>[];
if (title != null || message != null) { if (title != null || message != null) {
final Widget titleSection = _CupertinoAlertContentSection( final Widget titleSection = _CupertinoAlertContentSection(
...@@ -160,7 +185,7 @@ class CupertinoActionSheet extends StatelessWidget { ...@@ -160,7 +185,7 @@ class CupertinoActionSheet extends StatelessWidget {
} }
return Container( return Container(
color: _kBackgroundColor, color: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
...@@ -203,9 +228,11 @@ class CupertinoActionSheet extends StatelessWidget { ...@@ -203,9 +228,11 @@ class CupertinoActionSheet extends StatelessWidget {
child: BackdropFilter( child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: _kBlurAmount, sigmaY: _kBlurAmount), filter: ImageFilter.blur(sigmaX: _kBlurAmount, sigmaY: _kBlurAmount),
child: Container( child: Container(
decoration: _kAlertBlurOverlayDecoration, decoration: _kAlertBlurOverlayDecoration.copyWith(
color: CupertinoDynamicColor.resolve(_kAlertBlurOverlayColor, context),
),
child: _CupertinoAlertRenderWidget( child: _CupertinoAlertRenderWidget(
contentSection: _buildContent(), contentSection: Builder(builder: _buildContent),
actionsSection: _buildActions(), actionsSection: _buildActions(),
), ),
), ),
...@@ -234,6 +261,8 @@ class CupertinoActionSheet extends StatelessWidget { ...@@ -234,6 +261,8 @@ class CupertinoActionSheet extends StatelessWidget {
scopesRoute: true, scopesRoute: true,
explicitChildNodes: true, explicitChildNodes: true,
label: 'Alert', label: 'Alert',
child: CupertinoUserInterfaceLevel(
data: CupertinoUserInterfaceLevelData.elevated,
child: Container( child: Container(
width: actionSheetWidth, width: actionSheetWidth,
margin: const EdgeInsets.symmetric( margin: const EdgeInsets.symmetric(
...@@ -247,6 +276,7 @@ class CupertinoActionSheet extends StatelessWidget { ...@@ -247,6 +276,7 @@ class CupertinoActionSheet extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }
...@@ -291,16 +321,16 @@ class CupertinoActionSheetAction extends StatelessWidget { ...@@ -291,16 +321,16 @@ class CupertinoActionSheetAction extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
TextStyle style = _kActionSheetActionStyle; TextStyle style = _kActionSheetActionStyle.copyWith(
color: isDestructiveAction
? CupertinoSystemColors.of(context).systemRed
: CupertinoTheme.of(context).primaryColor,
);
if (isDefaultAction) { if (isDefaultAction) {
style = style.copyWith(fontWeight: FontWeight.w600); style = style.copyWith(fontWeight: FontWeight.w600);
} }
if (isDestructiveAction) {
style = style.copyWith(color: CupertinoColors.destructiveRed);
}
return GestureDetector( return GestureDetector(
onTap: onPressed, onTap: onPressed,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
...@@ -341,34 +371,25 @@ class _CupertinoActionSheetCancelButton extends StatefulWidget { ...@@ -341,34 +371,25 @@ class _CupertinoActionSheetCancelButton extends StatefulWidget {
} }
class _CupertinoActionSheetCancelButtonState extends State<_CupertinoActionSheetCancelButton> { class _CupertinoActionSheetCancelButtonState extends State<_CupertinoActionSheetCancelButton> {
Color _backgroundColor; bool isBeingPressed = false;
@override
void initState() {
_backgroundColor = CupertinoColors.white;
super.initState();
}
void _onTapDown(TapDownDetails event) { void _onTapDown(TapDownDetails event) {
setState(() { setState(() { isBeingPressed = true; });
_backgroundColor = _kCancelButtonPressedColor;
});
} }
void _onTapUp(TapUpDetails event) { void _onTapUp(TapUpDetails event) {
setState(() { setState(() { isBeingPressed = false; });
_backgroundColor = CupertinoColors.white;
});
} }
void _onTapCancel() { void _onTapCancel() {
setState(() { setState(() { isBeingPressed = false; });
_backgroundColor = CupertinoColors.white;
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Color backgroundColor = isBeingPressed
? _kCancelPressedColor
: CupertinoSystemColors.of(context).secondarySystemGroupedBackground;
return GestureDetector( return GestureDetector(
excludeFromSemantics: true, excludeFromSemantics: true,
onTapDown: _onTapDown, onTapDown: _onTapDown,
...@@ -376,7 +397,7 @@ class _CupertinoActionSheetCancelButtonState extends State<_CupertinoActionSheet ...@@ -376,7 +397,7 @@ class _CupertinoActionSheetCancelButtonState extends State<_CupertinoActionSheet
onTapCancel: _onTapCancel, onTapCancel: _onTapCancel,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: _backgroundColor, color: CupertinoDynamicColor.resolve(backgroundColor, context),
borderRadius: BorderRadius.circular(_kCornerRadius), borderRadius: BorderRadius.circular(_kCornerRadius),
), ),
child: widget.child, child: widget.child,
...@@ -399,9 +420,16 @@ class _CupertinoAlertRenderWidget extends RenderObjectWidget { ...@@ -399,9 +420,16 @@ 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,
); );
} }
@override
void updateRenderObject(BuildContext context, _RenderCupertinoAlert renderObject) {
super.updateRenderObject(context, renderObject);
renderObject.dividerColor = _kButtonDividerColor;
}
@override @override
RenderObjectElement createElement() { RenderObjectElement createElement() {
return _CupertinoAlertRenderElement(this); return _CupertinoAlertRenderElement(this);
...@@ -514,9 +542,14 @@ class _RenderCupertinoAlert extends RenderBox { ...@@ -514,9 +542,14 @@ class _RenderCupertinoAlert extends RenderBox {
RenderBox contentSection, RenderBox contentSection,
RenderBox actionsSection, RenderBox actionsSection,
double dividerThickness = 0.0, double dividerThickness = 0.0,
}) : _contentSection = contentSection, @required Color dividerColor,
}) : assert(dividerColor != null),
_contentSection = contentSection,
_actionsSection = actionsSection, _actionsSection = actionsSection,
_dividerThickness = dividerThickness; _dividerThickness = dividerThickness,
_dividerPaint = Paint()
..color = dividerColor
..style = PaintingStyle.fill;
RenderBox get contentSection => _contentSection; RenderBox get contentSection => _contentSection;
RenderBox _contentSection; RenderBox _contentSection;
...@@ -546,11 +579,17 @@ class _RenderCupertinoAlert extends RenderBox { ...@@ -546,11 +579,17 @@ class _RenderCupertinoAlert extends RenderBox {
} }
} }
Color get dividerColor => _dividerPaint.color;
set dividerColor(Color value) {
if (value == _dividerPaint.color)
return;
_dividerPaint.color = value;
markNeedsPaint();
}
final double _dividerThickness; final double _dividerThickness;
final Paint _dividerPaint = Paint() final Paint _dividerPaint;
..color = _kButtonDividerColor
..style = PaintingStyle.fill;
@override @override
void attach(PipelineOwner owner) { void attach(PipelineOwner owner) {
...@@ -777,6 +816,7 @@ class _CupertinoAlertContentSection extends StatelessWidget { ...@@ -777,6 +816,7 @@ class _CupertinoAlertContentSection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> titleContentGroup = <Widget>[]; final List<Widget> titleContentGroup = <Widget>[];
if (title != null) { if (title != null) {
titleContentGroup.add(Padding( titleContentGroup.add(Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
...@@ -994,14 +1034,21 @@ class _CupertinoAlertActionsRenderWidget extends MultiChildRenderObjectWidget { ...@@ -994,14 +1034,21 @@ class _CupertinoAlertActionsRenderWidget extends MultiChildRenderObjectWidget {
RenderObject createRenderObject(BuildContext context) { RenderObject createRenderObject(BuildContext context) {
return _RenderCupertinoAlertActions( return _RenderCupertinoAlertActions(
dividerThickness: _dividerThickness, dividerThickness: _dividerThickness,
dividerColor: _kButtonDividerColor,
hasCancelButton: _hasCancelButton, hasCancelButton: _hasCancelButton,
backgroundColor: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
pressedColor: CupertinoDynamicColor.resolve(_kPressedColor, context),
); );
} }
@override @override
void updateRenderObject(BuildContext context, _RenderCupertinoAlertActions renderObject) { void updateRenderObject(BuildContext context, _RenderCupertinoAlertActions renderObject) {
renderObject.dividerThickness = _dividerThickness; renderObject
renderObject.hasCancelButton = _hasCancelButton; ..dividerThickness = _dividerThickness
..dividerColor = _kButtonDividerColor
..hasCancelButton = _hasCancelButton
..backgroundColor = CupertinoDynamicColor.resolve(_kBackgroundColor, context)
..pressedColor = CupertinoDynamicColor.resolve(_kPressedColor, context);
} }
} }
...@@ -1029,11 +1076,22 @@ class _RenderCupertinoAlertActions extends RenderBox ...@@ -1029,11 +1076,22 @@ class _RenderCupertinoAlertActions extends RenderBox
_RenderCupertinoAlertActions({ _RenderCupertinoAlertActions({
List<RenderBox> children, List<RenderBox> children,
double dividerThickness = 0.0, double dividerThickness = 0.0,
@required Color dividerColor,
bool hasCancelButton = false, bool hasCancelButton = false,
Color backgroundColor,
Color pressedColor,
}) : _dividerThickness = dividerThickness, }) : _dividerThickness = dividerThickness,
_hasCancelButton = hasCancelButton { _hasCancelButton = hasCancelButton,
addAll(children); _buttonBackgroundPaint = Paint()
} ..style = PaintingStyle.fill
..color = backgroundColor,
_pressedButtonBackgroundPaint = Paint()
..style = PaintingStyle.fill
..color = pressedColor,
_dividerPaint = Paint()
..color = dividerColor
..style = PaintingStyle.fill
{ addAll(children); }
// The thickness of the divider between buttons. // The thickness of the divider between buttons.
double get dividerThickness => _dividerThickness; double get dividerThickness => _dividerThickness;
...@@ -1047,6 +1105,35 @@ class _RenderCupertinoAlertActions extends RenderBox ...@@ -1047,6 +1105,35 @@ class _RenderCupertinoAlertActions extends RenderBox
markNeedsLayout(); markNeedsLayout();
} }
Color get backgroundColor => _buttonBackgroundPaint.color;
set backgroundColor(Color newValue) {
if (newValue == _buttonBackgroundPaint.color) {
return;
}
_buttonBackgroundPaint.color = newValue;
markNeedsPaint();
}
Color get pressedColor => _pressedButtonBackgroundPaint.color;
set pressedColor(Color newValue) {
if (newValue == _pressedButtonBackgroundPaint.color) {
return;
}
_pressedButtonBackgroundPaint.color = newValue;
markNeedsPaint();
}
Color get dividerColor => _dividerPaint.color;
set dividerColor(Color value) {
if (value == _dividerPaint.color) {
return;
}
_dividerPaint.color = value;
markNeedsPaint();
}
bool _hasCancelButton; bool _hasCancelButton;
bool get hasCancelButton => _hasCancelButton; bool get hasCancelButton => _hasCancelButton;
set hasCancelButton(bool newValue) { set hasCancelButton(bool newValue) {
...@@ -1058,17 +1145,10 @@ class _RenderCupertinoAlertActions extends RenderBox ...@@ -1058,17 +1145,10 @@ class _RenderCupertinoAlertActions extends RenderBox
markNeedsLayout(); markNeedsLayout();
} }
final Paint _buttonBackgroundPaint = Paint() final Paint _buttonBackgroundPaint;
..color = _kBackgroundColor final Paint _pressedButtonBackgroundPaint;
..style = PaintingStyle.fill;
final Paint _pressedButtonBackgroundPaint = Paint()
..color = _kPressedColor
..style = PaintingStyle.fill;
final Paint _dividerPaint = Paint() final Paint _dividerPaint;
..color = _kButtonDividerColor
..style = PaintingStyle.fill;
@override @override
void setupParentData(RenderBox child) { void setupParentData(RenderBox child) {
......
...@@ -1089,14 +1089,14 @@ const CupertinoSystemColorsData _kSystemColorsFallback = CupertinoSystemColorsDa ...@@ -1089,14 +1089,14 @@ const CupertinoSystemColorsData _kSystemColorsFallback = CupertinoSystemColorsDa
darkHighContrastElevatedColor: Color.fromARGB(255, 36, 36, 38), darkHighContrastElevatedColor: Color.fromARGB(255, 36, 36, 38),
), ),
secondarySystemGroupedBackground: CupertinoDynamicColor( secondarySystemGroupedBackground: CupertinoDynamicColor(
color: Color.fromARGB(255, 242, 242, 247), color: Color.fromARGB(255, 255, 255, 255),
darkColor: Color.fromARGB(255, 0, 0, 0), darkColor: Color.fromARGB(255, 28, 28, 30),
highContrastColor: Color.fromARGB(255, 235, 235, 240), highContrastColor: Color.fromARGB(255, 255, 255, 255),
darkHighContrastColor: Color.fromARGB(255, 0, 0, 0), darkHighContrastColor: Color.fromARGB(255, 36, 36, 38),
elevatedColor: Color.fromARGB(255, 242, 242, 247), elevatedColor: Color.fromARGB(255, 255, 255, 255),
darkElevatedColor: Color.fromARGB(255, 28, 28, 30), darkElevatedColor: Color.fromARGB(255, 44, 44, 46),
highContrastElevatedColor: Color.fromARGB(255, 235, 235, 240), highContrastElevatedColor: Color.fromARGB(255, 255, 255, 255),
darkHighContrastElevatedColor: Color.fromARGB(255, 36, 36, 38), darkHighContrastElevatedColor: Color.fromARGB(255, 54, 54, 56),
), ),
tertiarySystemGroupedBackground: CupertinoDynamicColor( tertiarySystemGroupedBackground: CupertinoDynamicColor(
color: Color.fromARGB(255, 242, 242, 247), color: Color.fromARGB(255, 242, 242, 247),
......
...@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart'; ...@@ -7,6 +7,7 @@ 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() {
...@@ -61,7 +62,73 @@ void main() { ...@@ -61,7 +62,73 @@ void main() {
final DefaultTextStyle widget = tester.widget(find.widgetWithText(DefaultTextStyle, 'Ok')); final DefaultTextStyle widget = tester.widget(find.widgetWithText(DefaultTextStyle, 'Ok'));
expect(widget.style.color, CupertinoColors.destructiveRed); expect(widget.style.color, const CupertinoDynamicColor.withBrightnessAndContrast(
color: Color.fromARGB(255, 255, 59, 48),
darkColor: Color.fromARGB(255, 255, 69, 58),
highContrastColor: Color.fromARGB(255, 215, 0, 21),
darkHighContrastColor: Color.fromARGB(255, 255, 105, 97),
));
});
testWidgets('Action sheet dark mode', (WidgetTester tester) async {
final Widget action = CupertinoActionSheetAction(
child: const Text('action'),
onPressed: () {},
);
Brightness brightness = Brightness.light;
StateSetter stateSetter;
TextStyle actionTextStyle(String text) {
return tester.widget<DefaultTextStyle>(
find.descendant(
of: find.widgetWithText(CupertinoActionSheetAction, text),
matching: find.byType(DefaultTextStyle),
)
).style;
}
await tester.pumpWidget(
createAppWithButtonThatLaunchesActionSheet(
StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
stateSetter = setter;
return CupertinoTheme(
data: CupertinoThemeData(
brightness: brightness,
primaryColor: const CupertinoDynamicColor.withBrightnessAndContrast(
color: Color.fromARGB(255, 0, 122, 255),
darkColor: Color.fromARGB(255, 10, 132, 255),
highContrastColor: Color.fromARGB(255, 0, 64, 221),
darkHighContrastColor: Color.fromARGB(255, 64, 156, 255),
),
),
child: CupertinoActionSheet(actions: <Widget>[action]),
);
},
),
),
);
await tester.tap(find.text('Go'));
await tester.pump();
// Draw the overlay using the light variant.
expect(find.byType(CupertinoActionSheet), paints..rect(color: const Color(0x66000000)));
expect(
actionTextStyle('action').color.value,
const Color.fromARGB(255, 0, 122, 255).value,
);
stateSetter(() { brightness = Brightness.dark; });
await tester.pump();
// Draw the overlay using the dark variant.
expect(find.byType(CupertinoActionSheet), paints..rect(color: const Color(0x99000000)));
expect(
actionTextStyle('action').color.value,
const Color.fromARGB(255, 10, 132, 255).value,
);
}); });
testWidgets('Action sheet default text style', (WidgetTester tester) async { testWidgets('Action sheet default text style', (WidgetTester tester) async {
......
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