Skip to content

Commit

Permalink
Fix the scrolling layout deviation of CupertinoActionSheet (flutter…
Browse files Browse the repository at this point in the history
…#149439)

This PR changes `CupertinoActionSheet`'s layout algorithm, so that the actions section always has the lower priority to take up vertical space with a fixed minimum height of 84.3 px. 

Currently `CupertinoActionSheet` uses a very complicated rule the determine the sizes of various sections:
1. If there are <= 3 buttons and a cancel button, or 1 button without a cancel button, then the actions section should never scroll.
2. Otherwise, then the content section takes priority to take over spaces but must leave at least `actionsMinHeight` for the actions section.

This has been the case since `CupertinoActionSheet` was first introduced ([first PR](flutter#19232)). Maybe it was the case before, but it's no longer reproducible on the latest SwiftUI.

The following images for comparison (left to right: Current Flutter, Flutter after this PR, SwiftUI). Pay attention to whether the actions section scrolls. (There are also some height/padding issues, which will be fixed in a future PR.)

Two buttons:
<img width="1006" alt="image" src="https://github.com/flutter/flutter/assets/1596656/27ca5df7-1140-4bfd-9a5f-3b5972632c92">

Three buttons:
<img width="1025" alt="image" src="https://github.com/flutter/flutter/assets/1596656/f2be6a5c-1713-4ffe-9705-27a668e5133d">

Four buttons:
<img width="1024" alt="image" src="https://github.com/flutter/flutter/assets/1596656/5b90d9f4-11ed-4c04-bdea-0b81b1d2bd3d">

In SwiftUI, the action section seems to always have a minimum height of ~84 pixels regardless of the number of buttons. Therefore this PR also fixed this behavior, and adjusted the related tests.
  • Loading branch information
dkwingsmt authored Jun 4, 2024
1 parent c03fb95 commit c246ecd
Show file tree
Hide file tree
Showing 2 changed files with 11 additions and 33 deletions.
40 changes: 9 additions & 31 deletions packages/flutter/lib/src/cupertino/dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,6 @@ class _CupertinoActionSheetState extends State<CupertinoActionSheet> {
child: _ActionSheetMainSheet(
scrollController: _effectiveActionScrollController,
hasContent: hasContent,
hasCancelButton: widget.cancelButton != null,
contentSection: Builder(builder: _buildContent),
actions: widget.actions,
dividerColor: _kActionSheetButtonDividerColor,
Expand Down Expand Up @@ -918,15 +917,13 @@ class _ActionSheetMainSheet extends StatefulWidget {
required this.scrollController,
required this.actions,
required this.hasContent,
required this.hasCancelButton,
required this.contentSection,
required this.dividerColor,
});

final ScrollController? scrollController;
final List<Widget>? actions;
final bool hasContent;
final bool hasCancelButton;
final Widget contentSection;
final Color dividerColor;

Expand Down Expand Up @@ -980,25 +977,16 @@ class _ActionSheetMainSheetState extends State<_ActionSheetMainSheet> {

bool _hasActions() => (widget.actions?.length ?? 0) != 0;

// If `aggressivelyLayout` is true, then the content section takes as much
// space as needed up to `maxHeight`.
//
// If `aggressivelyLayout` is false, then the content section takes whatever
// space is left by the other sections.
Widget _buildContent({
required bool hasActions,
required bool aggressivelyLayout,
required double maxHeight,
}) {
if (hasActions && aggressivelyLayout) {
return ConstrainedBox(
constraints: BoxConstraints(
maxHeight: maxHeight,
),
Widget _buildContent({required bool hasActions, required double maxHeight}) {
if (!hasActions) {
return Flexible(
child: widget.contentSection,
);
}
return Flexible(
return ConstrainedBox(
constraints: BoxConstraints(
maxHeight: maxHeight,
),
child: widget.contentSection,
);
}
Expand All @@ -1019,16 +1007,8 @@ class _ActionSheetMainSheetState extends State<_ActionSheetMainSheet> {

@override
Widget build(BuildContext context) {
// The layout rule:
//
// 1. If there are <= 3 buttons and a cancel button, or 1 button without a
// cancel button, then the actions section should never scroll.
// 2. Otherwise, then the content section takes priority to take over spaces
// but must leave at least `actionsMinHeight` for the actions section.
final int numActions = widget.actions?.length ?? 0;
final bool actionsMightScroll =
(numActions > 3 && widget.hasCancelButton) ||
(numActions > 1 && !widget.hasCancelButton) ;
// The content section takes priority for vertical space but must leave at
// least `_kActionSheetActionsSectionMinHeight` for the actions section.
final Color backgroundColor = CupertinoDynamicColor.resolve(_kActionSheetBackgroundColor, context);
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
Expand All @@ -1037,7 +1017,6 @@ class _ActionSheetMainSheetState extends State<_ActionSheetMainSheet> {
children: <Widget>[
_buildContent(
hasActions: _hasActions(),
aggressivelyLayout: actionsMightScroll,
maxHeight: constraints.maxHeight - _kActionSheetActionsSectionMinHeight - _kDividerThickness,
),
if (widget.hasContent && _hasActions())
Expand All @@ -1046,7 +1025,6 @@ class _ActionSheetMainSheetState extends State<_ActionSheetMainSheet> {
hidden: false,
),
Flexible(
flex: actionsMightScroll ? 1 : 0,
child: Stack(
children: <Widget>[
Positioned.fill(
Expand Down
4 changes: 2 additions & 2 deletions packages/flutter/test/cupertino/action_sheet_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ void main() {
await tester.tap(find.text('Go'));
await tester.pump();

expect(findScrollableActionsSectionRenderBox(tester).size.height, moreOrLessEquals(112.3));
expect(findScrollableActionsSectionRenderBox(tester).size.height, moreOrLessEquals(84.3));
});

testWidgets('3 action buttons with cancel button', (WidgetTester tester) async {
Expand Down Expand Up @@ -753,7 +753,7 @@ void main() {
await tester.tap(find.text('Go'));
await tester.pump();

expect(findScrollableActionsSectionRenderBox(tester).size.height, moreOrLessEquals(168.6));
expect(findScrollableActionsSectionRenderBox(tester).size.height, moreOrLessEquals(84.3));
});

testWidgets('4+ action buttons with cancel button', (WidgetTester tester) async {
Expand Down

0 comments on commit c246ecd

Please sign in to comment.