Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 1cb16a1

Browse files
authored
iOS 16 context menu (#115805)
Updates the text selection toolbar on iOS light mode to match changes in native iOS 16.
1 parent 7966d55 commit 1cb16a1

File tree

4 files changed

+112
-26
lines changed

4 files changed

+112
-26
lines changed

packages/flutter/lib/src/cupertino/text_selection_toolbar.dart

+36-8
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import 'dart:collection';
66
import 'dart:ui' as ui;
77

8-
import 'package:flutter/foundation.dart' show clampDouble;
8+
import 'package:flutter/foundation.dart' show Brightness, clampDouble;
99
import 'package:flutter/rendering.dart';
1010
import 'package:flutter/widgets.dart';
1111

12+
import 'colors.dart';
1213
import 'text_selection_toolbar_button.dart';
14+
import 'theme.dart';
1315

1416
// Values extracted from https://developer.apple.com/design/resources/.
1517
// The height of the toolbar, including the arrow.
@@ -29,9 +31,27 @@ const double _kArrowScreenPadding = 26.0;
2931
// Values extracted from https://developer.apple.com/design/resources/.
3032
const Radius _kToolbarBorderRadius = Radius.circular(8);
3133

32-
// Colors extracted from https://developer.apple.com/design/resources/.
33-
// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507.
34-
const Color _kToolbarDividerColor = Color(0xFF808080);
34+
const CupertinoDynamicColor _kToolbarDividerColor = CupertinoDynamicColor.withBrightness(
35+
// This value was extracted from a screenshot of iOS 16.0.3, as light mode
36+
// didn't appear in the Apple design resources assets linked below.
37+
color: Color(0xFFB6B6B6),
38+
// Color extracted from https://developer.apple.com/design/resources/.
39+
// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507.
40+
darkColor: Color(0xFF808080),
41+
);
42+
43+
// These values were extracted from a screenshot of iOS 16.0.3, as light mode
44+
// didn't appear in the Apple design resources assets linked above.
45+
final BoxDecoration _kToolbarShadow = BoxDecoration(
46+
borderRadius: const BorderRadius.all(_kToolbarBorderRadius),
47+
boxShadow: <BoxShadow>[
48+
BoxShadow(
49+
color: CupertinoColors.black.withOpacity(0.1),
50+
blurRadius: 16.0,
51+
offset: Offset(0, _kToolbarArrowSize.height / 2),
52+
),
53+
],
54+
);
3555

3656
/// The type for a Function that builds a toolbar's container with the given
3757
/// child.
@@ -119,14 +139,23 @@ class CupertinoTextSelectionToolbar extends StatelessWidget {
119139
// Builds a toolbar just like the default iOS toolbar, with the right color
120140
// background and a rounded cutout with an arrow.
121141
static Widget _defaultToolbarBuilder(BuildContext context, Offset anchor, bool isAbove, Widget child) {
122-
return _CupertinoTextSelectionToolbarShape(
142+
final Widget outputChild = _CupertinoTextSelectionToolbarShape(
123143
anchor: anchor,
124144
isAbove: isAbove,
125145
child: DecoratedBox(
126-
decoration: const BoxDecoration(color: _kToolbarDividerColor),
146+
decoration: const BoxDecoration(
147+
color: _kToolbarDividerColor,
148+
),
127149
child: child,
128150
),
129151
);
152+
if (CupertinoTheme.brightnessOf(context) == Brightness.dark) {
153+
return outputChild;
154+
}
155+
return DecoratedBox(
156+
decoration: _kToolbarShadow,
157+
child: outputChild,
158+
);
130159
}
131160

132161
@override
@@ -226,7 +255,6 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox {
226255
super.child,
227256
);
228257

229-
230258
@override
231259
bool get isRepaintBoundary => true;
232260

@@ -485,7 +513,7 @@ class _CupertinoTextSelectionToolbarContentState extends State<_CupertinoTextSel
485513
onPressed: _handleNextPage,
486514
text: '▶',
487515
),
488-
nextButtonDisabled: CupertinoTextSelectionToolbarButton.text(
516+
nextButtonDisabled: const CupertinoTextSelectionToolbarButton.text(
489517
text: '▶',
490518
),
491519
children: widget.children,

packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart

+29-16
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ const TextStyle _kToolbarButtonFontStyle = TextStyle(
1818

1919
// Colors extracted from https://developer.apple.com/design/resources/.
2020
// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507.
21-
const Color _kToolbarBackgroundColor = Color(0xEB202020);
21+
const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.withBrightness(
22+
// This value was extracted from a screenshot of iOS 16.0.3, as light mode
23+
// didn't appear in the Apple design resources assets linked above.
24+
color: Color(0xEB202020),
25+
darkColor: Color(0xEBF7F7F7),
26+
);
27+
28+
const CupertinoDynamicColor _kToolbarTextColor = CupertinoDynamicColor.withBrightness(
29+
color: CupertinoColors.black,
30+
darkColor: CupertinoColors.white,
31+
);
2232

2333
// Eyeballed value.
2434
const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 16.0, horizontal: 18.0);
@@ -33,22 +43,17 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget {
3343
this.onPressed,
3444
required Widget this.child,
3545
}) : assert(child != null),
46+
text = null,
3647
buttonItem = null;
3748

3849
/// Create an instance of [CupertinoTextSelectionToolbarButton] whose child is
3950
/// a [Text] widget styled like the default iOS text selection toolbar button.
40-
CupertinoTextSelectionToolbarButton.text({
51+
const CupertinoTextSelectionToolbarButton.text({
4152
super.key,
4253
this.onPressed,
43-
required String text,
54+
required this.text,
4455
}) : buttonItem = null,
45-
child = Text(
46-
text,
47-
overflow: TextOverflow.ellipsis,
48-
style: _kToolbarButtonFontStyle.copyWith(
49-
color: onPressed != null ? CupertinoColors.white : CupertinoColors.inactiveGray,
50-
),
51-
);
56+
child = null;
5257

5358
/// Create an instance of [CupertinoTextSelectionToolbarButton] from the given
5459
/// [ContextMenuButtonItem].
@@ -59,6 +64,7 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget {
5964
required ContextMenuButtonItem this.buttonItem,
6065
}) : assert(buttonItem != null),
6166
child = null,
67+
text = null,
6268
onPressed = buttonItem.onPressed;
6369

6470
/// {@template flutter.cupertino.CupertinoTextSelectionToolbarButton.child}
@@ -79,6 +85,10 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget {
7985
/// {@endtemplate}
8086
final ContextMenuButtonItem? buttonItem;
8187

88+
/// The text used in the button's label when using
89+
/// [CupertinoTextSelectionToolbarButton.text].
90+
final String? text;
91+
8292
/// Returns the default button label String for the button of the given
8393
/// [ContextMenuButtonItem]'s [ContextMenuButtonType].
8494
static String getButtonLabel(BuildContext context, ContextMenuButtonItem buttonItem) {
@@ -105,12 +115,15 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget {
105115
@override
106116
Widget build(BuildContext context) {
107117
final Widget child = this.child ?? Text(
108-
getButtonLabel(context, buttonItem!),
109-
overflow: TextOverflow.ellipsis,
110-
style: _kToolbarButtonFontStyle.copyWith(
111-
color: onPressed != null ? CupertinoColors.white : CupertinoColors.inactiveGray,
112-
),
113-
);
118+
text ?? getButtonLabel(context, buttonItem!),
119+
overflow: TextOverflow.ellipsis,
120+
style: _kToolbarButtonFontStyle.copyWith(
121+
color: onPressed != null
122+
? _kToolbarTextColor
123+
: CupertinoColors.inactiveGray,
124+
),
125+
);
126+
114127
return CupertinoButton(
115128
borderRadius: null,
116129
color: _kToolbarBackgroundColor,

packages/flutter/test/cupertino/text_field_test.dart

+6-2
Original file line numberDiff line numberDiff line change
@@ -1523,7 +1523,11 @@ void main() {
15231523
await tester.pump(const Duration(milliseconds: 200));
15241524

15251525
Text text = tester.widget<Text>(find.text('Paste'));
1526-
expect(text.style!.color, CupertinoColors.white);
1526+
const CupertinoDynamicColor toolbarTextColor = CupertinoDynamicColor.withBrightness(
1527+
color: CupertinoColors.black,
1528+
darkColor: CupertinoColors.white,
1529+
);
1530+
expect(text.style!.color, toolbarTextColor);
15271531
expect(text.style!.fontSize, 14);
15281532
expect(text.style!.letterSpacing, -0.15);
15291533
expect(text.style!.fontWeight, FontWeight.w400);
@@ -1555,7 +1559,7 @@ void main() {
15551559

15561560
text = tester.widget<Text>(find.text('Paste'));
15571561
// The toolbar buttons' text are still the same style.
1558-
expect(text.style!.color, CupertinoColors.white);
1562+
expect(text.style!.color, toolbarTextColor);
15591563
expect(text.style!.fontSize, 14);
15601564
expect(text.style!.letterSpacing, -0.15);
15611565
expect(text.style!.fontWeight, FontWeight.w400);

packages/flutter/test/cupertino/text_selection_toolbar_test.dart

+41
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ class TestBox extends SizedBox {
6060
static const double itemWidth = 100.0;
6161
}
6262

63+
const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.withBrightness(
64+
color: Color(0xEB202020),
65+
darkColor: Color(0xEBF7F7F7),
66+
);
67+
6368
void main() {
6469
TestWidgetsFlutterBinding.ensureInitialized();
6570

@@ -262,4 +267,40 @@ void main() {
262267
expect(find.text('Paste'), findsNothing);
263268
expect(find.text('Select all'), findsNothing);
264269
}, skip: kIsWeb); // [intended] We do not use Flutter-rendered context menu on the Web.
270+
271+
testWidgets('draws dark buttons in dark mode and light button in light mode', (WidgetTester tester) async {
272+
for (final Brightness brightness in Brightness.values) {
273+
await tester.pumpWidget(
274+
CupertinoApp(
275+
home: Center(
276+
child: Builder(
277+
builder: (BuildContext context) {
278+
return MediaQuery(
279+
data: MediaQuery.of(context).copyWith(platformBrightness: brightness),
280+
child: CupertinoTextSelectionToolbar(
281+
anchorAbove: const Offset(100.0, 0.0),
282+
anchorBelow: const Offset(100.0, 0.0),
283+
children: <Widget>[
284+
CupertinoTextSelectionToolbarButton.text(
285+
onPressed: () {},
286+
text: 'Button',
287+
),
288+
],
289+
),
290+
);
291+
},
292+
),
293+
),
294+
),
295+
);
296+
297+
final Finder buttonFinder = find.byType(CupertinoButton);
298+
expect(find.byType(CupertinoButton), findsOneWidget);
299+
final CupertinoButton button = tester.widget(buttonFinder);
300+
expect(
301+
button.color,
302+
_kToolbarBackgroundColor,
303+
);
304+
}
305+
}, skip: kIsWeb); // [intended] We do not use Flutter-rendered context menu on the Web.
265306
}

0 commit comments

Comments
 (0)