diff --git a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart index a630b3dddf97..c6ad4c2f549f 100644 --- a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart +++ b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart @@ -5,11 +5,13 @@ import 'dart:collection'; import 'dart:ui' as ui; -import 'package:flutter/foundation.dart' show clampDouble; +import 'package:flutter/foundation.dart' show Brightness, clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import 'colors.dart'; import 'text_selection_toolbar_button.dart'; +import 'theme.dart'; // Values extracted from https://developer.apple.com/design/resources/. // The height of the toolbar, including the arrow. @@ -29,9 +31,27 @@ const double _kArrowScreenPadding = 26.0; // Values extracted from https://developer.apple.com/design/resources/. 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 _kToolbarDividerColor = Color(0xFF808080); +const CupertinoDynamicColor _kToolbarDividerColor = CupertinoDynamicColor.withBrightness( + // This value was extracted from a screenshot of iOS 16.0.3, as light mode + // didn't appear in the Apple design resources assets linked below. + color: Color(0xFFB6B6B6), + // Color extracted from https://developer.apple.com/design/resources/. + // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507. + darkColor: Color(0xFF808080), +); + +// These values were extracted from a screenshot of iOS 16.0.3, as light mode +// didn't appear in the Apple design resources assets linked above. +final BoxDecoration _kToolbarShadow = BoxDecoration( + borderRadius: const BorderRadius.all(_kToolbarBorderRadius), + boxShadow: [ + BoxShadow( + color: CupertinoColors.black.withOpacity(0.1), + blurRadius: 16.0, + offset: Offset(0, _kToolbarArrowSize.height / 2), + ), + ], +); /// The type for a Function that builds a toolbar's container with the given /// child. @@ -119,14 +139,23 @@ class CupertinoTextSelectionToolbar extends StatelessWidget { // Builds a toolbar just like the default iOS toolbar, with the right color // background and a rounded cutout with an arrow. static Widget _defaultToolbarBuilder(BuildContext context, Offset anchor, bool isAbove, Widget child) { - return _CupertinoTextSelectionToolbarShape( + final Widget outputChild = _CupertinoTextSelectionToolbarShape( anchor: anchor, isAbove: isAbove, child: DecoratedBox( - decoration: const BoxDecoration(color: _kToolbarDividerColor), + decoration: const BoxDecoration( + color: _kToolbarDividerColor, + ), child: child, ), ); + if (CupertinoTheme.brightnessOf(context) == Brightness.dark) { + return outputChild; + } + return DecoratedBox( + decoration: _kToolbarShadow, + child: outputChild, + ); } @override @@ -226,7 +255,6 @@ class _RenderCupertinoTextSelectionToolbarShape extends RenderShiftedBox { super.child, ); - @override bool get isRepaintBoundary => true; @@ -485,7 +513,7 @@ class _CupertinoTextSelectionToolbarContentState extends State<_CupertinoTextSel onPressed: _handleNextPage, text: '▶', ), - nextButtonDisabled: CupertinoTextSelectionToolbarButton.text( + nextButtonDisabled: const CupertinoTextSelectionToolbarButton.text( text: '▶', ), children: widget.children, diff --git a/packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart b/packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart index 6cc1cc40e93c..4ca6d67c7008 100644 --- a/packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart +++ b/packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart @@ -18,7 +18,17 @@ const TextStyle _kToolbarButtonFontStyle = TextStyle( // Colors extracted from https://developer.apple.com/design/resources/. // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507. -const Color _kToolbarBackgroundColor = Color(0xEB202020); +const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.withBrightness( + // This value was extracted from a screenshot of iOS 16.0.3, as light mode + // didn't appear in the Apple design resources assets linked above. + color: Color(0xEB202020), + darkColor: Color(0xEBF7F7F7), +); + +const CupertinoDynamicColor _kToolbarTextColor = CupertinoDynamicColor.withBrightness( + color: CupertinoColors.black, + darkColor: CupertinoColors.white, +); // Eyeballed value. const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 16.0, horizontal: 18.0); @@ -33,22 +43,17 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget { this.onPressed, required Widget this.child, }) : assert(child != null), + text = null, buttonItem = null; /// Create an instance of [CupertinoTextSelectionToolbarButton] whose child is /// a [Text] widget styled like the default iOS text selection toolbar button. - CupertinoTextSelectionToolbarButton.text({ + const CupertinoTextSelectionToolbarButton.text({ super.key, this.onPressed, - required String text, + required this.text, }) : buttonItem = null, - child = Text( - text, - overflow: TextOverflow.ellipsis, - style: _kToolbarButtonFontStyle.copyWith( - color: onPressed != null ? CupertinoColors.white : CupertinoColors.inactiveGray, - ), - ); + child = null; /// Create an instance of [CupertinoTextSelectionToolbarButton] from the given /// [ContextMenuButtonItem]. @@ -59,6 +64,7 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget { required ContextMenuButtonItem this.buttonItem, }) : assert(buttonItem != null), child = null, + text = null, onPressed = buttonItem.onPressed; /// {@template flutter.cupertino.CupertinoTextSelectionToolbarButton.child} @@ -79,6 +85,10 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget { /// {@endtemplate} final ContextMenuButtonItem? buttonItem; + /// The text used in the button's label when using + /// [CupertinoTextSelectionToolbarButton.text]. + final String? text; + /// Returns the default button label String for the button of the given /// [ContextMenuButtonItem]'s [ContextMenuButtonType]. static String getButtonLabel(BuildContext context, ContextMenuButtonItem buttonItem) { @@ -105,12 +115,15 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget { @override Widget build(BuildContext context) { final Widget child = this.child ?? Text( - getButtonLabel(context, buttonItem!), - overflow: TextOverflow.ellipsis, - style: _kToolbarButtonFontStyle.copyWith( - color: onPressed != null ? CupertinoColors.white : CupertinoColors.inactiveGray, - ), - ); + text ?? getButtonLabel(context, buttonItem!), + overflow: TextOverflow.ellipsis, + style: _kToolbarButtonFontStyle.copyWith( + color: onPressed != null + ? _kToolbarTextColor + : CupertinoColors.inactiveGray, + ), + ); + return CupertinoButton( borderRadius: null, color: _kToolbarBackgroundColor, diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 44076c65a8a5..8050c301f765 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -1523,7 +1523,11 @@ void main() { await tester.pump(const Duration(milliseconds: 200)); Text text = tester.widget(find.text('Paste')); - expect(text.style!.color, CupertinoColors.white); + const CupertinoDynamicColor toolbarTextColor = CupertinoDynamicColor.withBrightness( + color: CupertinoColors.black, + darkColor: CupertinoColors.white, + ); + expect(text.style!.color, toolbarTextColor); expect(text.style!.fontSize, 14); expect(text.style!.letterSpacing, -0.15); expect(text.style!.fontWeight, FontWeight.w400); @@ -1555,7 +1559,7 @@ void main() { text = tester.widget(find.text('Paste')); // The toolbar buttons' text are still the same style. - expect(text.style!.color, CupertinoColors.white); + expect(text.style!.color, toolbarTextColor); expect(text.style!.fontSize, 14); expect(text.style!.letterSpacing, -0.15); expect(text.style!.fontWeight, FontWeight.w400); diff --git a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart index 5052d1ee9128..015b09842604 100644 --- a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart +++ b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart @@ -60,6 +60,11 @@ class TestBox extends SizedBox { static const double itemWidth = 100.0; } +const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.withBrightness( + color: Color(0xEB202020), + darkColor: Color(0xEBF7F7F7), +); + void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -262,4 +267,40 @@ void main() { expect(find.text('Paste'), findsNothing); expect(find.text('Select all'), findsNothing); }, skip: kIsWeb); // [intended] We do not use Flutter-rendered context menu on the Web. + + testWidgets('draws dark buttons in dark mode and light button in light mode', (WidgetTester tester) async { + for (final Brightness brightness in Brightness.values) { + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: Builder( + builder: (BuildContext context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(platformBrightness: brightness), + child: CupertinoTextSelectionToolbar( + anchorAbove: const Offset(100.0, 0.0), + anchorBelow: const Offset(100.0, 0.0), + children: [ + CupertinoTextSelectionToolbarButton.text( + onPressed: () {}, + text: 'Button', + ), + ], + ), + ); + }, + ), + ), + ), + ); + + final Finder buttonFinder = find.byType(CupertinoButton); + expect(find.byType(CupertinoButton), findsOneWidget); + final CupertinoButton button = tester.widget(buttonFinder); + expect( + button.color, + _kToolbarBackgroundColor, + ); + } + }, skip: kIsWeb); // [intended] We do not use Flutter-rendered context menu on the Web. }