Fix iOS context menu position when flipped below (#119565)

* Fix anchorBelow calculation, and share toolbar padding constant

* Fix constant references in test

* Test below position when padding is not offset by content distance
diff --git a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart
index bee18a8..296c8f8 100644
--- a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart
+++ b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart
@@ -19,9 +19,6 @@
 // Vertical distance between the tip of the arrow and the line of text the arrow
 // is pointing to. The value used here is eyeballed.
 const double _kToolbarContentDistance = 8.0;
-// Minimal padding from all edges of the selection toolbar to all edges of the
-// screen.
-const double _kToolbarScreenPadding = 8.0;
 const Size _kToolbarArrowSize = Size(14.0, 7.0);
 
 // Minimal padding from tip of the selection toolbar arrow to horizontal edges of the
@@ -123,6 +120,16 @@
   /// default Cupertino toolbar.
   final CupertinoToolbarBuilder toolbarBuilder;
 
+  /// Minimal padding from all edges of the selection toolbar to all edges of the
+  /// viewport.
+  ///
+  /// See also:
+  ///
+  ///  * [SpellCheckSuggestionsToolbar], which uses this same value for its
+  ///    padding from the edges of the viewport.
+  ///  * [TextSelectionToolbar], which uses this same value as well.
+  static const double kToolbarScreenPadding = 8.0;
+
   // Add the visual vertical line spacer between children buttons.
   static List<Widget> _addChildrenSpacers(List<Widget> children) {
     final List<Widget> nextChildren = <Widget>[];
@@ -163,7 +170,7 @@
     assert(debugCheckHasMediaQuery(context));
     final EdgeInsets mediaQueryPadding = MediaQuery.paddingOf(context);
 
-    final double paddingAbove = mediaQueryPadding.top + _kToolbarScreenPadding;
+    final double paddingAbove = mediaQueryPadding.top + kToolbarScreenPadding;
     final double toolbarHeightNeeded = paddingAbove
         + _kToolbarContentDistance
         + _kToolbarHeight;
@@ -180,15 +187,15 @@
     );
     final Offset anchorBelowAdjusted = Offset(
       clampDouble(anchorBelow.dx, leftMargin, rightMargin),
-      anchorBelow.dy - _kToolbarContentDistance + paddingAbove,
+      anchorBelow.dy + _kToolbarContentDistance - paddingAbove,
     );
 
     return Padding(
       padding: EdgeInsets.fromLTRB(
-        _kToolbarScreenPadding,
+        kToolbarScreenPadding,
         paddingAbove,
-        _kToolbarScreenPadding,
-        _kToolbarScreenPadding,
+        kToolbarScreenPadding,
+        kToolbarScreenPadding,
       ),
       child: CustomSingleChildLayout(
         delegate: TextSelectionToolbarLayoutDelegate(
diff --git a/packages/flutter/lib/src/material/spell_check_suggestions_toolbar.dart b/packages/flutter/lib/src/material/spell_check_suggestions_toolbar.dart
index 560beaa..6e4cadf 100644
--- a/packages/flutter/lib/src/material/spell_check_suggestions_toolbar.dart
+++ b/packages/flutter/lib/src/material/spell_check_suggestions_toolbar.dart
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/cupertino.dart';
 import 'package:flutter/services.dart' show SuggestionSpan;
-import 'package:flutter/widgets.dart';
 
 import 'adaptive_text_selection_toolbar.dart';
 import 'colors.dart';
@@ -142,16 +142,16 @@
         anchor + const Offset(0.0, kToolbarContentDistanceBelow);
     final MediaQueryData mediaQueryData = MediaQuery.of(context);
     final double softKeyboardViewInsetsBottom = mediaQueryData.viewInsets.bottom;
-    final double paddingAbove = mediaQueryData.padding.top + TextSelectionToolbar.kToolbarScreenPadding;
+    final double paddingAbove = mediaQueryData.padding.top + CupertinoTextSelectionToolbar.kToolbarScreenPadding;
     // Makes up for the Padding.
-    final Offset localAdjustment = Offset(TextSelectionToolbar.kToolbarScreenPadding, paddingAbove);
+    final Offset localAdjustment = Offset(CupertinoTextSelectionToolbar.kToolbarScreenPadding, paddingAbove);
 
     return Padding(
       padding: EdgeInsets.fromLTRB(
-        TextSelectionToolbar.kToolbarScreenPadding,
+        CupertinoTextSelectionToolbar.kToolbarScreenPadding,
         kToolbarContentDistanceBelow,
-        TextSelectionToolbar.kToolbarScreenPadding,
-        TextSelectionToolbar.kToolbarScreenPadding + softKeyboardViewInsetsBottom,
+        CupertinoTextSelectionToolbar.kToolbarScreenPadding,
+        CupertinoTextSelectionToolbar.kToolbarScreenPadding + softKeyboardViewInsetsBottom,
       ),
       child: CustomSingleChildLayout(
         delegate: SpellCheckSuggestionsToolbarLayoutDelegate(
diff --git a/packages/flutter/lib/src/material/text_selection_toolbar.dart b/packages/flutter/lib/src/material/text_selection_toolbar.dart
index 270b2a9..9927cd1 100644
--- a/packages/flutter/lib/src/material/text_selection_toolbar.dart
+++ b/packages/flutter/lib/src/material/text_selection_toolbar.dart
@@ -4,9 +4,9 @@
 
 import 'dart:math' as math;
 
+import 'package:flutter/cupertino.dart';
 import 'package:flutter/foundation.dart' show listEquals;
 import 'package:flutter/rendering.dart';
-import 'package:flutter/widgets.dart';
 
 import 'debug.dart';
 import 'icon_button.dart';
@@ -76,15 +76,6 @@
   /// {@endtemplate}
   final ToolbarBuilder toolbarBuilder;
 
-  /// Minimal padding from all edges of the selection toolbar to all edges of the
-  /// viewport.
-  ///
-  /// See also:
-  ///
-  ///  * [SpellCheckSuggestionsToolbar], which uses this same value for its
-  ///    padding from the edges of the viewport.
-  static const double kToolbarScreenPadding = 8.0;
-
   /// The size of the text selection handles.
   ///
   /// See also:
@@ -111,19 +102,20 @@
     final Offset anchorBelowPadded =
         anchorBelow + const Offset(0.0, kToolbarContentDistanceBelow);
 
+    const double screenPadding = CupertinoTextSelectionToolbar.kToolbarScreenPadding;
     final double paddingAbove = MediaQuery.paddingOf(context).top
-        + kToolbarScreenPadding;
+        + screenPadding;
     final double availableHeight = anchorAbovePadded.dy - _kToolbarContentDistance - paddingAbove;
     final bool fitsAbove = _kToolbarHeight <= availableHeight;
     // Makes up for the Padding above the Stack.
-    final Offset localAdjustment = Offset(kToolbarScreenPadding, paddingAbove);
+    final Offset localAdjustment = Offset(screenPadding, paddingAbove);
 
     return Padding(
       padding: EdgeInsets.fromLTRB(
-        kToolbarScreenPadding,
+        screenPadding,
         paddingAbove,
-        kToolbarScreenPadding,
-        kToolbarScreenPadding,
+        screenPadding,
+        screenPadding,
       ),
       child: CustomSingleChildLayout(
         delegate: TextSelectionToolbarLayoutDelegate(
diff --git a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart
index 7727630..83b6d4b 100644
--- a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart
+++ b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart
@@ -218,6 +218,7 @@
     const double height = _kToolbarHeight;
     const double anchorBelowY = 500.0;
     double anchorAboveY = 0.0;
+    const double paddingAbove = 12.0;
 
     await tester.pumpWidget(
       CupertinoApp(
@@ -225,14 +226,26 @@
           child: StatefulBuilder(
             builder: (BuildContext context, StateSetter setter) {
               setState = setter;
-              return CupertinoTextSelectionToolbar(
-                anchorAbove: Offset(50.0, anchorAboveY),
-                anchorBelow: const Offset(50.0, anchorBelowY),
-                children: <Widget>[
-                  Container(color: const Color(0xffff0000), width: 50.0, height: height),
-                  Container(color: const Color(0xff00ff00), width: 50.0, height: height),
-                  Container(color: const Color(0xff0000ff), width: 50.0, height: height),
-                ],
+              final MediaQueryData data = MediaQuery.of(context);
+              // Add some custom vertical padding to make this test more strict.
+              // By default in the testing environment, _kToolbarContentDistance
+              // and the built-in padding from CupertinoApp can end up canceling
+              // each other out.
+              return MediaQuery(
+                data: data.copyWith(
+                  padding: data.viewPadding.copyWith(
+                    top: paddingAbove,
+                  ),
+                ),
+                child: CupertinoTextSelectionToolbar(
+                  anchorAbove: Offset(50.0, anchorAboveY),
+                  anchorBelow: const Offset(50.0, anchorBelowY),
+                  children: <Widget>[
+                    Container(color: const Color(0xffff0000), width: 50.0, height: height),
+                    Container(color: const Color(0xff00ff00), width: 50.0, height: height),
+                    Container(color: const Color(0xff0000ff), width: 50.0, height: height),
+                  ],
+                ),
               );
             },
           ),
@@ -244,10 +257,14 @@
     // belowAnchor.
     double toolbarY = tester.getTopLeft(findToolbar()).dy;
     expect(toolbarY, equals(anchorBelowY + _kToolbarContentDistance));
+    expect(find.byType(CustomSingleChildLayout), findsOneWidget);
+    final CustomSingleChildLayout layout = tester.widget(find.byType(CustomSingleChildLayout));
+    final TextSelectionToolbarLayoutDelegate delegate = layout.delegate as TextSelectionToolbarLayoutDelegate;
+    expect(delegate.anchorBelow.dy, anchorBelowY - paddingAbove);
 
     // Even when it barely doesn't fit.
     setState(() {
-      anchorAboveY = 50.0;
+      anchorAboveY = 70.0;
     });
     await tester.pump();
     toolbarY = tester.getTopLeft(findToolbar()).dy;
@@ -255,7 +272,7 @@
 
     // When it does fit above aboveAnchor, it positions itself there.
     setState(() {
-      anchorAboveY = 60.0;
+      anchorAboveY = 80.0;
     });
     await tester.pump();
     toolbarY = tester.getTopLeft(findToolbar()).dy;
diff --git a/packages/flutter/test/material/spell_check_suggestions_toolbar_test.dart b/packages/flutter/test/material/spell_check_suggestions_toolbar_test.dart
index e504c8e..440deda 100644
--- a/packages/flutter/test/material/spell_check_suggestions_toolbar_test.dart
+++ b/packages/flutter/test/material/spell_check_suggestions_toolbar_test.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/cupertino.dart' show CupertinoTextSelectionToolbar;
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 
@@ -48,7 +49,7 @@
   testWidgets('positions toolbar below anchor when it fits above bottom view padding', (WidgetTester tester) async {
     // We expect the toolbar to be positioned right below the anchor with padding accounted for.
     const double expectedToolbarY =
-        _kAnchor + (2 * SpellCheckSuggestionsToolbar.kToolbarContentDistanceBelow) - TextSelectionToolbar.kToolbarScreenPadding;
+        _kAnchor + (2 * SpellCheckSuggestionsToolbar.kToolbarContentDistanceBelow) - CupertinoTextSelectionToolbar.kToolbarScreenPadding;
 
     await tester.pumpWidget(
       MaterialApp(
@@ -68,7 +69,7 @@
   testWidgets('re-positions toolbar higher below anchor when it does not fit above bottom view padding', (WidgetTester tester) async {
     // We expect the toolbar to be positioned _kTestToolbarOverlap pixels above the anchor with padding accounted for.
     const double expectedToolbarY =
-        _kAnchor + (2 * SpellCheckSuggestionsToolbar.kToolbarContentDistanceBelow) - TextSelectionToolbar.kToolbarScreenPadding - _kTestToolbarOverlap;
+        _kAnchor + (2 * SpellCheckSuggestionsToolbar.kToolbarContentDistanceBelow) - CupertinoTextSelectionToolbar.kToolbarScreenPadding - _kTestToolbarOverlap;
 
     await tester.pumpWidget(
       MaterialApp(