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';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'interface_level.dart';
import 'scrollbar.dart';
import 'theme.dart';
const TextStyle _kActionSheetActionStyle = TextStyle(
fontFamily: '.SF UI Text',
inherit: false,
fontSize: 20.0,
fontWeight: FontWeight.w400,
color: CupertinoColors.activeBlue,
textBaseline: TextBaseline.alphabetic,
);
......@@ -32,28 +33,52 @@ const TextStyle _kActionSheetContentStyle = TextStyle(
// 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 just the basis for the
// overlay blend mode.
// 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
// 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
// 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
// areas between the content section and actions section, as well as between
// buttons.
const Color _kButtonDividerColor = Color(0x403F3F3F);
const Color _kContentTextColor = Color(0xFF8F8F8F);
const Color _kCancelButtonPressedColor = Color(0xFFEAEAEA);
// Eye-balled from iOS 13 beta simulator.
const Color _kButtonDividerColor = _kContentTextColor;
const double _kBlurAmount = 20.0;
const double _kEdgeHorizontalPadding = 8.0;
......@@ -148,7 +173,7 @@ class CupertinoActionSheet extends StatelessWidget {
/// Typically this is an [CupertinoActionSheetAction] widget.
final Widget cancelButton;
Widget _buildContent() {
Widget _buildContent(BuildContext context) {
final List<Widget> content = <Widget>[];
if (title != null || message != null) {
final Widget titleSection = _CupertinoAlertContentSection(
......@@ -160,7 +185,7 @@ class CupertinoActionSheet extends StatelessWidget {
}
return Container(
color: _kBackgroundColor,
color: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
......@@ -203,9 +228,11 @@ class CupertinoActionSheet extends StatelessWidget {
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: _kBlurAmount, sigmaY: _kBlurAmount),
child: Container(
decoration: _kAlertBlurOverlayDecoration,
decoration: _kAlertBlurOverlayDecoration.copyWith(
color: CupertinoDynamicColor.resolve(_kAlertBlurOverlayColor, context),
),
child: _CupertinoAlertRenderWidget(
contentSection: _buildContent(),
contentSection: Builder(builder: _buildContent),
actionsSection: _buildActions(),
),
),
......@@ -234,16 +261,19 @@ class CupertinoActionSheet extends StatelessWidget {
scopesRoute: true,
explicitChildNodes: true,
label: 'Alert',
child: Container(
width: actionSheetWidth,
margin: const EdgeInsets.symmetric(
horizontal: _kEdgeHorizontalPadding,
vertical: _kEdgeVerticalPadding,
),
child: Column(
children: children,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
child: CupertinoUserInterfaceLevel(
data: CupertinoUserInterfaceLevelData.elevated,
child: Container(
width: actionSheetWidth,
margin: const EdgeInsets.symmetric(
horizontal: _kEdgeHorizontalPadding,
vertical: _kEdgeVerticalPadding,
),
child: Column(
children: children,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
),
),
),
),
......@@ -291,16 +321,16 @@ class CupertinoActionSheetAction extends StatelessWidget {
@override
Widget build(BuildContext context) {
TextStyle style = _kActionSheetActionStyle;
TextStyle style = _kActionSheetActionStyle.copyWith(
color: isDestructiveAction
? CupertinoSystemColors.of(context).systemRed
: CupertinoTheme.of(context).primaryColor,
);
if (isDefaultAction) {
style = style.copyWith(fontWeight: FontWeight.w600);
}
if (isDestructiveAction) {
style = style.copyWith(color: CupertinoColors.destructiveRed);
}
return GestureDetector(
onTap: onPressed,
behavior: HitTestBehavior.opaque,
......@@ -341,34 +371,25 @@ class _CupertinoActionSheetCancelButton extends StatefulWidget {
}
class _CupertinoActionSheetCancelButtonState extends State<_CupertinoActionSheetCancelButton> {
Color _backgroundColor;
@override
void initState() {
_backgroundColor = CupertinoColors.white;
super.initState();
}
bool isBeingPressed = false;
void _onTapDown(TapDownDetails event) {
setState(() {
_backgroundColor = _kCancelButtonPressedColor;
});
setState(() { isBeingPressed = true; });
}
void _onTapUp(TapUpDetails event) {
setState(() {
_backgroundColor = CupertinoColors.white;
});
setState(() { isBeingPressed = false; });
}
void _onTapCancel() {
setState(() {
_backgroundColor = CupertinoColors.white;
});
setState(() { isBeingPressed = false; });
}
@override
Widget build(BuildContext context) {
final Color backgroundColor = isBeingPressed
? _kCancelPressedColor
: CupertinoSystemColors.of(context).secondarySystemGroupedBackground;
return GestureDetector(
excludeFromSemantics: true,
onTapDown: _onTapDown,
......@@ -376,7 +397,7 @@ class _CupertinoActionSheetCancelButtonState extends State<_CupertinoActionSheet
onTapCancel: _onTapCancel,
child: Container(
decoration: BoxDecoration(
color: _backgroundColor,
color: CupertinoDynamicColor.resolve(backgroundColor, context),
borderRadius: BorderRadius.circular(_kCornerRadius),
),
child: widget.child,
......@@ -399,9 +420,16 @@ class _CupertinoAlertRenderWidget extends RenderObjectWidget {
RenderObject createRenderObject(BuildContext context) {
return _RenderCupertinoAlert(
dividerThickness: _kDividerThickness / MediaQuery.of(context).devicePixelRatio,
dividerColor: _kButtonDividerColor,
);
}
@override
void updateRenderObject(BuildContext context, _RenderCupertinoAlert renderObject) {
super.updateRenderObject(context, renderObject);
renderObject.dividerColor = _kButtonDividerColor;
}
@override
RenderObjectElement createElement() {
return _CupertinoAlertRenderElement(this);
......@@ -514,9 +542,14 @@ class _RenderCupertinoAlert extends RenderBox {
RenderBox contentSection,
RenderBox actionsSection,
double dividerThickness = 0.0,
}) : _contentSection = contentSection,
@required Color dividerColor,
}) : assert(dividerColor != null),
_contentSection = contentSection,
_actionsSection = actionsSection,
_dividerThickness = dividerThickness;
_dividerThickness = dividerThickness,
_dividerPaint = Paint()
..color = dividerColor
..style = PaintingStyle.fill;
RenderBox get contentSection => _contentSection;
RenderBox _contentSection;
......@@ -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 Paint _dividerPaint = Paint()
..color = _kButtonDividerColor
..style = PaintingStyle.fill;
final Paint _dividerPaint;
@override
void attach(PipelineOwner owner) {
......@@ -777,6 +816,7 @@ class _CupertinoAlertContentSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<Widget> titleContentGroup = <Widget>[];
if (title != null) {
titleContentGroup.add(Padding(
padding: const EdgeInsets.only(
......@@ -994,14 +1034,21 @@ class _CupertinoAlertActionsRenderWidget extends MultiChildRenderObjectWidget {
RenderObject createRenderObject(BuildContext context) {
return _RenderCupertinoAlertActions(
dividerThickness: _dividerThickness,
dividerColor: _kButtonDividerColor,
hasCancelButton: _hasCancelButton,
backgroundColor: CupertinoDynamicColor.resolve(_kBackgroundColor, context),
pressedColor: CupertinoDynamicColor.resolve(_kPressedColor, context),
);
}
@override
void updateRenderObject(BuildContext context, _RenderCupertinoAlertActions renderObject) {
renderObject.dividerThickness = _dividerThickness;
renderObject.hasCancelButton = _hasCancelButton;
renderObject
..dividerThickness = _dividerThickness
..dividerColor = _kButtonDividerColor
..hasCancelButton = _hasCancelButton
..backgroundColor = CupertinoDynamicColor.resolve(_kBackgroundColor, context)
..pressedColor = CupertinoDynamicColor.resolve(_kPressedColor, context);
}
}
......@@ -1029,11 +1076,22 @@ class _RenderCupertinoAlertActions extends RenderBox
_RenderCupertinoAlertActions({
List<RenderBox> children,
double dividerThickness = 0.0,
@required Color dividerColor,
bool hasCancelButton = false,
Color backgroundColor,
Color pressedColor,
}) : _dividerThickness = dividerThickness,
_hasCancelButton = hasCancelButton {
addAll(children);
}
_hasCancelButton = hasCancelButton,
_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.
double get dividerThickness => _dividerThickness;
......@@ -1047,6 +1105,35 @@ class _RenderCupertinoAlertActions extends RenderBox
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 get hasCancelButton => _hasCancelButton;
set hasCancelButton(bool newValue) {
......@@ -1058,17 +1145,10 @@ class _RenderCupertinoAlertActions extends RenderBox
markNeedsLayout();
}
final Paint _buttonBackgroundPaint = Paint()
..color = _kBackgroundColor
..style = PaintingStyle.fill;
final Paint _pressedButtonBackgroundPaint = Paint()
..color = _kPressedColor
..style = PaintingStyle.fill;
final Paint _buttonBackgroundPaint;
final Paint _pressedButtonBackgroundPaint;
final Paint _dividerPaint = Paint()
..color = _kButtonDividerColor
..style = PaintingStyle.fill;
final Paint _dividerPaint;
@override
void setupParentData(RenderBox child) {
......
......@@ -1089,14 +1089,14 @@ const CupertinoSystemColorsData _kSystemColorsFallback = CupertinoSystemColorsDa
darkHighContrastElevatedColor: Color.fromARGB(255, 36, 36, 38),
),
secondarySystemGroupedBackground: CupertinoDynamicColor(
color: Color.fromARGB(255, 242, 242, 247),
darkColor: Color.fromARGB(255, 0, 0, 0),
highContrastColor: Color.fromARGB(255, 235, 235, 240),
darkHighContrastColor: Color.fromARGB(255, 0, 0, 0),
elevatedColor: Color.fromARGB(255, 242, 242, 247),
darkElevatedColor: Color.fromARGB(255, 28, 28, 30),
highContrastElevatedColor: Color.fromARGB(255, 235, 235, 240),
darkHighContrastElevatedColor: Color.fromARGB(255, 36, 36, 38),
color: Color.fromARGB(255, 255, 255, 255),
darkColor: Color.fromARGB(255, 28, 28, 30),
highContrastColor: Color.fromARGB(255, 255, 255, 255),
darkHighContrastColor: Color.fromARGB(255, 36, 36, 38),
elevatedColor: Color.fromARGB(255, 255, 255, 255),
darkElevatedColor: Color.fromARGB(255, 44, 44, 46),
highContrastElevatedColor: Color.fromARGB(255, 255, 255, 255),
darkHighContrastElevatedColor: Color.fromARGB(255, 54, 54, 56),
),
tertiarySystemGroupedBackground: CupertinoDynamicColor(
color: Color.fromARGB(255, 242, 242, 247),
......
......@@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
void main() {
......@@ -61,7 +62,73 @@ void main() {
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 {
......
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