Unverified Commit 9529d678 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Cupertino { TabScafold, TextSelection, TextField } dark mode & minor fidelity update (#41431)

parent 1946fc4d
0728c1b6e968602e173d0153a88d9cfb932d8c9b fc0d93237ae0c7d27c1b8a07927a35434f1cf4e4
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'bottom_tab_bar.dart'; import 'bottom_tab_bar.dart';
import 'colors.dart';
import 'theme.dart'; import 'theme.dart';
/// Coordinates tab selection between a [CupertinoTabBar] and a [CupertinoTabScaffold]. /// Coordinates tab selection between a [CupertinoTabBar] and a [CupertinoTabScaffold].
...@@ -398,7 +399,8 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> { ...@@ -398,7 +399,8 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: widget.backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor, color: CupertinoDynamicColor.resolve(widget.backgroundColor, context)
?? CupertinoTheme.of(context).scaffoldBackgroundColor,
), ),
child: Stack( child: Stack(
children: <Widget>[ children: <Widget>[
......
...@@ -13,9 +13,12 @@ import 'theme.dart'; ...@@ -13,9 +13,12 @@ import 'theme.dart';
export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization; export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization;
// Value extracted via color reader from iOS simulator. // Value inspected from Xcode 11 & iOS 13.0 Simulator.
const BorderSide _kDefaultRoundedBorderSide = BorderSide( const BorderSide _kDefaultRoundedBorderSide = BorderSide(
color: CupertinoColors.lightBackgroundGray, color: CupertinoDynamicColor.withBrightness(
color: Color(0x33000000),
darkColor: Color(0x33FFFFFF),
),
style: BorderStyle.solid, style: BorderStyle.solid,
width: 0.0, width: 0.0,
); );
...@@ -25,16 +28,27 @@ const Border _kDefaultRoundedBorder = Border( ...@@ -25,16 +28,27 @@ const Border _kDefaultRoundedBorder = Border(
left: _kDefaultRoundedBorderSide, left: _kDefaultRoundedBorderSide,
right: _kDefaultRoundedBorderSide, right: _kDefaultRoundedBorderSide,
); );
// Counted manually on magnified simulator.
const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration( const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration(
color: CupertinoDynamicColor.withBrightness(
color: CupertinoColors.white,
darkColor: CupertinoColors.black,
),
border: _kDefaultRoundedBorder, border: _kDefaultRoundedBorder,
borderRadius: BorderRadius.all(Radius.circular(4.0)), borderRadius: BorderRadius.all(Radius.circular(5.0)),
); );
// Value extracted via color reader from iOS simulator. const Color _kDisabledBackground = CupertinoDynamicColor.withBrightness(
const Color _kSelectionHighlightColor = Color(0x667FAACF); color: Color(0xFFFAFAFA),
const Color _kInactiveTextColor = Color(0xFFC2C2C2); darkColor: Color(0xFF050505),
const Color _kDisabledBackground = Color(0xFFFAFAFA); );
// Value inspected from Xcode 11 & iOS 13.0 Simulator.
// Note it may not be consistent with https://developer.apple.com/design/resources/.
const CupertinoDynamicColor _kClearButtonColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFF636366),
darkColor: Color(0xFFAEAEB2),
);
// An eyeballed value that moves the cursor slightly left of where it is // An eyeballed value that moves the cursor slightly left of where it is
// rendered for text on Android so it's positioning more accurately matches the // rendered for text on Android so it's positioning more accurately matches the
...@@ -200,8 +214,8 @@ class CupertinoTextField extends StatefulWidget { ...@@ -200,8 +214,8 @@ class CupertinoTextField extends StatefulWidget {
this.padding = const EdgeInsets.all(6.0), this.padding = const EdgeInsets.all(6.0),
this.placeholder, this.placeholder,
this.placeholderStyle = const TextStyle( this.placeholderStyle = const TextStyle(
fontWeight: FontWeight.w300, fontWeight: FontWeight.w400,
color: _kInactiveTextColor, color: CupertinoColors.placeholderText,
), ),
this.prefix, this.prefix,
this.prefixMode = OverlayVisibilityMode.always, this.prefixMode = OverlayVisibilityMode.always,
...@@ -772,12 +786,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -772,12 +786,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
if (widget.onChanged != null && textChanged) if (widget.onChanged != null && textChanged)
widget.onChanged(_effectiveController.text); widget.onChanged(_effectiveController.text);
} : null, } : null,
child: const Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 6.0), padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: Icon( child: Icon(
CupertinoIcons.clear_thick_circled, CupertinoIcons.clear_thick_circled,
size: 18.0, size: 18.0,
color: _kInactiveTextColor, color: CupertinoDynamicColor.resolve(_kClearButtonColor, context),
), ),
), ),
), ),
...@@ -798,17 +812,49 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -798,17 +812,49 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
formatters.add(LengthLimitingTextInputFormatter(widget.maxLength)); formatters.add(LengthLimitingTextInputFormatter(widget.maxLength));
} }
final CupertinoThemeData themeData = CupertinoTheme.of(context); final CupertinoThemeData themeData = CupertinoTheme.of(context);
final TextStyle textStyle = themeData.textTheme.textStyle.merge(widget.style);
final TextStyle placeholderStyle = textStyle.merge(widget.placeholderStyle); final TextStyle resolvedStyle = widget.style?.copyWith(
color: CupertinoDynamicColor.resolve(widget.style?.color, context),
backgroundColor: CupertinoDynamicColor.resolve(widget.style?.backgroundColor, context),
);
final TextStyle textStyle = themeData.textTheme.textStyle.merge(resolvedStyle);
final TextStyle resolvedPlaceholderStyle = widget.placeholderStyle?.copyWith(
color: CupertinoDynamicColor.resolve(widget.placeholderStyle?.color, context),
backgroundColor: CupertinoDynamicColor.resolve(widget.placeholderStyle?.backgroundColor, context),
);
final TextStyle placeholderStyle = textStyle.merge(resolvedPlaceholderStyle);
final Brightness keyboardAppearance = widget.keyboardAppearance ?? themeData.brightness; final Brightness keyboardAppearance = widget.keyboardAppearance ?? themeData.brightness;
final Color cursorColor = widget.cursorColor ?? themeData.primaryColor; final Color cursorColor = CupertinoDynamicColor.resolve(widget.cursorColor, context) ?? themeData.primaryColor;
final Color disabledColor = CupertinoTheme.of(context).brightness == Brightness.light final Color disabledColor = CupertinoDynamicColor.resolve(_kDisabledBackground, context);
? _kDisabledBackground
: CupertinoColors.darkBackgroundGray; final Color decorationColor = CupertinoDynamicColor.resolve(widget.decoration?.color, context);
final BoxBorder border = widget.decoration?.border;
Border resolvedBorder = border;
if (border is Border) {
BorderSide resolveBorderSide(BorderSide side) {
return side == BorderSide.none
? side
: side.copyWith(color: CupertinoDynamicColor.resolve(side.color, context));
}
resolvedBorder = border == null || border.runtimeType != Border
? border
: Border(
top: resolveBorderSide(border.top),
left: resolveBorderSide(border.left),
bottom: resolveBorderSide(border.bottom),
right: resolveBorderSide(border.right),
);
}
final BoxDecoration effectiveDecoration = enabled final BoxDecoration effectiveDecoration = widget.decoration?.copyWith(
? widget.decoration border: resolvedBorder,
: widget.decoration?.copyWith(color: widget.decoration?.color ?? disabledColor); color: enabled ? decorationColor : (decorationColor ?? disabledColor),
);
final Widget paddedEditable = Padding( final Widget paddedEditable = Padding(
padding: widget.padding, padding: widget.padding,
...@@ -833,7 +879,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -833,7 +879,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
maxLines: widget.maxLines, maxLines: widget.maxLines,
minLines: widget.minLines, minLines: widget.minLines,
expands: widget.expands, expands: widget.expands,
selectionColor: _kSelectionHighlightColor, selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2),
selectionControls: widget.selectionEnabled selectionControls: widget.selectionEnabled
? cupertinoTextSelectionControls : null, ? cupertinoTextSelectionControls : null,
onChanged: widget.onChanged, onChanged: widget.onChanged,
...@@ -848,7 +894,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -848,7 +894,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
cursorOpacityAnimates: true, cursorOpacityAnimates: true,
cursorOffset: cursorOffset, cursorOffset: cursorOffset,
paintCursorAboveText: true, paintCursorAboveText: true,
backgroundCursorColor: CupertinoColors.inactiveGray, backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
scrollPadding: widget.scrollPadding, scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance, keyboardAppearance: keyboardAppearance,
dragStartBehavior: widget.dragStartBehavior, dragStartBehavior: widget.dragStartBehavior,
......
...@@ -11,12 +11,13 @@ import 'package:flutter/rendering.dart'; ...@@ -11,12 +11,13 @@ import 'package:flutter/rendering.dart';
import 'button.dart'; import 'button.dart';
import 'colors.dart'; import 'colors.dart';
import 'localizations.dart'; import 'localizations.dart';
import 'theme.dart';
// Read off from the output on iOS 12. This color does not vary with the // Read off from the output on iOS 12. This color does not vary with the
// application's theme color. // application's theme color.
const Color _kHandlesColor = Color(0xFF136FE0);
const double _kSelectionHandleOverlap = 1.5; const double _kSelectionHandleOverlap = 1.5;
const double _kSelectionHandleRadius = 5.5; // Extracted from https://developer.apple.com/design/resources/.
const double _kSelectionHandleRadius = 6;
// Minimal padding from all edges of the selection toolbar to all edges of the // Minimal padding from all edges of the selection toolbar to all edges of the
// screen. // screen.
...@@ -31,13 +32,16 @@ const double _kToolbarContentDistance = 8.0; ...@@ -31,13 +32,16 @@ const double _kToolbarContentDistance = 8.0;
// Values derived from https://developer.apple.com/design/resources/. // Values derived from https://developer.apple.com/design/resources/.
// 92% Opacity ~= 0xEB // 92% Opacity ~= 0xEB
// Values extracted from https://developer.apple.com/design/resources/.
// The height of the toolbar, including the arrow. // The height of the toolbar, including the arrow.
const double _kToolbarHeight = 43.0; const double _kToolbarHeight = 43.0;
const Color _kToolbarBackgroundColor = Color(0xEB202020);
const Color _kToolbarDividerColor = Color(0xFF808080);
const Size _kToolbarArrowSize = Size(14.0, 7.0); const Size _kToolbarArrowSize = Size(14.0, 7.0);
const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 10.0, horizontal: 18.0);
const Radius _kToolbarBorderRadius = Radius.circular(8); const Radius _kToolbarBorderRadius = Radius.circular(8);
// Colors extracted from https://developer.apple.com/design/resources/.
// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507.
const Color _kToolbarBackgroundColor = Color(0xEB202020);
const Color _kToolbarDividerColor = Color(0xFF808080);
const TextStyle _kToolbarButtonFontStyle = TextStyle( const TextStyle _kToolbarButtonFontStyle = TextStyle(
inherit: false, inherit: false,
...@@ -47,6 +51,9 @@ const TextStyle _kToolbarButtonFontStyle = TextStyle( ...@@ -47,6 +51,9 @@ const TextStyle _kToolbarButtonFontStyle = TextStyle(
color: CupertinoColors.white, color: CupertinoColors.white,
); );
// Eyeballed value.
const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 10.0, horizontal: 18.0);
/// An iOS-style toolbar that appears in response to text selection. /// An iOS-style toolbar that appears in response to text selection.
/// ///
/// Typically displays buttons for text manipulation, e.g. copying and pasting text. /// Typically displays buttons for text manipulation, e.g. copying and pasting text.
...@@ -247,12 +254,14 @@ class _ToolbarRenderBox extends RenderShiftedBox { ...@@ -247,12 +254,14 @@ class _ToolbarRenderBox extends RenderShiftedBox {
/// Draws a single text selection handle with a bar and a ball. /// Draws a single text selection handle with a bar and a ball.
class _TextSelectionHandlePainter extends CustomPainter { class _TextSelectionHandlePainter extends CustomPainter {
const _TextSelectionHandlePainter(); const _TextSelectionHandlePainter(this.color);
final Color color;
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final Paint paint = Paint() final Paint paint = Paint()
..color = _kHandlesColor ..color = color
..strokeWidth = 2.0; ..strokeWidth = 2.0;
canvas.drawCircle( canvas.drawCircle(
const Offset(_kSelectionHandleRadius, _kSelectionHandleRadius), const Offset(_kSelectionHandleRadius, _kSelectionHandleRadius),
...@@ -274,7 +283,7 @@ class _TextSelectionHandlePainter extends CustomPainter { ...@@ -274,7 +283,7 @@ class _TextSelectionHandlePainter extends CustomPainter {
} }
@override @override
bool shouldRepaint(_TextSelectionHandlePainter oldPainter) => false; bool shouldRepaint(_TextSelectionHandlePainter oldPainter) => color != oldPainter.color;
} }
class _CupertinoTextSelectionControls extends TextSelectionControls { class _CupertinoTextSelectionControls extends TextSelectionControls {
...@@ -379,8 +388,8 @@ class _CupertinoTextSelectionControls extends TextSelectionControls { ...@@ -379,8 +388,8 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
final Widget handle = SizedBox.fromSize( final Widget handle = SizedBox.fromSize(
size: desiredSize, size: desiredSize,
child: const CustomPaint( child: CustomPaint(
painter: _TextSelectionHandlePainter(), painter: _TextSelectionHandlePainter(CupertinoTheme.of(context).primaryColor),
), ),
); );
......
...@@ -174,6 +174,59 @@ void main() { ...@@ -174,6 +174,59 @@ void main() {
expect(tab2.text.style.color.value, CupertinoColors.systemRed.darkColor.value); expect(tab2.text.style.color.value, CupertinoColors.systemRed.darkColor.value);
}); });
testWidgets('dark mode background color', (WidgetTester tester) async {
const CupertinoDynamicColor backgroundColor = CupertinoDynamicColor.withBrightness(
color: Color(0xFF123456),
darkColor: Color(0xFF654321),
);
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.light),
home: CupertinoTabScaffold(
backgroundColor: backgroundColor,
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const Placeholder();
},
),
),
);
// The DecoratedBox with the smallest depth is the DecoratedBox of the
// CupertinoTabScaffold.
BoxDecoration tabDecoration = tester.firstWidget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoTabScaffold),
matching: find.byType(DecoratedBox),
)
).decoration;
expect(tabDecoration.color.value, backgroundColor.color.value);
// Dark mode
await tester.pumpWidget(
CupertinoApp(
theme: const CupertinoThemeData(brightness: Brightness.dark),
home: CupertinoTabScaffold(
backgroundColor: backgroundColor,
tabBar: _buildTabBar(),
tabBuilder: (BuildContext context, int index) {
return const Placeholder();
},
),
),
);
tabDecoration = tester.firstWidget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoTabScaffold),
matching: find.byType(DecoratedBox),
)
).decoration;
expect(tabDecoration.color.value, backgroundColor.darkColor.value);
});
testWidgets('Does not lose state when focusing on text input', (WidgetTester tester) async { testWidgets('Does not lose state when focusing on text input', (WidgetTester tester) async {
// Regression testing for https://github.com/flutter/flutter/issues/28457. // Regression testing for https://github.com/flutter/flutter/issues/28457.
......
...@@ -327,7 +327,33 @@ void main() { ...@@ -327,7 +327,33 @@ void main() {
), ),
); );
final BoxDecoration decoration = tester.widget<DecoratedBox>( BoxDecoration decoration = tester.widget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoTextField),
matching: find.byType(DecoratedBox),
),
).decoration;
expect(
decoration.borderRadius,
BorderRadius.circular(5),
);
expect(
decoration.border.bottom.color.value,
0x33000000,
);
// Dark mode.
await tester.pumpWidget(
const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.dark),
home: Center(
child: CupertinoTextField(),
),
),
);
decoration = tester.widget<DecoratedBox>(
find.descendant( find.descendant(
of: find.byType(CupertinoTextField), of: find.byType(CupertinoTextField),
matching: find.byType(DecoratedBox), matching: find.byType(DecoratedBox),
...@@ -336,11 +362,11 @@ void main() { ...@@ -336,11 +362,11 @@ void main() {
expect( expect(
decoration.borderRadius, decoration.borderRadius,
BorderRadius.circular(4.0), BorderRadius.circular(5),
); );
expect( expect(
decoration.border.bottom.color, decoration.border.bottom.color.value,
CupertinoColors.lightBackgroundGray, 0x33FFFFFF,
); );
}, },
); );
...@@ -479,7 +505,7 @@ void main() { ...@@ -479,7 +505,7 @@ void main() {
find.byKey(const ValueKey<int>(1)), find.byKey(const ValueKey<int>(1)),
matchesGoldenFile( matchesGoldenFile(
'text_field_cursor_test.cupertino.0.png', 'text_field_cursor_test.cupertino.0.png',
version: 2, version: 3,
), ),
); );
}); });
...@@ -512,7 +538,7 @@ void main() { ...@@ -512,7 +538,7 @@ void main() {
find.byKey(const ValueKey<int>(1)), find.byKey(const ValueKey<int>(1)),
matchesGoldenFile( matchesGoldenFile(
'text_field_cursor_test.cupertino.1.png', 'text_field_cursor_test.cupertino.1.png',
version: 2, version: 3,
), ),
); );
}); });
...@@ -569,6 +595,23 @@ void main() { ...@@ -569,6 +595,23 @@ void main() {
}, },
); );
testWidgets('placeholder dark mode', (WidgetTester tester) async {
await tester.pumpWidget(
const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.dark),
home: Center(
child: CupertinoTextField(
placeholder: 'placeholder',
textAlign: TextAlign.right,
),
),
),
);
final Text placeholder = tester.widget(find.text('placeholder'));
expect(placeholder.style.color.value, CupertinoColors.placeholderText.darkColor.value);
});
testWidgets( testWidgets(
'placeholders are lightly colored and disappears once typing starts', 'placeholders are lightly colored and disappears once typing starts',
(WidgetTester tester) async { (WidgetTester tester) async {
...@@ -583,7 +626,7 @@ void main() { ...@@ -583,7 +626,7 @@ void main() {
); );
final Text placeholder = tester.widget(find.text('placeholder')); final Text placeholder = tester.widget(find.text('placeholder'));
expect(placeholder.style.color, const Color(0xFFC2C2C2)); expect(placeholder.style.color.value, CupertinoColors.placeholderText.color.value);
await tester.enterText(find.byType(CupertinoTextField), 'input'); await tester.enterText(find.byType(CupertinoTextField), 'input');
await tester.pump(); await tester.pump();
...@@ -2701,8 +2744,8 @@ void main() { ...@@ -2701,8 +2744,8 @@ void main() {
).decoration; ).decoration;
expect( expect(
decoration.border.bottom.color, decoration.border.bottom.color.value,
CupertinoColors.lightBackgroundGray, // Border color is the same regardless. 0x33FFFFFF,
); );
await tester.enterText(find.byType(CupertinoTextField), 'smoked meat'); await tester.enterText(find.byType(CupertinoTextField), 'smoked meat');
...@@ -2878,19 +2921,38 @@ void main() { ...@@ -2878,19 +2921,38 @@ void main() {
}); });
testWidgets('cursor can override color from theme', (WidgetTester tester) async { testWidgets('cursor can override color from theme', (WidgetTester tester) async {
const CupertinoDynamicColor cursorColor = CupertinoDynamicColor.withBrightness(
color: Color(0x12345678),
darkColor: Color(0x87654321),
);
await tester.pumpWidget( await tester.pumpWidget(
const CupertinoApp( const CupertinoApp(
theme: CupertinoThemeData(), theme: CupertinoThemeData(),
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
cursorColor: Color(0xFFF44336), cursorColor: cursorColor,
), ),
), ),
), ),
); );
final EditableText editableText = tester.firstWidget(find.byType(EditableText)); EditableText editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.cursorColor, const Color(0xFFF44336)); expect(editableText.cursorColor.value, 0x12345678);
await tester.pumpWidget(
const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.dark),
home: Center(
child: CupertinoTextField(
cursorColor: cursorColor,
),
),
),
);
editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.cursorColor.value, 0x87654321);
}); });
testWidgets('iOS shows selection handles', (WidgetTester tester) async { testWidgets('iOS shows selection handles', (WidgetTester tester) async {
...@@ -2965,6 +3027,8 @@ void main() { ...@@ -2965,6 +3027,8 @@ void main() {
child: SizedBox( child: SizedBox(
width: 200, width: 200,
height: 200, height: 200,
child: RepaintBoundary(
key: const ValueKey<int>(1),
child: CupertinoTextField( child: CupertinoTextField(
controller: TextEditingController(text: 'lorem'), controller: TextEditingController(text: 'lorem'),
enabled: false, enabled: false,
...@@ -2973,13 +3037,14 @@ void main() { ...@@ -2973,13 +3037,14 @@ void main() {
), ),
), ),
), ),
),
); );
await expectLater( await expectLater(
find.byType(CupertinoTextField), find.byKey(const ValueKey<int>(1)),
matchesGoldenFile( matchesGoldenFile(
'text_field_test.disabled.png', 'text_field_test.disabled.png',
version: 0, version: 1,
), ),
); );
}); });
......
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