iOS 16 context menu (#115805)
Updates the text selection toolbar on iOS light mode to match changes in native iOS 16.
This commit is contained in:
parent
7966d5584f
commit
1cb16a1e39
@ -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>[
|
||||
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,
|
||||
|
@ -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,
|
||||
|
@ -1523,7 +1523,11 @@ void main() {
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
Text text = tester.widget<Text>(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<Text>(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);
|
||||
|
@ -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: <Widget>[
|
||||
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.
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user