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 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'bottom_tab_bar.dart';
import 'colors.dart';
import 'theme.dart';
/// Coordinates tab selection between a [CupertinoTabBar] and a [CupertinoTabScaffold].
......@@ -398,7 +399,8 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
return DecoratedBox(
decoration: BoxDecoration(
color: widget.backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
color: CupertinoDynamicColor.resolve(widget.backgroundColor, context)
?? CupertinoTheme.of(context).scaffoldBackgroundColor,
),
child: Stack(
children: <Widget>[
......
......@@ -13,9 +13,12 @@ import 'theme.dart';
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(
color: CupertinoColors.lightBackgroundGray,
color: CupertinoDynamicColor.withBrightness(
color: Color(0x33000000),
darkColor: Color(0x33FFFFFF),
),
style: BorderStyle.solid,
width: 0.0,
);
......@@ -25,16 +28,27 @@ const Border _kDefaultRoundedBorder = Border(
left: _kDefaultRoundedBorderSide,
right: _kDefaultRoundedBorderSide,
);
// Counted manually on magnified simulator.
const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration(
color: CupertinoDynamicColor.withBrightness(
color: CupertinoColors.white,
darkColor: CupertinoColors.black,
),
border: _kDefaultRoundedBorder,
borderRadius: BorderRadius.all(Radius.circular(4.0)),
borderRadius: BorderRadius.all(Radius.circular(5.0)),
);
const Color _kDisabledBackground = CupertinoDynamicColor.withBrightness(
color: Color(0xFFFAFAFA),
darkColor: Color(0xFF050505),
);
// Value extracted via color reader from iOS simulator.
const Color _kSelectionHighlightColor = Color(0x667FAACF);
const Color _kInactiveTextColor = Color(0xFFC2C2C2);
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
// rendered for text on Android so it's positioning more accurately matches the
......@@ -200,8 +214,8 @@ class CupertinoTextField extends StatefulWidget {
this.padding = const EdgeInsets.all(6.0),
this.placeholder,
this.placeholderStyle = const TextStyle(
fontWeight: FontWeight.w300,
color: _kInactiveTextColor,
fontWeight: FontWeight.w400,
color: CupertinoColors.placeholderText,
),
this.prefix,
this.prefixMode = OverlayVisibilityMode.always,
......@@ -772,12 +786,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
if (widget.onChanged != null && textChanged)
widget.onChanged(_effectiveController.text);
} : null,
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 6.0),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: Icon(
CupertinoIcons.clear_thick_circled,
size: 18.0,
color: _kInactiveTextColor,
color: CupertinoDynamicColor.resolve(_kClearButtonColor, context),
),
),
),
......@@ -798,17 +812,49 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
formatters.add(LengthLimitingTextInputFormatter(widget.maxLength));
}
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 Color cursorColor = widget.cursorColor ?? themeData.primaryColor;
final Color disabledColor = CupertinoTheme.of(context).brightness == Brightness.light
? _kDisabledBackground
: CupertinoColors.darkBackgroundGray;
final Color cursorColor = CupertinoDynamicColor.resolve(widget.cursorColor, context) ?? themeData.primaryColor;
final Color disabledColor = CupertinoDynamicColor.resolve(_kDisabledBackground, context);
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
? widget.decoration
: widget.decoration?.copyWith(color: widget.decoration?.color ?? disabledColor);
final BoxDecoration effectiveDecoration = widget.decoration?.copyWith(
border: resolvedBorder,
color: enabled ? decorationColor : (decorationColor ?? disabledColor),
);
final Widget paddedEditable = Padding(
padding: widget.padding,
......@@ -833,7 +879,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
maxLines: widget.maxLines,
minLines: widget.minLines,
expands: widget.expands,
selectionColor: _kSelectionHighlightColor,
selectionColor: CupertinoTheme.of(context).primaryColor.withOpacity(0.2),
selectionControls: widget.selectionEnabled
? cupertinoTextSelectionControls : null,
onChanged: widget.onChanged,
......@@ -848,7 +894,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
cursorOpacityAnimates: true,
cursorOffset: cursorOffset,
paintCursorAboveText: true,
backgroundCursorColor: CupertinoColors.inactiveGray,
backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance,
dragStartBehavior: widget.dragStartBehavior,
......
......@@ -11,12 +11,13 @@ import 'package:flutter/rendering.dart';
import 'button.dart';
import 'colors.dart';
import 'localizations.dart';
import 'theme.dart';
// Read off from the output on iOS 12. This color does not vary with the
// application's theme color.
const Color _kHandlesColor = Color(0xFF136FE0);
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
// screen.
......@@ -31,13 +32,16 @@ const double _kToolbarContentDistance = 8.0;
// Values derived from https://developer.apple.com/design/resources/.
// 92% Opacity ~= 0xEB
// Values extracted from https://developer.apple.com/design/resources/.
// The height of the toolbar, including the arrow.
const double _kToolbarHeight = 43.0;
const Color _kToolbarBackgroundColor = Color(0xEB202020);
const Color _kToolbarDividerColor = Color(0xFF808080);
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);
// 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(
inherit: false,
......@@ -47,6 +51,9 @@ const TextStyle _kToolbarButtonFontStyle = TextStyle(
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.
///
/// Typically displays buttons for text manipulation, e.g. copying and pasting text.
......@@ -247,12 +254,14 @@ class _ToolbarRenderBox extends RenderShiftedBox {
/// Draws a single text selection handle with a bar and a ball.
class _TextSelectionHandlePainter extends CustomPainter {
const _TextSelectionHandlePainter();
const _TextSelectionHandlePainter(this.color);
final Color color;
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint()
..color = _kHandlesColor
..color = color
..strokeWidth = 2.0;
canvas.drawCircle(
const Offset(_kSelectionHandleRadius, _kSelectionHandleRadius),
......@@ -274,7 +283,7 @@ class _TextSelectionHandlePainter extends CustomPainter {
}
@override
bool shouldRepaint(_TextSelectionHandlePainter oldPainter) => false;
bool shouldRepaint(_TextSelectionHandlePainter oldPainter) => color != oldPainter.color;
}
class _CupertinoTextSelectionControls extends TextSelectionControls {
......@@ -379,8 +388,8 @@ class _CupertinoTextSelectionControls extends TextSelectionControls {
final Widget handle = SizedBox.fromSize(
size: desiredSize,
child: const CustomPaint(
painter: _TextSelectionHandlePainter(),
child: CustomPaint(
painter: _TextSelectionHandlePainter(CupertinoTheme.of(context).primaryColor),
),
);
......
......@@ -174,6 +174,59 @@ void main() {
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 {
// Regression testing for https://github.com/flutter/flutter/issues/28457.
......
......@@ -327,7 +327,7 @@ void main() {
),
);
final BoxDecoration decoration = tester.widget<DecoratedBox>(
BoxDecoration decoration = tester.widget<DecoratedBox>(
find.descendant(
of: find.byType(CupertinoTextField),
matching: find.byType(DecoratedBox),
......@@ -336,11 +336,37 @@ void main() {
expect(
decoration.borderRadius,
BorderRadius.circular(4.0),
BorderRadius.circular(5),
);
expect(
decoration.border.bottom.color,
CupertinoColors.lightBackgroundGray,
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(
of: find.byType(CupertinoTextField),
matching: find.byType(DecoratedBox),
),
).decoration;
expect(
decoration.borderRadius,
BorderRadius.circular(5),
);
expect(
decoration.border.bottom.color.value,
0x33FFFFFF,
);
},
);
......@@ -479,7 +505,7 @@ void main() {
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile(
'text_field_cursor_test.cupertino.0.png',
version: 2,
version: 3,
),
);
});
......@@ -512,7 +538,7 @@ void main() {
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile(
'text_field_cursor_test.cupertino.1.png',
version: 2,
version: 3,
),
);
});
......@@ -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(
'placeholders are lightly colored and disappears once typing starts',
(WidgetTester tester) async {
......@@ -583,7 +626,7 @@ void main() {
);
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.pump();
......@@ -2701,8 +2744,8 @@ void main() {
).decoration;
expect(
decoration.border.bottom.color,
CupertinoColors.lightBackgroundGray, // Border color is the same regardless.
decoration.border.bottom.color.value,
0x33FFFFFF,
);
await tester.enterText(find.byType(CupertinoTextField), 'smoked meat');
......@@ -2878,19 +2921,38 @@ void main() {
});
testWidgets('cursor can override color from theme', (WidgetTester tester) async {
const CupertinoDynamicColor cursorColor = CupertinoDynamicColor.withBrightness(
color: Color(0x12345678),
darkColor: Color(0x87654321),
);
await tester.pumpWidget(
const CupertinoApp(
theme: CupertinoThemeData(),
home: Center(
child: CupertinoTextField(
cursorColor: Color(0xFFF44336),
cursorColor: cursorColor,
),
),
),
);
final EditableText editableText = tester.firstWidget(find.byType(EditableText));
expect(editableText.cursorColor, const Color(0xFFF44336));
EditableText editableText = tester.firstWidget(find.byType(EditableText));
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 {
......@@ -2965,9 +3027,12 @@ void main() {
child: SizedBox(
width: 200,
height: 200,
child: CupertinoTextField(
controller: TextEditingController(text: 'lorem'),
enabled: false,
child: RepaintBoundary(
key: const ValueKey<int>(1),
child: CupertinoTextField(
controller: TextEditingController(text: 'lorem'),
enabled: false,
),
),
),
),
......@@ -2976,10 +3041,10 @@ void main() {
);
await expectLater(
find.byType(CupertinoTextField),
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile(
'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