From a8f67ee37f45361097818a7c784307be2c97ba2e Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Thu, 4 Nov 2021 01:31:57 +0200 Subject: [PATCH] [Cupertino] Exclude border for last action item in `CupertinoContextMenu` (#92481) --- .../lib/src/cupertino/context_menu.dart | 30 +++++++-- .../src/cupertino/context_menu_action.dart | 3 - .../test/cupertino/context_menu_test.dart | 67 ++++++++++++++++++- 3 files changed, 92 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart index caf0e82c94..e5bc3e2ee5 100644 --- a/packages/flutter/lib/src/cupertino/context_menu.dart +++ b/packages/flutter/lib/src/cupertino/context_menu.dart @@ -9,10 +9,17 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; +import 'colors.dart'; + // The scale of the child at the time that the CupertinoContextMenu opens. // This value was eyeballed from a physical device running iOS 13.1.2. const double _kOpenScale = 1.1; +const Color _borderColor = CupertinoDynamicColor.withBrightness( + color: Color(0xFFA9A9AF), + darkColor: Color(0xFF57585A), +); + typedef _DismissCallback = void Function( BuildContext context, double scale, @@ -1151,8 +1158,8 @@ class _ContextMenuSheet extends StatelessWidget { // Get the children, whose order depends on orientation and // contextMenuLocation. - List get children { - final Flexible menu = Flexible( + List getChildren(BuildContext context) { + final Widget menu = Flexible( fit: FlexFit.tight, flex: 2, child: IntrinsicHeight( @@ -1160,7 +1167,22 @@ class _ContextMenuSheet extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(13.0)), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, - children: actions, + children: [ + actions.first, + for (Widget action in actions.skip(1)) + DecoratedBox( + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: CupertinoDynamicColor.resolve(_borderColor, context), + width: 0.5, + ) + ), + ), + position: DecorationPosition.foreground, + child: action, + ), + ], ), ), ), @@ -1195,7 +1217,7 @@ class _ContextMenuSheet extends StatelessWidget { Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.start, - children: children, + children: getChildren(context), ); } } diff --git a/packages/flutter/lib/src/cupertino/context_menu_action.dart b/packages/flutter/lib/src/cupertino/context_menu_action.dart index 3be99045b0..478fe2e7de 100644 --- a/packages/flutter/lib/src/cupertino/context_menu_action.dart +++ b/packages/flutter/lib/src/cupertino/context_menu_action.dart @@ -115,9 +115,6 @@ class _CupertinoContextMenuActionState extends State child: Container( decoration: BoxDecoration( color: _isPressed ? _kBackgroundColorPressed : _kBackgroundColor, - border: const Border( - bottom: BorderSide(color: _kBackgroundColorPressed), - ), ), padding: const EdgeInsets.symmetric( vertical: 16.0, diff --git a/packages/flutter/test/cupertino/context_menu_test.dart b/packages/flutter/test/cupertino/context_menu_test.dart index dc5ed1de57..ffb0011778 100644 --- a/packages/flutter/test/cupertino/context_menu_test.dart +++ b/packages/flutter/test/cupertino/context_menu_test.dart @@ -9,7 +9,6 @@ void main() { final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized() as TestWidgetsFlutterBinding; const double _kOpenScale = 1.1; - Widget _getChild() { return Container( width: 300.0, @@ -66,6 +65,13 @@ void main() { ); } + Finder _findStaticChildDecoration(WidgetTester tester) { + return find.descendant( + of: _findStatic(), + matching: find.byType(DecoratedBox), + ); + } + group('CupertinoContextMenu before and during opening', () { testWidgets('An unopened CupertinoContextMenu renders child in the same place as without', (WidgetTester tester) async { // Measure the child in the scene with no CupertinoContextMenu. @@ -190,6 +196,65 @@ void main() { }); group('CupertinoContextMenu when open', () { + testWidgets('Last action does not have border', (WidgetTester tester) async { + final Widget child = _getChild(); + await tester.pumpWidget(CupertinoApp( + home: CupertinoPageScaffold( + child: Center( + child: CupertinoContextMenu( + actions: const [ + CupertinoContextMenuAction( + child: Text('CupertinoContextMenuAction One'), + ), + ], + child: child, + ), + ), + ), + )); + + // Open the CupertinoContextMenu + final TestGesture firstGesture = await tester.startGesture(tester.getCenter(find.byWidget(child))); + await tester.pumpAndSettle(); + await firstGesture.up(); + await tester.pumpAndSettle(); + expect(_findStatic(), findsOneWidget); + + expect(_findStaticChildDecoration(tester), findsNWidgets(1)); + + // Close the CupertinoContextMenu. + await tester.tapAt(const Offset(1.0, 1.0)); + await tester.pumpAndSettle(); + expect(_findStatic(), findsNothing); + + await tester.pumpWidget(CupertinoApp( + home: CupertinoPageScaffold( + child: Center( + child: CupertinoContextMenu( + actions: const [ + CupertinoContextMenuAction( + child: Text('CupertinoContextMenuAction One'), + ), + CupertinoContextMenuAction( + child: Text('CupertinoContextMenuAction Two'), + ), + ], + child: child, + ), + ), + ), + )); + + // Open the CupertinoContextMenu + final TestGesture secondGesture = await tester.startGesture(tester.getCenter(find.byWidget(child))); + await tester.pumpAndSettle(); + await secondGesture.up(); + await tester.pumpAndSettle(); + expect(_findStatic(), findsOneWidget); + + expect(_findStaticChildDecoration(tester), findsNWidgets(3)); + }); + testWidgets('Can close CupertinoContextMenu by background tap', (WidgetTester tester) async { final Widget child = _getChild(); await tester.pumpWidget(_getContextMenu(child: child));