Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
24258141
Unverified
Commit
24258141
authored
Apr 07, 2021
by
Marcel Čampa
Committed by
GitHub
Apr 07, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Dedup CupertinoAlertDialog and CupertinoActionSheet source code (#78202)
parent
0efb28d7
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
894 additions
and
1635 deletions
+894
-1635
cupertino.dart
packages/flutter/lib/cupertino.dart
+0
-1
action_sheet.dart
packages/flutter/lib/src/cupertino/action_sheet.dart
+0
-1429
dialog.dart
packages/flutter/lib/src/cupertino/dialog.dart
+894
-205
No files found.
packages/flutter/lib/cupertino.dart
View file @
24258141
...
...
@@ -22,7 +22,6 @@
library
cupertino
;
export
'src/cupertino/action_sheet.dart'
;
export
'src/cupertino/activity_indicator.dart'
;
export
'src/cupertino/app.dart'
;
export
'src/cupertino/bottom_tab_bar.dart'
;
...
...
packages/flutter/lib/src/cupertino/action_sheet.dart
deleted
100644 → 0
View file @
0efb28d7
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:ui'
show
ImageFilter
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
import
'interface_level.dart'
;
import
'scrollbar.dart'
;
import
'theme.dart'
;
const
TextStyle
_kActionSheetActionStyle
=
TextStyle
(
fontFamily:
'.SF UI Text'
,
inherit:
false
,
fontSize:
20.0
,
fontWeight:
FontWeight
.
w400
,
textBaseline:
TextBaseline
.
alphabetic
,
);
const
TextStyle
_kActionSheetContentStyle
=
TextStyle
(
fontFamily:
'.SF UI Text'
,
inherit:
false
,
fontSize:
13.0
,
fontWeight:
FontWeight
.
w400
,
color:
_kContentTextColor
,
textBaseline:
TextBaseline
.
alphabetic
,
);
// Translucent, very light gray that is painted on top of the blurred backdrop
// as the action sheet's background color.
// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/39272. Use
// System Materials once we have them.
// Extracted from https://developer.apple.com/design/resources/.
const
Color
_kBackgroundColor
=
CupertinoDynamicColor
.
withBrightness
(
color:
Color
(
0xC7F9F9F9
),
darkColor:
Color
(
0xC7252525
),
);
// Translucent, light gray that is painted on top of the blurred backdrop as
// the background color of a pressed button.
// Eye-balled from iOS 13 beta simulator.
const
Color
_kPressedColor
=
CupertinoDynamicColor
.
withBrightness
(
color:
Color
(
0xFFE1E1E1
),
darkColor:
Color
(
0xFF2E2E2E
),
);
const
Color
_kCancelPressedColor
=
CupertinoDynamicColor
.
withBrightness
(
color:
Color
(
0xFFECECEC
),
darkColor:
Color
(
0xFF49494B
),
);
// The gray color used for text that appears in the title area.
// Extracted from https://developer.apple.com/design/resources/.
const
Color
_kContentTextColor
=
Color
(
0xFF8F8F8F
);
// Translucent gray that is painted on top of the blurred backdrop in the gap
// areas between the content section and actions section, as well as between
// buttons.
// Eye-balled from iOS 13 beta simulator.
const
Color
_kButtonDividerColor
=
_kContentTextColor
;
const
double
_kBlurAmount
=
20.0
;
const
double
_kEdgeHorizontalPadding
=
8.0
;
const
double
_kCancelButtonPadding
=
8.0
;
const
double
_kEdgeVerticalPadding
=
10.0
;
const
double
_kContentHorizontalPadding
=
40.0
;
const
double
_kContentVerticalPadding
=
14.0
;
const
double
_kButtonHeight
=
56.0
;
const
double
_kCornerRadius
=
14.0
;
const
double
_kDividerThickness
=
1.0
;
/// An iOS-style action sheet.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=U-ao8p4A82k}
///
/// An action sheet is a specific style of alert that presents the user
/// with a set of two or more choices related to the current context.
/// An action sheet can have a title, an additional message, and a list
/// of actions. The title is displayed above the message and the actions
/// are displayed below this content.
///
/// This action sheet styles its title and message to match standard iOS action
/// sheet title and message text style.
///
/// To display action buttons that look like standard iOS action sheet buttons,
/// provide [CupertinoActionSheetAction]s for the [actions] given to this action
/// sheet.
///
/// To include a iOS-style cancel button separate from the other buttons,
/// provide an [CupertinoActionSheetAction] for the [cancelButton] given to this
/// action sheet.
///
/// An action sheet is typically passed as the child widget to
/// [showCupertinoModalPopup], which displays the action sheet by sliding it up
/// from the bottom of the screen.
///
/// {@tool snippet}
/// This sample shows how to use a [CupertinoActionSheet].
/// The [CupertinoActionSheet] shows an alert with a set of two choices
/// when [CupertinoButton] is pressed.
///
/// ```dart
/// class MyStatefulWidget extends StatefulWidget {
/// const MyStatefulWidget({Key? key}) : super(key: key);
///
/// @override
/// _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
/// }
///
/// class _MyStatefulWidgetState extends State<MyStatefulWidget> {
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoPageScaffold(
/// child: Center(
/// child: CupertinoButton(
/// onPressed: () {
/// showCupertinoModalPopup<void>(
/// context: context,
/// builder: (BuildContext context) => CupertinoActionSheet(
/// title: const Text('Title'),
/// message: const Text('Message'),
/// actions: <CupertinoActionSheetAction>[
/// CupertinoActionSheetAction(
/// child: const Text('Action One'),
/// onPressed: () {
/// Navigator.pop(context);
/// },
/// ),
/// CupertinoActionSheetAction(
/// child: const Text('Action Two'),
/// onPressed: () {
/// Navigator.pop(context);
/// },
/// )
/// ],
/// ),
/// );
/// },
/// child: const Text('CupertinoActionSheet'),
/// ),
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [CupertinoActionSheetAction], which is an iOS-style action sheet button.
/// * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
class
CupertinoActionSheet
extends
StatelessWidget
{
/// Creates an iOS-style action sheet.
///
/// An action sheet must have a non-null value for at least one of the
/// following arguments: [actions], [title], [message], or [cancelButton].
///
/// Generally, action sheets are used to give the user a choice between
/// two or more choices for the current context.
const
CupertinoActionSheet
({
Key
?
key
,
this
.
title
,
this
.
message
,
this
.
actions
,
this
.
messageScrollController
,
this
.
actionScrollController
,
this
.
cancelButton
,
})
:
assert
(
actions
!=
null
||
title
!=
null
||
message
!=
null
||
cancelButton
!=
null
,
'An action sheet must have a non-null value for at least one of the following arguments: '
'actions, title, message, or cancelButton'
,
),
super
(
key:
key
);
/// An optional title of the action sheet. When the [message] is non-null,
/// the font of the [title] is bold.
///
/// Typically a [Text] widget.
final
Widget
?
title
;
/// An optional descriptive message that provides more details about the
/// reason for the alert.
///
/// Typically a [Text] widget.
final
Widget
?
message
;
/// The set of actions that are displayed for the user to select.
///
/// Typically this is a list of [CupertinoActionSheetAction] widgets.
final
List
<
Widget
>?
actions
;
/// A scroll controller that can be used to control the scrolling of the
/// [message] in the action sheet.
///
/// This attribute is typically not needed, as alert messages should be
/// short.
final
ScrollController
?
messageScrollController
;
/// A scroll controller that can be used to control the scrolling of the
/// [actions] in the action sheet.
///
/// This attribute is typically not needed.
final
ScrollController
?
actionScrollController
;
/// The optional cancel button that is grouped separately from the other
/// actions.
///
/// Typically this is an [CupertinoActionSheetAction] widget.
final
Widget
?
cancelButton
;
Widget
_buildContent
(
BuildContext
context
)
{
final
List
<
Widget
>
content
=
<
Widget
>[];
if
(
title
!=
null
||
message
!=
null
)
{
final
Widget
titleSection
=
_CupertinoAlertContentSection
(
title:
title
,
message:
message
,
scrollController:
messageScrollController
,
);
content
.
add
(
Flexible
(
child:
titleSection
));
}
return
Container
(
color:
CupertinoDynamicColor
.
resolve
(
_kBackgroundColor
,
context
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
content
,
),
);
}
Widget
_buildActions
()
{
if
(
actions
==
null
||
actions
!.
isEmpty
)
{
return
Container
(
height:
0.0
,
);
}
return
_CupertinoAlertActionSection
(
children:
actions
!,
scrollController:
actionScrollController
,
hasCancelButton:
cancelButton
!=
null
,
);
}
Widget
_buildCancelButton
()
{
final
double
cancelPadding
=
(
actions
!=
null
||
message
!=
null
||
title
!=
null
)
?
_kCancelButtonPadding
:
0.0
;
return
Padding
(
padding:
EdgeInsets
.
only
(
top:
cancelPadding
),
child:
_CupertinoActionSheetCancelButton
(
child:
cancelButton
,
),
);
}
@override
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
final
List
<
Widget
>
children
=
<
Widget
>[
Flexible
(
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12.0
),
child:
BackdropFilter
(
filter:
ImageFilter
.
blur
(
sigmaX:
_kBlurAmount
,
sigmaY:
_kBlurAmount
),
child:
_CupertinoAlertRenderWidget
(
contentSection:
Builder
(
builder:
_buildContent
),
actionsSection:
_buildActions
(),
),
),
),
),
if
(
cancelButton
!=
null
)
_buildCancelButton
(),
];
final
Orientation
orientation
=
MediaQuery
.
of
(
context
).
orientation
;
final
double
actionSheetWidth
;
if
(
orientation
==
Orientation
.
portrait
)
{
actionSheetWidth
=
MediaQuery
.
of
(
context
).
size
.
width
-
(
_kEdgeHorizontalPadding
*
2
);
}
else
{
actionSheetWidth
=
MediaQuery
.
of
(
context
).
size
.
height
-
(
_kEdgeHorizontalPadding
*
2
);
}
return
SafeArea
(
child:
Semantics
(
namesRoute:
true
,
scopesRoute:
true
,
explicitChildNodes:
true
,
label:
'Alert'
,
child:
CupertinoUserInterfaceLevel
(
data:
CupertinoUserInterfaceLevelData
.
elevated
,
child:
Container
(
width:
actionSheetWidth
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
_kEdgeHorizontalPadding
,
vertical:
_kEdgeVerticalPadding
,
),
child:
Column
(
children:
children
,
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
),
),
),
),
);
}
}
/// A button typically used in a [CupertinoActionSheet].
///
/// See also:
///
/// * [CupertinoActionSheet], an alert that presents the user with a set of two or
/// more choices related to the current context.
class
CupertinoActionSheetAction
extends
StatelessWidget
{
/// Creates an action for an iOS-style action sheet.
///
/// The [child] and [onPressed] arguments must not be null.
const
CupertinoActionSheetAction
({
Key
?
key
,
required
this
.
onPressed
,
this
.
isDefaultAction
=
false
,
this
.
isDestructiveAction
=
false
,
required
this
.
child
,
})
:
assert
(
child
!=
null
),
assert
(
onPressed
!=
null
),
super
(
key:
key
);
/// The callback that is called when the button is tapped.
///
/// This attribute must not be null.
final
VoidCallback
onPressed
;
/// Whether this action is the default choice in the action sheet.
///
/// Default buttons have bold text.
final
bool
isDefaultAction
;
/// Whether this action might change or delete data.
///
/// Destructive buttons have red text.
final
bool
isDestructiveAction
;
/// The widget below this widget in the tree.
///
/// Typically a [Text] widget.
final
Widget
child
;
@override
Widget
build
(
BuildContext
context
)
{
TextStyle
style
=
_kActionSheetActionStyle
.
copyWith
(
color:
isDestructiveAction
?
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
systemRed
,
context
)
:
CupertinoTheme
.
of
(
context
).
primaryColor
,
);
if
(
isDefaultAction
)
{
style
=
style
.
copyWith
(
fontWeight:
FontWeight
.
w600
);
}
return
GestureDetector
(
onTap:
onPressed
,
behavior:
HitTestBehavior
.
opaque
,
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minHeight:
_kButtonHeight
,
),
child:
Semantics
(
button:
true
,
child:
Container
(
alignment:
Alignment
.
center
,
padding:
const
EdgeInsets
.
symmetric
(
vertical:
16.0
,
horizontal:
10.0
,
),
child:
DefaultTextStyle
(
style:
style
,
child:
child
,
textAlign:
TextAlign
.
center
,
),
),
),
),
);
}
}
class
_CupertinoActionSheetCancelButton
extends
StatefulWidget
{
const
_CupertinoActionSheetCancelButton
({
Key
?
key
,
this
.
child
,
})
:
super
(
key:
key
);
final
Widget
?
child
;
@override
_CupertinoActionSheetCancelButtonState
createState
()
=>
_CupertinoActionSheetCancelButtonState
();
}
class
_CupertinoActionSheetCancelButtonState
extends
State
<
_CupertinoActionSheetCancelButton
>
{
bool
isBeingPressed
=
false
;
void
_onTapDown
(
TapDownDetails
event
)
{
setState
(()
{
isBeingPressed
=
true
;
});
}
void
_onTapUp
(
TapUpDetails
event
)
{
setState
(()
{
isBeingPressed
=
false
;
});
}
void
_onTapCancel
()
{
setState
(()
{
isBeingPressed
=
false
;
});
}
@override
Widget
build
(
BuildContext
context
)
{
final
Color
backgroundColor
=
isBeingPressed
?
_kCancelPressedColor
:
CupertinoColors
.
secondarySystemGroupedBackground
;
return
GestureDetector
(
excludeFromSemantics:
true
,
onTapDown:
_onTapDown
,
onTapUp:
_onTapUp
,
onTapCancel:
_onTapCancel
,
child:
Container
(
decoration:
BoxDecoration
(
color:
CupertinoDynamicColor
.
resolve
(
backgroundColor
,
context
),
borderRadius:
BorderRadius
.
circular
(
_kCornerRadius
),
),
child:
widget
.
child
,
),
);
}
}
class
_CupertinoAlertRenderWidget
extends
RenderObjectWidget
{
const
_CupertinoAlertRenderWidget
({
Key
?
key
,
required
this
.
contentSection
,
required
this
.
actionsSection
,
})
:
super
(
key:
key
);
final
Widget
contentSection
;
final
Widget
actionsSection
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
return
_RenderCupertinoAlert
(
dividerThickness:
_kDividerThickness
/
MediaQuery
.
of
(
context
).
devicePixelRatio
,
dividerColor:
CupertinoDynamicColor
.
resolve
(
_kButtonDividerColor
,
context
),
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderCupertinoAlert
renderObject
)
{
super
.
updateRenderObject
(
context
,
renderObject
);
renderObject
.
dividerColor
=
CupertinoDynamicColor
.
resolve
(
_kButtonDividerColor
,
context
);
}
@override
RenderObjectElement
createElement
()
{
return
_CupertinoAlertRenderElement
(
this
);
}
}
class
_CupertinoAlertRenderElement
extends
RenderObjectElement
{
_CupertinoAlertRenderElement
(
_CupertinoAlertRenderWidget
widget
)
:
super
(
widget
);
Element
?
_contentElement
;
Element
?
_actionsElement
;
@override
_CupertinoAlertRenderWidget
get
widget
=>
super
.
widget
as
_CupertinoAlertRenderWidget
;
@override
_RenderCupertinoAlert
get
renderObject
=>
super
.
renderObject
as
_RenderCupertinoAlert
;
@override
void
visitChildren
(
ElementVisitor
visitor
)
{
if
(
_contentElement
!=
null
)
{
visitor
(
_contentElement
!);
}
if
(
_actionsElement
!=
null
)
{
visitor
(
_actionsElement
!);
}
}
@override
void
mount
(
Element
?
parent
,
Object
?
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
_contentElement
=
updateChild
(
_contentElement
,
widget
.
contentSection
,
_AlertSections
.
contentSection
);
_actionsElement
=
updateChild
(
_actionsElement
,
widget
.
actionsSection
,
_AlertSections
.
actionsSection
);
}
@override
void
insertRenderObjectChild
(
RenderObject
child
,
_AlertSections
slot
)
{
_placeChildInSlot
(
child
,
slot
);
}
@override
void
moveRenderObjectChild
(
RenderObject
child
,
_AlertSections
oldSlot
,
_AlertSections
newSlot
)
{
_placeChildInSlot
(
child
,
newSlot
);
}
@override
void
update
(
RenderObjectWidget
newWidget
)
{
super
.
update
(
newWidget
);
_contentElement
=
updateChild
(
_contentElement
,
widget
.
contentSection
,
_AlertSections
.
contentSection
);
_actionsElement
=
updateChild
(
_actionsElement
,
widget
.
actionsSection
,
_AlertSections
.
actionsSection
);
}
@override
void
forgetChild
(
Element
child
)
{
assert
(
child
==
_contentElement
||
child
==
_actionsElement
);
if
(
_contentElement
==
child
)
{
_contentElement
=
null
;
}
else
if
(
_actionsElement
==
child
)
{
_actionsElement
=
null
;
}
super
.
forgetChild
(
child
);
}
@override
void
removeRenderObjectChild
(
RenderObject
child
,
_AlertSections
slot
)
{
assert
(
child
==
renderObject
.
contentSection
||
child
==
renderObject
.
actionsSection
);
if
(
renderObject
.
contentSection
==
child
)
{
renderObject
.
contentSection
=
null
;
}
else
if
(
renderObject
.
actionsSection
==
child
)
{
renderObject
.
actionsSection
=
null
;
}
}
void
_placeChildInSlot
(
RenderObject
child
,
_AlertSections
slot
)
{
assert
(
slot
!=
null
);
switch
(
slot
)
{
case
_AlertSections
.
contentSection
:
renderObject
.
contentSection
=
child
as
RenderBox
;
break
;
case
_AlertSections
.
actionsSection
:
renderObject
.
actionsSection
=
child
as
RenderBox
;
break
;
}
}
}
// An iOS-style layout policy for sizing an alert's content section and action
// button section.
//
// The policy is as follows:
//
// If all content and buttons fit on the screen:
// The content section and action button section are sized intrinsically.
//
// If all content and buttons do not fit on the screen:
// A minimum height for the action button section is calculated. The action
// button section will not be rendered shorter than this minimum. See
// _RenderCupertinoAlertActions for the minimum height calculation.
//
// With the minimum action button section calculated, the content section can
// take up as much of the remaining space as it needs.
//
// After the content section is laid out, the action button section is allowed
// to take up any remaining space that was not consumed by the content section.
class
_RenderCupertinoAlert
extends
RenderBox
{
_RenderCupertinoAlert
({
RenderBox
?
contentSection
,
RenderBox
?
actionsSection
,
double
dividerThickness
=
0.0
,
required
Color
dividerColor
,
})
:
assert
(
dividerColor
!=
null
),
_contentSection
=
contentSection
,
_actionsSection
=
actionsSection
,
_dividerThickness
=
dividerThickness
,
_dividerPaint
=
Paint
()
..
color
=
dividerColor
..
style
=
PaintingStyle
.
fill
;
RenderBox
?
get
contentSection
=>
_contentSection
;
RenderBox
?
_contentSection
;
set
contentSection
(
RenderBox
?
newContentSection
)
{
if
(
newContentSection
!=
_contentSection
)
{
if
(
null
!=
_contentSection
)
{
dropChild
(
_contentSection
!);
}
_contentSection
=
newContentSection
;
if
(
null
!=
_contentSection
)
{
adoptChild
(
_contentSection
!);
}
}
}
RenderBox
?
get
actionsSection
=>
_actionsSection
;
RenderBox
?
_actionsSection
;
set
actionsSection
(
RenderBox
?
newActionsSection
)
{
if
(
newActionsSection
!=
_actionsSection
)
{
if
(
null
!=
_actionsSection
)
{
dropChild
(
_actionsSection
!);
}
_actionsSection
=
newActionsSection
;
if
(
null
!=
_actionsSection
)
{
adoptChild
(
_actionsSection
!);
}
}
}
Color
get
dividerColor
=>
_dividerPaint
.
color
;
set
dividerColor
(
Color
value
)
{
if
(
value
==
_dividerPaint
.
color
)
return
;
_dividerPaint
.
color
=
value
;
markNeedsPaint
();
}
final
double
_dividerThickness
;
final
Paint
_dividerPaint
;
@override
void
attach
(
PipelineOwner
owner
)
{
super
.
attach
(
owner
);
if
(
null
!=
contentSection
)
{
contentSection
!.
attach
(
owner
);
}
if
(
null
!=
actionsSection
)
{
actionsSection
!.
attach
(
owner
);
}
}
@override
void
detach
()
{
super
.
detach
();
if
(
null
!=
contentSection
)
{
contentSection
!.
detach
();
}
if
(
null
!=
actionsSection
)
{
actionsSection
!.
detach
();
}
}
@override
void
redepthChildren
()
{
if
(
null
!=
contentSection
)
{
redepthChild
(
contentSection
!);
}
if
(
null
!=
actionsSection
)
{
redepthChild
(
actionsSection
!);
}
}
@override
void
setupParentData
(
RenderBox
child
)
{
if
(
child
.
parentData
is
!
MultiChildLayoutParentData
)
{
child
.
parentData
=
MultiChildLayoutParentData
();
}
}
@override
void
visitChildren
(
RenderObjectVisitor
visitor
)
{
if
(
contentSection
!=
null
)
{
visitor
(
contentSection
!);
}
if
(
actionsSection
!=
null
)
{
visitor
(
actionsSection
!);
}
}
@override
List
<
DiagnosticsNode
>
debugDescribeChildren
()
{
final
List
<
DiagnosticsNode
>
value
=
<
DiagnosticsNode
>[];
if
(
contentSection
!=
null
)
{
value
.
add
(
contentSection
!.
toDiagnosticsNode
(
name:
'content'
));
}
if
(
actionsSection
!=
null
)
{
value
.
add
(
actionsSection
!.
toDiagnosticsNode
(
name:
'actions'
));
}
return
value
;
}
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
return
constraints
.
minWidth
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
return
constraints
.
maxWidth
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
final
double
contentHeight
=
contentSection
!.
getMinIntrinsicHeight
(
width
);
final
double
actionsHeight
=
actionsSection
!.
getMinIntrinsicHeight
(
width
);
final
bool
hasDivider
=
contentHeight
>
0.0
&&
actionsHeight
>
0.0
;
double
height
=
contentHeight
+
(
hasDivider
?
_dividerThickness
:
0.0
)
+
actionsHeight
;
if
(
actionsHeight
>
0
||
contentHeight
>
0
)
height
-=
2
*
_kEdgeVerticalPadding
;
if
(
height
.
isFinite
)
return
height
;
return
0.0
;
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
final
double
contentHeight
=
contentSection
!.
getMaxIntrinsicHeight
(
width
);
final
double
actionsHeight
=
actionsSection
!.
getMaxIntrinsicHeight
(
width
);
final
bool
hasDivider
=
contentHeight
>
0.0
&&
actionsHeight
>
0.0
;
double
height
=
contentHeight
+
(
hasDivider
?
_dividerThickness
:
0.0
)
+
actionsHeight
;
if
(
actionsHeight
>
0
||
contentHeight
>
0
)
height
-=
2
*
_kEdgeVerticalPadding
;
if
(
height
.
isFinite
)
return
height
;
return
0.0
;
}
double
_computeDividerThickness
(
BoxConstraints
constraints
)
{
final
bool
hasDivider
=
contentSection
!.
getMaxIntrinsicHeight
(
constraints
.
maxWidth
)
>
0.0
&&
actionsSection
!.
getMaxIntrinsicHeight
(
constraints
.
maxWidth
)
>
0.0
;
return
hasDivider
?
_dividerThickness
:
0.0
;
}
_AlertSizes
_computeSizes
({
required
BoxConstraints
constraints
,
required
ChildLayouter
layoutChild
,
required
double
dividerThickness
})
{
final
double
minActionsHeight
=
actionsSection
!.
getMinIntrinsicHeight
(
constraints
.
maxWidth
);
final
Size
contentSize
=
layoutChild
(
contentSection
!,
constraints
.
deflate
(
EdgeInsets
.
only
(
bottom:
minActionsHeight
+
dividerThickness
)),
);
final
Size
actionsSize
=
layoutChild
(
actionsSection
!,
constraints
.
deflate
(
EdgeInsets
.
only
(
top:
contentSize
.
height
+
dividerThickness
)),
);
final
double
actionSheetHeight
=
contentSize
.
height
+
dividerThickness
+
actionsSize
.
height
;
return
_AlertSizes
(
size:
Size
(
constraints
.
maxWidth
,
actionSheetHeight
),
contentHeight:
contentSize
.
height
,
);
}
@override
Size
computeDryLayout
(
BoxConstraints
constraints
)
{
return
_computeSizes
(
constraints:
constraints
,
layoutChild:
ChildLayoutHelper
.
dryLayoutChild
,
dividerThickness:
_computeDividerThickness
(
constraints
),
).
size
;
}
@override
void
performLayout
()
{
final
BoxConstraints
constraints
=
this
.
constraints
;
final
double
dividerThickness
=
_computeDividerThickness
(
constraints
);
final
_AlertSizes
alertSizes
=
_computeSizes
(
constraints:
constraints
,
layoutChild:
ChildLayoutHelper
.
layoutChild
,
dividerThickness:
dividerThickness
,
);
size
=
alertSizes
.
size
;
// Set the position of the actions box to sit at the bottom of the alert.
// The content box defaults to the top left, which is where we want it.
assert
(
actionsSection
!.
parentData
is
MultiChildLayoutParentData
);
final
MultiChildLayoutParentData
actionParentData
=
actionsSection
!.
parentData
!
as
MultiChildLayoutParentData
;
actionParentData
.
offset
=
Offset
(
0.0
,
alertSizes
.
contentHeight
+
dividerThickness
);
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
MultiChildLayoutParentData
contentParentData
=
contentSection
!.
parentData
!
as
MultiChildLayoutParentData
;
contentSection
!.
paint
(
context
,
offset
+
contentParentData
.
offset
);
final
bool
hasDivider
=
contentSection
!.
size
.
height
>
0.0
&&
actionsSection
!.
size
.
height
>
0.0
;
if
(
hasDivider
)
{
_paintDividerBetweenContentAndActions
(
context
.
canvas
,
offset
);
}
final
MultiChildLayoutParentData
actionsParentData
=
actionsSection
!.
parentData
!
as
MultiChildLayoutParentData
;
actionsSection
!.
paint
(
context
,
offset
+
actionsParentData
.
offset
);
}
void
_paintDividerBetweenContentAndActions
(
Canvas
canvas
,
Offset
offset
)
{
canvas
.
drawRect
(
Rect
.
fromLTWH
(
offset
.
dx
,
offset
.
dy
+
contentSection
!.
size
.
height
,
size
.
width
,
_dividerThickness
,
),
_dividerPaint
,
);
}
@override
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
required
Offset
position
})
{
final
MultiChildLayoutParentData
contentSectionParentData
=
contentSection
!.
parentData
!
as
MultiChildLayoutParentData
;
final
MultiChildLayoutParentData
actionsSectionParentData
=
actionsSection
!.
parentData
!
as
MultiChildLayoutParentData
;
return
result
.
addWithPaintOffset
(
offset:
contentSectionParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
contentSectionParentData
.
offset
);
return
contentSection
!.
hitTest
(
result
,
position:
transformed
);
},
)
||
result
.
addWithPaintOffset
(
offset:
actionsSectionParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
actionsSectionParentData
.
offset
);
return
actionsSection
!.
hitTest
(
result
,
position:
transformed
);
},
);
}
}
class
_AlertSizes
{
const
_AlertSizes
({
required
this
.
size
,
required
this
.
contentHeight
});
final
Size
size
;
final
double
contentHeight
;
}
// Visual components of an alert that need to be explicitly sized and
// laid out at runtime.
enum
_AlertSections
{
contentSection
,
actionsSection
,
}
// The "content section" of a CupertinoActionSheet.
//
// If title is missing, then only content is added. If content is
// missing, then only a title is added. If both are missing, then it returns
// a SingleChildScrollView with a zero-sized Container.
class
_CupertinoAlertContentSection
extends
StatelessWidget
{
const
_CupertinoAlertContentSection
({
Key
?
key
,
this
.
title
,
this
.
message
,
this
.
scrollController
,
})
:
super
(
key:
key
);
// An optional title of the action sheet. When the message is non-null,
// the font of the title is bold.
//
// Typically a Text widget.
final
Widget
?
title
;
// An optional descriptive message that provides more details about the
// reason for the alert.
//
// Typically a Text widget.
final
Widget
?
message
;
// A scroll controller that can be used to control the scrolling of the
// content in the action sheet.
//
// Defaults to null, and is typically not needed, since most alert contents
// are short.
final
ScrollController
?
scrollController
;
@override
Widget
build
(
BuildContext
context
)
{
final
List
<
Widget
>
titleContentGroup
=
<
Widget
>[];
if
(
title
!=
null
)
{
titleContentGroup
.
add
(
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
_kContentHorizontalPadding
,
right:
_kContentHorizontalPadding
,
bottom:
_kContentVerticalPadding
,
top:
_kContentVerticalPadding
,
),
child:
DefaultTextStyle
(
style:
message
==
null
?
_kActionSheetContentStyle
:
_kActionSheetContentStyle
.
copyWith
(
fontWeight:
FontWeight
.
w600
),
textAlign:
TextAlign
.
center
,
child:
title
!,
),
));
}
if
(
message
!=
null
)
{
titleContentGroup
.
add
(
Padding
(
padding:
EdgeInsets
.
only
(
left:
_kContentHorizontalPadding
,
right:
_kContentHorizontalPadding
,
bottom:
title
==
null
?
_kContentVerticalPadding
:
22.0
,
top:
title
==
null
?
_kContentVerticalPadding
:
0.0
,
),
child:
DefaultTextStyle
(
style:
title
==
null
?
_kActionSheetContentStyle
.
copyWith
(
fontWeight:
FontWeight
.
w600
)
:
_kActionSheetContentStyle
,
textAlign:
TextAlign
.
center
,
child:
message
!,
),
),
);
}
if
(
titleContentGroup
.
isEmpty
)
{
return
SingleChildScrollView
(
controller:
scrollController
,
child:
const
SizedBox
(
width:
0.0
,
height:
0.0
,
),
);
}
// Add padding between the widgets if necessary.
if
(
titleContentGroup
.
length
>
1
)
{
titleContentGroup
.
insert
(
1
,
const
Padding
(
padding:
EdgeInsets
.
only
(
top:
8.0
)));
}
return
CupertinoScrollbar
(
child:
SingleChildScrollView
(
controller:
scrollController
,
child:
Column
(
mainAxisSize:
MainAxisSize
.
max
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
titleContentGroup
,
),
),
);
}
}
// The "actions section" of a CupertinoActionSheet.
//
// See _RenderCupertinoAlertActions for details about action button sizing
// and layout.
class
_CupertinoAlertActionSection
extends
StatefulWidget
{
const
_CupertinoAlertActionSection
({
Key
?
key
,
required
this
.
children
,
this
.
scrollController
,
this
.
hasCancelButton
,
})
:
assert
(
children
!=
null
),
super
(
key:
key
);
final
List
<
Widget
>
children
;
// A scroll controller that can be used to control the scrolling of the
// actions in the action sheet.
//
// Defaults to null, and is typically not needed, since most alerts
// don't have many actions.
final
ScrollController
?
scrollController
;
final
bool
?
hasCancelButton
;
@override
_CupertinoAlertActionSectionState
createState
()
=>
_CupertinoAlertActionSectionState
();
}
class
_CupertinoAlertActionSectionState
extends
State
<
_CupertinoAlertActionSection
>
{
@override
Widget
build
(
BuildContext
context
)
{
final
double
devicePixelRatio
=
MediaQuery
.
of
(
context
).
devicePixelRatio
;
final
List
<
Widget
>
interactiveButtons
=
<
Widget
>[];
for
(
int
i
=
0
;
i
<
widget
.
children
.
length
;
i
+=
1
)
{
interactiveButtons
.
add
(
_PressableActionButton
(
child:
widget
.
children
[
i
],
),
);
}
return
CupertinoScrollbar
(
child:
SingleChildScrollView
(
controller:
widget
.
scrollController
,
child:
_CupertinoAlertActionsRenderWidget
(
actionButtons:
interactiveButtons
,
dividerThickness:
_kDividerThickness
/
devicePixelRatio
,
hasCancelButton:
widget
.
hasCancelButton
??
false
,
),
),
);
}
}
// A button that updates its render state when pressed.
//
// The pressed state is forwarded to an _ActionButtonParentDataWidget. The
// corresponding _ActionButtonParentData is then interpreted and rendered
// appropriately by _RenderCupertinoAlertActions.
class
_PressableActionButton
extends
StatefulWidget
{
const
_PressableActionButton
({
required
this
.
child
,
});
final
Widget
child
;
@override
_PressableActionButtonState
createState
()
=>
_PressableActionButtonState
();
}
class
_PressableActionButtonState
extends
State
<
_PressableActionButton
>
{
bool
_isPressed
=
false
;
@override
Widget
build
(
BuildContext
context
)
{
return
_ActionButtonParentDataWidget
(
isPressed:
_isPressed
,
// TODO(mattcarroll): Button press dynamics need overhaul for iOS:
// https://github.com/flutter/flutter/issues/19786
child:
GestureDetector
(
excludeFromSemantics:
true
,
behavior:
HitTestBehavior
.
opaque
,
onTapDown:
(
TapDownDetails
details
)
=>
setState
(()
=>
_isPressed
=
true
),
onTapUp:
(
TapUpDetails
details
)
=>
setState
(()
=>
_isPressed
=
false
),
// TODO(mattcarroll): Cancel is currently triggered when user moves past
// slop instead of off button: https://github.com/flutter/flutter/issues/19783
onTapCancel:
()
=>
setState
(()
=>
_isPressed
=
false
),
child:
widget
.
child
,
),
);
}
}
// ParentDataWidget that updates _ActionButtonParentData for an action button.
//
// Each action button requires knowledge of whether or not it is pressed so that
// the alert can correctly render the button. The pressed state is held within
// _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for
// updating the pressed state of an _ActionButtonParentData based on the
// incoming isPressed property.
class
_ActionButtonParentDataWidget
extends
ParentDataWidget
<
_ActionButtonParentData
>
{
const
_ActionButtonParentDataWidget
({
Key
?
key
,
required
this
.
isPressed
,
required
Widget
child
,
})
:
super
(
key:
key
,
child:
child
);
final
bool
isPressed
;
@override
void
applyParentData
(
RenderObject
renderObject
)
{
assert
(
renderObject
.
parentData
is
_ActionButtonParentData
);
final
_ActionButtonParentData
parentData
=
renderObject
.
parentData
!
as
_ActionButtonParentData
;
if
(
parentData
.
isPressed
!=
isPressed
)
{
parentData
.
isPressed
=
isPressed
;
// Force a repaint.
final
AbstractNode
?
targetParent
=
renderObject
.
parent
;
if
(
targetParent
is
RenderObject
)
targetParent
.
markNeedsPaint
();
}
}
@override
Type
get
debugTypicalAncestorWidgetClass
=>
_CupertinoAlertActionsRenderWidget
;
}
// ParentData applied to individual action buttons that report whether or not
// that button is currently pressed by the user.
class
_ActionButtonParentData
extends
MultiChildLayoutParentData
{
_ActionButtonParentData
({
this
.
isPressed
=
false
,
});
bool
isPressed
;
}
// An iOS-style alert action button layout.
//
// See _RenderCupertinoAlertActions for specific layout policy details.
class
_CupertinoAlertActionsRenderWidget
extends
MultiChildRenderObjectWidget
{
_CupertinoAlertActionsRenderWidget
({
Key
?
key
,
required
List
<
Widget
>
actionButtons
,
double
dividerThickness
=
0.0
,
bool
hasCancelButton
=
false
,
})
:
_dividerThickness
=
dividerThickness
,
_hasCancelButton
=
hasCancelButton
,
super
(
key:
key
,
children:
actionButtons
);
final
double
_dividerThickness
;
final
bool
_hasCancelButton
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
_RenderCupertinoAlertActions
(
dividerThickness:
_dividerThickness
,
dividerColor:
CupertinoDynamicColor
.
resolve
(
_kButtonDividerColor
,
context
),
hasCancelButton:
_hasCancelButton
,
backgroundColor:
CupertinoDynamicColor
.
resolve
(
_kBackgroundColor
,
context
),
pressedColor:
CupertinoDynamicColor
.
resolve
(
_kPressedColor
,
context
),
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderCupertinoAlertActions
renderObject
)
{
renderObject
..
dividerThickness
=
_dividerThickness
..
dividerColor
=
CupertinoDynamicColor
.
resolve
(
_kButtonDividerColor
,
context
)
..
hasCancelButton
=
_hasCancelButton
..
backgroundColor
=
CupertinoDynamicColor
.
resolve
(
_kBackgroundColor
,
context
)
..
pressedColor
=
CupertinoDynamicColor
.
resolve
(
_kPressedColor
,
context
);
}
}
// An iOS-style layout policy for sizing and positioning an action sheet's
// buttons.
//
// The policy is as follows:
//
// Action sheet buttons are always stacked vertically. In the case where the
// content section and the action section combined can not fit on the screen
// without scrolling, the height of the action section is determined as
// follows.
//
// If the user has included a separate cancel button, the height of the action
// section can be up to the height of 3 action buttons (i.e., the user can
// include 1, 2, or 3 action buttons and they will appear without needing to
// be scrolled). If 4+ action buttons are provided, the height of the action
// section shrinks to 1.5 buttons tall, and is scrollable.
//
// If the user has not included a separate cancel button, the height of the
// action section is at most 1.5 buttons tall.
class
_RenderCupertinoAlertActions
extends
RenderBox
with
ContainerRenderObjectMixin
<
RenderBox
,
MultiChildLayoutParentData
>,
RenderBoxContainerDefaultsMixin
<
RenderBox
,
MultiChildLayoutParentData
>
{
_RenderCupertinoAlertActions
({
List
<
RenderBox
>?
children
,
double
dividerThickness
=
0.0
,
required
Color
dividerColor
,
bool
hasCancelButton
=
false
,
required
Color
backgroundColor
,
required
Color
pressedColor
,
})
:
_dividerThickness
=
dividerThickness
,
_hasCancelButton
=
hasCancelButton
,
_buttonBackgroundPaint
=
Paint
()
..
style
=
PaintingStyle
.
fill
..
color
=
backgroundColor
,
_pressedButtonBackgroundPaint
=
Paint
()
..
style
=
PaintingStyle
.
fill
..
color
=
pressedColor
,
_dividerPaint
=
Paint
()
..
color
=
dividerColor
..
style
=
PaintingStyle
.
fill
{
addAll
(
children
);
}
// The thickness of the divider between buttons.
double
get
dividerThickness
=>
_dividerThickness
;
double
_dividerThickness
;
set
dividerThickness
(
double
newValue
)
{
if
(
newValue
==
_dividerThickness
)
{
return
;
}
_dividerThickness
=
newValue
;
markNeedsLayout
();
}
Color
get
backgroundColor
=>
_buttonBackgroundPaint
.
color
;
set
backgroundColor
(
Color
newValue
)
{
if
(
newValue
==
_buttonBackgroundPaint
.
color
)
{
return
;
}
_buttonBackgroundPaint
.
color
=
newValue
;
markNeedsPaint
();
}
Color
get
pressedColor
=>
_pressedButtonBackgroundPaint
.
color
;
set
pressedColor
(
Color
newValue
)
{
if
(
newValue
==
_pressedButtonBackgroundPaint
.
color
)
{
return
;
}
_pressedButtonBackgroundPaint
.
color
=
newValue
;
markNeedsPaint
();
}
Color
get
dividerColor
=>
_dividerPaint
.
color
;
set
dividerColor
(
Color
value
)
{
if
(
value
==
_dividerPaint
.
color
)
{
return
;
}
_dividerPaint
.
color
=
value
;
markNeedsPaint
();
}
bool
_hasCancelButton
;
bool
get
hasCancelButton
=>
_hasCancelButton
;
set
hasCancelButton
(
bool
newValue
)
{
if
(
newValue
==
_hasCancelButton
)
{
return
;
}
_hasCancelButton
=
newValue
;
markNeedsLayout
();
}
final
Paint
_buttonBackgroundPaint
;
final
Paint
_pressedButtonBackgroundPaint
;
final
Paint
_dividerPaint
;
@override
void
setupParentData
(
RenderBox
child
)
{
if
(
child
.
parentData
is
!
_ActionButtonParentData
)
child
.
parentData
=
_ActionButtonParentData
();
}
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
return
constraints
.
minWidth
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
return
constraints
.
maxWidth
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
if
(
childCount
==
0
)
return
0.0
;
if
(
childCount
==
1
)
return
firstChild
!.
computeMaxIntrinsicHeight
(
width
)
+
dividerThickness
;
if
(
hasCancelButton
&&
childCount
<
4
)
return
_computeMinIntrinsicHeightWithCancel
(
width
);
return
_computeMinIntrinsicHeightWithoutCancel
(
width
);
}
// The minimum height for more than 2-3 buttons when a cancel button is
// included is the full height of button stack.
double
_computeMinIntrinsicHeightWithCancel
(
double
width
)
{
assert
(
childCount
==
2
||
childCount
==
3
);
if
(
childCount
==
2
)
{
return
firstChild
!.
getMinIntrinsicHeight
(
width
)
+
childAfter
(
firstChild
!)!.
getMinIntrinsicHeight
(
width
)
+
dividerThickness
;
}
return
firstChild
!.
getMinIntrinsicHeight
(
width
)
+
childAfter
(
firstChild
!)!.
getMinIntrinsicHeight
(
width
)
+
childAfter
(
childAfter
(
firstChild
!)!)!.
getMinIntrinsicHeight
(
width
)
+
(
dividerThickness
*
2
);
}
// The minimum height for more than 2 buttons when no cancel button or 4+
// buttons when a cancel button is included is the height of the 1st button
// + 50% the height of the 2nd button + 2 dividers.
double
_computeMinIntrinsicHeightWithoutCancel
(
double
width
)
{
assert
(
childCount
>=
2
);
return
firstChild
!.
getMinIntrinsicHeight
(
width
)
+
dividerThickness
+
(
0.5
*
childAfter
(
firstChild
!)!.
getMinIntrinsicHeight
(
width
));
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
if
(
childCount
==
0
)
return
0.0
;
if
(
childCount
==
1
)
return
firstChild
!.
computeMaxIntrinsicHeight
(
width
)
+
dividerThickness
;
return
_computeMaxIntrinsicHeightStacked
(
width
);
}
// Max height of a stack of buttons is the sum of all button heights + a
// divider for each button.
double
_computeMaxIntrinsicHeightStacked
(
double
width
)
{
assert
(
childCount
>=
2
);
final
double
allDividersHeight
=
(
childCount
-
1
)
*
dividerThickness
;
double
heightAccumulation
=
allDividersHeight
;
RenderBox
?
button
=
firstChild
;
while
(
button
!=
null
)
{
heightAccumulation
+=
button
.
getMaxIntrinsicHeight
(
width
);
button
=
childAfter
(
button
);
}
return
heightAccumulation
;
}
@override
Size
computeDryLayout
(
BoxConstraints
constraints
)
{
return
_performLayout
(
constraints
,
dry:
true
);
}
@override
void
performLayout
()
{
size
=
_performLayout
(
constraints
,
dry:
false
);
}
Size
_performLayout
(
BoxConstraints
constraints
,
{
bool
dry
=
false
})
{
final
BoxConstraints
perButtonConstraints
=
constraints
.
copyWith
(
minHeight:
0.0
,
maxHeight:
double
.
infinity
,
);
RenderBox
?
child
=
firstChild
;
int
index
=
0
;
double
verticalOffset
=
0.0
;
while
(
child
!=
null
)
{
final
Size
childSize
;
if
(!
dry
)
{
child
.
layout
(
perButtonConstraints
,
parentUsesSize:
true
,
);
childSize
=
child
.
size
;
assert
(
child
.
parentData
is
MultiChildLayoutParentData
);
final
MultiChildLayoutParentData
parentData
=
child
.
parentData
!
as
MultiChildLayoutParentData
;
parentData
.
offset
=
Offset
(
0.0
,
verticalOffset
);
}
else
{
childSize
=
child
.
getDryLayout
(
constraints
);
}
verticalOffset
+=
childSize
.
height
;
if
(
index
<
childCount
-
1
)
{
// Add a gap for the next divider.
verticalOffset
+=
dividerThickness
;
}
index
+=
1
;
child
=
childAfter
(
child
);
}
return
constraints
.
constrain
(
Size
(
constraints
.
maxWidth
,
verticalOffset
),
);
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
Canvas
canvas
=
context
.
canvas
;
_drawButtonBackgroundsAndDividersStacked
(
canvas
,
offset
);
_drawButtons
(
context
,
offset
);
}
void
_drawButtonBackgroundsAndDividersStacked
(
Canvas
canvas
,
Offset
offset
)
{
final
Offset
dividerOffset
=
Offset
(
0.0
,
dividerThickness
);
final
Path
backgroundFillPath
=
Path
()
..
fillType
=
PathFillType
.
evenOdd
..
addRect
(
Rect
.
fromLTWH
(
0.0
,
0.0
,
size
.
width
,
size
.
height
));
final
Path
pressedBackgroundFillPath
=
Path
();
final
Path
dividersPath
=
Path
();
Offset
accumulatingOffset
=
offset
;
RenderBox
?
child
=
firstChild
;
RenderBox
?
prevChild
;
while
(
child
!=
null
)
{
assert
(
child
.
parentData
is
_ActionButtonParentData
);
final
_ActionButtonParentData
currentButtonParentData
=
child
.
parentData
!
as
_ActionButtonParentData
;
final
bool
isButtonPressed
=
currentButtonParentData
.
isPressed
;
bool
isPrevButtonPressed
=
false
;
if
(
prevChild
!=
null
)
{
assert
(
prevChild
.
parentData
is
_ActionButtonParentData
);
final
_ActionButtonParentData
previousButtonParentData
=
prevChild
.
parentData
!
as
_ActionButtonParentData
;
isPrevButtonPressed
=
previousButtonParentData
.
isPressed
;
}
final
bool
isDividerPresent
=
child
!=
firstChild
;
final
bool
isDividerPainted
=
isDividerPresent
&&
!(
isButtonPressed
||
isPrevButtonPressed
);
final
Rect
dividerRect
=
Rect
.
fromLTWH
(
accumulatingOffset
.
dx
,
accumulatingOffset
.
dy
,
size
.
width
,
_dividerThickness
,
);
final
Rect
buttonBackgroundRect
=
Rect
.
fromLTWH
(
accumulatingOffset
.
dx
,
accumulatingOffset
.
dy
+
(
isDividerPresent
?
dividerThickness
:
0.0
),
size
.
width
,
child
.
size
.
height
,
);
// If this button is pressed, then we don't want a white background to be
// painted, so we erase this button from the background path.
if
(
isButtonPressed
)
{
backgroundFillPath
.
addRect
(
buttonBackgroundRect
);
pressedBackgroundFillPath
.
addRect
(
buttonBackgroundRect
);
}
// If this divider is needed, then we erase the divider area from the
// background path, and on top of that we paint a translucent gray to
// darken the divider area.
if
(
isDividerPainted
)
{
backgroundFillPath
.
addRect
(
dividerRect
);
dividersPath
.
addRect
(
dividerRect
);
}
accumulatingOffset
+=
(
isDividerPresent
?
dividerOffset
:
Offset
.
zero
)
+
Offset
(
0.0
,
child
.
size
.
height
);
prevChild
=
child
;
child
=
childAfter
(
child
);
}
canvas
.
drawPath
(
backgroundFillPath
,
_buttonBackgroundPaint
);
canvas
.
drawPath
(
pressedBackgroundFillPath
,
_pressedButtonBackgroundPaint
);
canvas
.
drawPath
(
dividersPath
,
_dividerPaint
);
}
void
_drawButtons
(
PaintingContext
context
,
Offset
offset
)
{
RenderBox
?
child
=
firstChild
;
while
(
child
!=
null
)
{
final
MultiChildLayoutParentData
childParentData
=
child
.
parentData
!
as
MultiChildLayoutParentData
;
context
.
paintChild
(
child
,
childParentData
.
offset
+
offset
);
child
=
childAfter
(
child
);
}
}
@override
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
required
Offset
position
})
{
return
defaultHitTestChildren
(
result
,
position:
position
);
}
}
packages/flutter/lib/src/cupertino/dialog.dart
View file @
24258141
...
...
@@ -13,6 +13,7 @@ import 'colors.dart';
import
'interface_level.dart'
;
import
'localizations.dart'
;
import
'scrollbar.dart'
;
import
'theme.dart'
;
// TODO(abarth): These constants probably belong somewhere more general.
...
...
@@ -49,18 +50,46 @@ const TextStyle _kCupertinoDialogActionStyle = TextStyle(
textBaseline:
TextBaseline
.
alphabetic
,
);
// CupertinoActionSheet-specific text styles.
const
TextStyle
_kActionSheetActionStyle
=
TextStyle
(
fontFamily:
'.SF UI Text'
,
inherit:
false
,
fontSize:
20.0
,
fontWeight:
FontWeight
.
w400
,
textBaseline:
TextBaseline
.
alphabetic
,
);
const
TextStyle
_kActionSheetContentStyle
=
TextStyle
(
fontFamily:
'.SF UI Text'
,
inherit:
false
,
fontSize:
13.0
,
fontWeight:
FontWeight
.
w400
,
color:
_kActionSheetContentTextColor
,
textBaseline:
TextBaseline
.
alphabetic
,
);
// Generic constants shared between Dialog and ActionSheet.
const
double
_kBlurAmount
=
20.0
;
const
double
_kCornerRadius
=
14.0
;
const
double
_kDividerThickness
=
1.0
;
// Dialog specific constants.
// iOS dialogs have a normal display width and another display width that is
// used when the device is in accessibility mode. Each of these widths are
// listed below.
const
double
_kCupertinoDialogWidth
=
270.0
;
const
double
_kAccessibilityCupertinoDialogWidth
=
310.0
;
const
double
_kBlurAmount
=
20.0
;
const
double
_kEdgePadding
=
20.0
;
const
double
_kMinButtonHeight
=
45.0
;
const
double
_kMinButtonFontSize
=
10.0
;
const
double
_kDialogCornerRadius
=
14.0
;
const
double
_kDividerThickness
=
1.0
;
const
double
_kDialogEdgePadding
=
20.0
;
const
double
_kDialogMinButtonHeight
=
45.0
;
const
double
_kDialogMinButtonFontSize
=
10.0
;
// ActionSheet specific constants.
const
double
_kActionSheetEdgeHorizontalPadding
=
8.0
;
const
double
_kActionSheetCancelButtonPadding
=
8.0
;
const
double
_kActionSheetEdgeVerticalPadding
=
10.0
;
const
double
_kActionSheetContentHorizontalPadding
=
40.0
;
const
double
_kActionSheetContentVerticalPadding
=
14.0
;
const
double
_kActionSheetButtonHeight
=
56.0
;
// A translucent color that is painted on top of the blurred backdrop as the
// dialog's background color
...
...
@@ -73,11 +102,36 @@ const Color _kDialogColor = CupertinoDynamicColor.withBrightness(
// Translucent light gray that is painted on top of the blurred backdrop as the
// background color of a pressed button.
// Eyeballed from iOS 13 beta simulator.
const
Color
_k
Dialog
PressedColor
=
CupertinoDynamicColor
.
withBrightness
(
const
Color
_kPressedColor
=
CupertinoDynamicColor
.
withBrightness
(
color:
Color
(
0xFFE1E1E1
),
darkColor:
Color
(
0xFF2E2E2E
),
);
const
Color
_kActionSheetCancelPressedColor
=
CupertinoDynamicColor
.
withBrightness
(
color:
Color
(
0xFFECECEC
),
darkColor:
Color
(
0xFF49494B
),
);
// Translucent, very light gray that is painted on top of the blurred backdrop
// as the action sheet's background color.
// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/39272. Use
// System Materials once we have them.
// Extracted from https://developer.apple.com/design/resources/.
const
Color
_kActionSheetBackgroundColor
=
CupertinoDynamicColor
.
withBrightness
(
color:
Color
(
0xC7F9F9F9
),
darkColor:
Color
(
0xC7252525
),
);
// The gray color used for text that appears in the title area.
// Extracted from https://developer.apple.com/design/resources/.
const
Color
_kActionSheetContentTextColor
=
Color
(
0xFF8F8F8F
);
// Translucent gray that is painted on top of the blurred backdrop in the gap
// areas between the content section and actions section, as well as between
// buttons.
// Eye-balled from iOS 13 beta simulator.
const
Color
_kActionSheetButtonDividerColor
=
_kActionSheetContentTextColor
;
// The alert dialog layout policy changes depending on whether the user is using
// a "regular" font size vs a "large" font size. This is a spectrum. There are
// many "regular" font sizes and many "large" font sizes. But depending on which
...
...
@@ -101,6 +155,8 @@ bool _isInAccessibilityMode(BuildContext context) {
/// An iOS-style alert dialog.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=75CsnyRXf5I}
///
/// An alert dialog informs the user about situations that require
/// acknowledgement. An alert dialog has an optional title, optional content,
/// and an optional list of actions. The title is displayed above the content
...
...
@@ -117,6 +173,58 @@ bool _isInAccessibilityMode(BuildContext context) {
/// Typically passed as the child widget to [showDialog], which displays the
/// dialog.
///
/// {@tool snippet}
/// This sample shows how to use a [CupertinoAlertDialog].
/// The [CupertinoAlertDialog] shows an alert with a set of two choices
/// when [CupertinoButton] is pressed.
///
/// ```dart
/// class MyStatefulWidget extends StatefulWidget {
/// const MyStatefulWidget({Key? key}) : super(key: key);
///
/// @override
/// _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
/// }
///
/// class _MyStatefulWidgetState extends State<MyStatefulWidget> {
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoPageScaffold(
/// child: Center(
/// child: CupertinoButton(
/// onPressed: () {
/// showCupertinoDialog<void>(
/// context: context,
/// builder: (BuildContext context) => CupertinoAlertDialog(
/// title: const Text('Alert'),
/// content: const Text('Proceed with destructive action?'),
/// actions: <CupertinoDialogAction>[
/// CupertinoDialogAction(
/// child: const Text('No'),
/// onPressed: () {
/// Navigator.pop(context);
/// },
/// ),
/// CupertinoDialogAction(
/// child: const Text('Yes'),
/// isDestructiveAction: true,
/// onPressed: () {
/// // Do something destructive.
/// },
/// )
/// ],
/// ),
/// );
/// },
/// child: const Text('CupertinoAlertDialog'),
/// ),
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [CupertinoPopupSurface], which is a generic iOS-style popup surface that
...
...
@@ -188,14 +296,34 @@ class CupertinoAlertDialog extends StatelessWidget {
final
Curve
insetAnimationCurve
;
Widget
_buildContent
(
BuildContext
context
)
{
final
double
textScaleFactor
=
MediaQuery
.
of
(
context
).
textScaleFactor
;
final
List
<
Widget
>
children
=
<
Widget
>[
if
(
title
!=
null
||
content
!=
null
)
Flexible
(
flex:
3
,
child:
_CupertinoAlertContentSection
(
title:
title
,
content
:
content
,
message
:
content
,
scrollController:
scrollController
,
titlePadding:
EdgeInsets
.
only
(
left:
_kDialogEdgePadding
,
right:
_kDialogEdgePadding
,
bottom:
content
==
null
?
_kDialogEdgePadding
:
1.0
,
top:
_kDialogEdgePadding
*
textScaleFactor
,
),
messagePadding:
EdgeInsets
.
only
(
left:
_kDialogEdgePadding
,
right:
_kDialogEdgePadding
,
bottom:
_kDialogEdgePadding
*
textScaleFactor
,
top:
title
==
null
?
_kDialogEdgePadding
:
1.0
,
),
titleTextStyle:
_kCupertinoDialogTitleStyle
.
copyWith
(
color:
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
label
,
context
),
),
messageTextStyle:
_kCupertinoDialogContentStyle
.
copyWith
(
color:
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
label
,
context
),
),
),
),
];
...
...
@@ -218,6 +346,7 @@ class CupertinoAlertDialog extends StatelessWidget {
actionSection
=
_CupertinoAlertActionSection
(
children:
actions
,
scrollController:
actionScrollController
,
isActionSheet:
false
,
);
}
...
...
@@ -251,10 +380,10 @@ class CupertinoAlertDialog extends StatelessWidget {
context:
context
,
child:
Center
(
child:
Container
(
margin:
const
EdgeInsets
.
symmetric
(
vertical:
_kEdgePadding
),
margin:
const
EdgeInsets
.
symmetric
(
vertical:
_k
Dialog
EdgePadding
),
width:
isInAccessibilityMode
?
_kAccessibilityCupertinoDialogWidth
:
_kCupertinoDialogWidth
,
?
_kAccessibilityCupertinoDialogWidth
:
_kCupertinoDialogWidth
,
child:
CupertinoPopupSurface
(
isSurfacePainted:
false
,
child:
Semantics
(
...
...
@@ -265,6 +394,7 @@ class CupertinoAlertDialog extends StatelessWidget {
child:
_CupertinoDialogRenderWidget
(
contentSection:
_buildContent
(
context
),
actionsSection:
_buildActions
(),
dividerColor:
CupertinoColors
.
separator
,
),
),
),
...
...
@@ -318,7 +448,7 @@ class CupertinoPopupSurface extends StatelessWidget {
@override
Widget
build
(
BuildContext
context
)
{
return
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
_k
Dialog
CornerRadius
),
borderRadius:
BorderRadius
.
circular
(
_kCornerRadius
),
child:
BackdropFilter
(
filter:
ImageFilter
.
blur
(
sigmaX:
_kBlurAmount
,
sigmaY:
_kBlurAmount
),
child:
Container
(
...
...
@@ -330,6 +460,392 @@ class CupertinoPopupSurface extends StatelessWidget {
}
}
/// An iOS-style action sheet.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=U-ao8p4A82k}
///
/// An action sheet is a specific style of alert that presents the user
/// with a set of two or more choices related to the current context.
/// An action sheet can have a title, an additional message, and a list
/// of actions. The title is displayed above the message and the actions
/// are displayed below this content.
///
/// This action sheet styles its title and message to match standard iOS action
/// sheet title and message text style.
///
/// To display action buttons that look like standard iOS action sheet buttons,
/// provide [CupertinoActionSheetAction]s for the [actions] given to this action
/// sheet.
///
/// To include a iOS-style cancel button separate from the other buttons,
/// provide an [CupertinoActionSheetAction] for the [cancelButton] given to this
/// action sheet.
///
/// An action sheet is typically passed as the child widget to
/// [showCupertinoModalPopup], which displays the action sheet by sliding it up
/// from the bottom of the screen.
///
/// {@tool snippet}
/// This sample shows how to use a [CupertinoActionSheet].
/// The [CupertinoActionSheet] shows a modal popup that slides in from the
/// bottom when [CupertinoButton] is pressed.
///
/// ```dart
/// class MyStatefulWidget extends StatefulWidget {
/// const MyStatefulWidget({Key? key}) : super(key: key);
///
/// @override
/// _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
/// }
///
/// class _MyStatefulWidgetState extends State<MyStatefulWidget> {
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoPageScaffold(
/// child: Center(
/// child: CupertinoButton(
/// onPressed: () {
/// showCupertinoModalPopup<void>(
/// context: context,
/// builder: (BuildContext context) => CupertinoActionSheet(
/// title: const Text('Title'),
/// message: const Text('Message'),
/// actions: <CupertinoActionSheetAction>[
/// CupertinoActionSheetAction(
/// child: const Text('Action One'),
/// onPressed: () {
/// Navigator.pop(context);
/// },
/// ),
/// CupertinoActionSheetAction(
/// child: const Text('Action Two'),
/// onPressed: () {
/// Navigator.pop(context);
/// },
/// )
/// ],
/// ),
/// );
/// },
/// child: const Text('CupertinoActionSheet'),
/// ),
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [CupertinoActionSheetAction], which is an iOS-style action sheet button.
/// * <https://developer.apple.com/design/human-interface-guidelines/ios/views/action-sheets/>
class
CupertinoActionSheet
extends
StatelessWidget
{
/// Creates an iOS-style action sheet.
///
/// An action sheet must have a non-null value for at least one of the
/// following arguments: [actions], [title], [message], or [cancelButton].
///
/// Generally, action sheets are used to give the user a choice between
/// two or more choices for the current context.
const
CupertinoActionSheet
({
Key
?
key
,
this
.
title
,
this
.
message
,
this
.
actions
,
this
.
messageScrollController
,
this
.
actionScrollController
,
this
.
cancelButton
,
})
:
assert
(
actions
!=
null
||
title
!=
null
||
message
!=
null
||
cancelButton
!=
null
,
'An action sheet must have a non-null value for at least one of the following arguments: '
'actions, title, message, or cancelButton'
,
),
super
(
key:
key
);
/// An optional title of the action sheet. When the [message] is non-null,
/// the font of the [title] is bold.
///
/// Typically a [Text] widget.
final
Widget
?
title
;
/// An optional descriptive message that provides more details about the
/// reason for the alert.
///
/// Typically a [Text] widget.
final
Widget
?
message
;
/// The set of actions that are displayed for the user to select.
///
/// Typically this is a list of [CupertinoActionSheetAction] widgets.
final
List
<
Widget
>?
actions
;
/// A scroll controller that can be used to control the scrolling of the
/// [message] in the action sheet.
///
/// This attribute is typically not needed, as alert messages should be
/// short.
final
ScrollController
?
messageScrollController
;
/// A scroll controller that can be used to control the scrolling of the
/// [actions] in the action sheet.
///
/// This attribute is typically not needed.
final
ScrollController
?
actionScrollController
;
/// The optional cancel button that is grouped separately from the other
/// actions.
///
/// Typically this is an [CupertinoActionSheetAction] widget.
final
Widget
?
cancelButton
;
Widget
_buildContent
(
BuildContext
context
)
{
final
List
<
Widget
>
content
=
<
Widget
>[];
if
(
title
!=
null
||
message
!=
null
)
{
final
Widget
titleSection
=
_CupertinoAlertContentSection
(
title:
title
,
message:
message
,
scrollController:
messageScrollController
,
titlePadding:
const
EdgeInsets
.
only
(
left:
_kActionSheetContentHorizontalPadding
,
right:
_kActionSheetContentHorizontalPadding
,
bottom:
_kActionSheetContentVerticalPadding
,
top:
_kActionSheetContentVerticalPadding
,
),
messagePadding:
EdgeInsets
.
only
(
left:
_kActionSheetContentHorizontalPadding
,
right:
_kActionSheetContentHorizontalPadding
,
bottom:
title
==
null
?
_kActionSheetContentVerticalPadding
:
22.0
,
top:
title
==
null
?
_kActionSheetContentVerticalPadding
:
0.0
,
),
titleTextStyle:
message
==
null
?
_kActionSheetContentStyle
:
_kActionSheetContentStyle
.
copyWith
(
fontWeight:
FontWeight
.
w600
),
messageTextStyle:
title
==
null
?
_kActionSheetContentStyle
.
copyWith
(
fontWeight:
FontWeight
.
w600
)
:
_kActionSheetContentStyle
,
additionalPaddingBetweenTitleAndMessage:
const
EdgeInsets
.
only
(
top:
8.0
),
);
content
.
add
(
Flexible
(
child:
titleSection
));
}
return
Container
(
color:
CupertinoDynamicColor
.
resolve
(
_kActionSheetBackgroundColor
,
context
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
children:
content
,
),
);
}
Widget
_buildActions
()
{
if
(
actions
==
null
||
actions
!.
isEmpty
)
{
return
Container
(
height:
0.0
,
);
}
return
_CupertinoAlertActionSection
(
children:
actions
!,
scrollController:
actionScrollController
,
hasCancelButton:
cancelButton
!=
null
,
isActionSheet:
true
,
);
}
Widget
_buildCancelButton
()
{
final
double
cancelPadding
=
(
actions
!=
null
||
message
!=
null
||
title
!=
null
)
?
_kActionSheetCancelButtonPadding
:
0.0
;
return
Padding
(
padding:
EdgeInsets
.
only
(
top:
cancelPadding
),
child:
_CupertinoActionSheetCancelButton
(
child:
cancelButton
,
),
);
}
@override
Widget
build
(
BuildContext
context
)
{
assert
(
debugCheckHasMediaQuery
(
context
));
final
List
<
Widget
>
children
=
<
Widget
>[
Flexible
(
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12.0
),
child:
BackdropFilter
(
filter:
ImageFilter
.
blur
(
sigmaX:
_kBlurAmount
,
sigmaY:
_kBlurAmount
),
child:
_CupertinoDialogRenderWidget
(
contentSection:
Builder
(
builder:
_buildContent
),
actionsSection:
_buildActions
(),
dividerColor:
_kActionSheetButtonDividerColor
,
isActionSheet:
true
,
),
),
),
),
if
(
cancelButton
!=
null
)
_buildCancelButton
(),
];
final
Orientation
orientation
=
MediaQuery
.
of
(
context
).
orientation
;
final
double
actionSheetWidth
;
if
(
orientation
==
Orientation
.
portrait
)
{
actionSheetWidth
=
MediaQuery
.
of
(
context
).
size
.
width
-
(
_kActionSheetEdgeHorizontalPadding
*
2
);
}
else
{
actionSheetWidth
=
MediaQuery
.
of
(
context
).
size
.
height
-
(
_kActionSheetEdgeHorizontalPadding
*
2
);
}
return
SafeArea
(
child:
Semantics
(
namesRoute:
true
,
scopesRoute:
true
,
explicitChildNodes:
true
,
label:
'Alert'
,
child:
CupertinoUserInterfaceLevel
(
data:
CupertinoUserInterfaceLevelData
.
elevated
,
child:
Container
(
width:
actionSheetWidth
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
_kActionSheetEdgeHorizontalPadding
,
vertical:
_kActionSheetEdgeVerticalPadding
,
),
child:
Column
(
children:
children
,
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
stretch
,
),
),
),
),
);
}
}
/// A button typically used in a [CupertinoActionSheet].
///
/// See also:
///
/// * [CupertinoActionSheet], an alert that presents the user with a set of two or
/// more choices related to the current context.
class
CupertinoActionSheetAction
extends
StatelessWidget
{
/// Creates an action for an iOS-style action sheet.
///
/// The [child] and [onPressed] arguments must not be null.
const
CupertinoActionSheetAction
({
Key
?
key
,
required
this
.
onPressed
,
this
.
isDefaultAction
=
false
,
this
.
isDestructiveAction
=
false
,
required
this
.
child
,
})
:
assert
(
child
!=
null
),
assert
(
onPressed
!=
null
),
super
(
key:
key
);
/// The callback that is called when the button is tapped.
///
/// This attribute must not be null.
final
VoidCallback
onPressed
;
/// Whether this action is the default choice in the action sheet.
///
/// Default buttons have bold text.
final
bool
isDefaultAction
;
/// Whether this action might change or delete data.
///
/// Destructive buttons have red text.
final
bool
isDestructiveAction
;
/// The widget below this widget in the tree.
///
/// Typically a [Text] widget.
final
Widget
child
;
@override
Widget
build
(
BuildContext
context
)
{
TextStyle
style
=
_kActionSheetActionStyle
.
copyWith
(
color:
isDestructiveAction
?
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
systemRed
,
context
)
:
CupertinoTheme
.
of
(
context
).
primaryColor
,
);
if
(
isDefaultAction
)
{
style
=
style
.
copyWith
(
fontWeight:
FontWeight
.
w600
);
}
return
GestureDetector
(
onTap:
onPressed
,
behavior:
HitTestBehavior
.
opaque
,
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minHeight:
_kActionSheetButtonHeight
,
),
child:
Semantics
(
button:
true
,
child:
Container
(
alignment:
Alignment
.
center
,
padding:
const
EdgeInsets
.
symmetric
(
vertical:
16.0
,
horizontal:
10.0
,
),
child:
DefaultTextStyle
(
style:
style
,
child:
child
,
textAlign:
TextAlign
.
center
,
),
),
),
),
);
}
}
class
_CupertinoActionSheetCancelButton
extends
StatefulWidget
{
const
_CupertinoActionSheetCancelButton
({
Key
?
key
,
this
.
child
,
})
:
super
(
key:
key
);
final
Widget
?
child
;
@override
_CupertinoActionSheetCancelButtonState
createState
()
=>
_CupertinoActionSheetCancelButtonState
();
}
class
_CupertinoActionSheetCancelButtonState
extends
State
<
_CupertinoActionSheetCancelButton
>
{
bool
isBeingPressed
=
false
;
void
_onTapDown
(
TapDownDetails
event
)
{
setState
(()
{
isBeingPressed
=
true
;
});
}
void
_onTapUp
(
TapUpDetails
event
)
{
setState
(()
{
isBeingPressed
=
false
;
});
}
void
_onTapCancel
()
{
setState
(()
{
isBeingPressed
=
false
;
});
}
@override
Widget
build
(
BuildContext
context
)
{
final
Color
backgroundColor
=
isBeingPressed
?
_kActionSheetCancelPressedColor
:
CupertinoColors
.
secondarySystemGroupedBackground
;
return
GestureDetector
(
excludeFromSemantics:
true
,
onTapDown:
_onTapDown
,
onTapUp:
_onTapUp
,
onTapCancel:
_onTapCancel
,
child:
Container
(
decoration:
BoxDecoration
(
color:
CupertinoDynamicColor
.
resolve
(
backgroundColor
,
context
),
borderRadius:
BorderRadius
.
circular
(
_kCornerRadius
),
),
child:
widget
.
child
,
),
);
}
}
// iOS style layout policy widget for sizing an alert dialog's content section and
// action button section.
//
...
...
@@ -339,35 +855,44 @@ class _CupertinoDialogRenderWidget extends RenderObjectWidget {
Key
?
key
,
required
this
.
contentSection
,
required
this
.
actionsSection
,
required
this
.
dividerColor
,
this
.
isActionSheet
=
false
,
})
:
super
(
key:
key
);
final
Widget
contentSection
;
final
Widget
actionsSection
;
final
Color
dividerColor
;
final
bool
isActionSheet
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
_RenderCupertinoDialog
(
dividerThickness:
_kDividerThickness
/
MediaQuery
.
of
(
context
).
devicePixelRatio
,
isInAccessibilityMode:
_isInAccessibilityMode
(
context
),
dividerColor:
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
separator
,
context
),
isInAccessibilityMode:
_isInAccessibilityMode
(
context
)
&&
!
isActionSheet
,
dividerColor:
CupertinoDynamicColor
.
resolve
(
dividerColor
,
context
),
isActionSheet:
isActionSheet
,
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderCupertinoDialog
renderObject
)
{
renderObject
..
isInAccessibilityMode
=
_isInAccessibilityMode
(
context
)
..
dividerColor
=
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
separat
or
,
context
);
..
isInAccessibilityMode
=
_isInAccessibilityMode
(
context
)
&&
!
isActionSheet
..
dividerColor
=
CupertinoDynamicColor
.
resolve
(
dividerCol
or
,
context
);
}
@override
RenderObjectElement
createElement
()
{
return
_CupertinoDialogRenderElement
(
this
);
return
_CupertinoDialogRenderElement
(
this
,
allowMoveRenderObjectChild:
isActionSheet
);
}
}
class
_CupertinoDialogRenderElement
extends
RenderObjectElement
{
_CupertinoDialogRenderElement
(
_CupertinoDialogRenderWidget
widget
)
:
super
(
widget
);
_CupertinoDialogRenderElement
(
_CupertinoDialogRenderWidget
widget
,
{
this
.
allowMoveRenderObjectChild
=
false
})
:
super
(
widget
);
// Whether to allow overriden method moveRenderObjectChild call or default to super.
// CupertinoActionSheet should default to [super] but CupertinoAlertDialog not.
final
bool
allowMoveRenderObjectChild
;
Element
?
_contentElement
;
Element
?
_actionsElement
;
...
...
@@ -397,20 +922,17 @@ class _CupertinoDialogRenderElement extends RenderObjectElement {
@override
void
insertRenderObjectChild
(
RenderObject
child
,
_AlertDialogSections
slot
)
{
assert
(
slot
!=
null
);
switch
(
slot
)
{
case
_AlertDialogSections
.
contentSection
:
renderObject
.
contentSection
=
child
as
RenderBox
;
break
;
case
_AlertDialogSections
.
actionsSection
:
renderObject
.
actionsSection
=
child
as
RenderBox
;
break
;
}
_placeChildInSlot
(
child
,
slot
);
}
@override
void
moveRenderObjectChild
(
RenderObject
child
,
_AlertDialogSections
oldSlot
,
_AlertDialogSections
newSlot
)
{
assert
(
false
);
if
(!
allowMoveRenderObjectChild
)
{
super
.
moveRenderObjectChild
(
child
,
oldSlot
,
newSlot
);
return
;
}
_placeChildInSlot
(
child
,
newSlot
);
}
@override
...
...
@@ -442,6 +964,18 @@ class _CupertinoDialogRenderElement extends RenderObjectElement {
renderObject
.
actionsSection
=
null
;
}
}
void
_placeChildInSlot
(
RenderObject
child
,
_AlertDialogSections
slot
)
{
assert
(
slot
!=
null
);
switch
(
slot
)
{
case
_AlertDialogSections
.
contentSection
:
renderObject
.
contentSection
=
child
as
RenderBox
;
break
;
case
_AlertDialogSections
.
actionsSection
:
renderObject
.
actionsSection
=
child
as
RenderBox
;
break
;
}
}
}
// iOS style layout policy for sizing an alert dialog's content section and action
...
...
@@ -474,15 +1008,16 @@ class _RenderCupertinoDialog extends RenderBox {
RenderBox
?
actionsSection
,
double
dividerThickness
=
0.0
,
bool
isInAccessibilityMode
=
false
,
bool
isActionSheet
=
false
,
required
Color
dividerColor
,
})
:
_contentSection
=
contentSection
,
_actionsSection
=
actionsSection
,
_dividerThickness
=
dividerThickness
,
_isInAccessibilityMode
=
isInAccessibilityMode
,
_isActionSheet
=
isActionSheet
,
_dividerPaint
=
Paint
()
..
color
=
dividerColor
..
style
=
PaintingStyle
.
fill
;
..
color
=
dividerColor
..
style
=
PaintingStyle
.
fill
;
RenderBox
?
get
contentSection
=>
_contentSection
;
RenderBox
?
_contentSection
;
...
...
@@ -521,6 +1056,15 @@ class _RenderCupertinoDialog extends RenderBox {
}
}
bool
_isActionSheet
;
bool
get
isActionSheet
=>
_isActionSheet
;
set
isActionSheet
(
bool
newValue
)
{
if
(
newValue
!=
_isActionSheet
)
{
_isActionSheet
=
newValue
;
markNeedsLayout
();
}
}
double
get
_dialogWidth
=>
isInAccessibilityMode
?
_kAccessibilityCupertinoDialogWidth
:
_kCupertinoDialogWidth
;
...
...
@@ -572,8 +1116,10 @@ class _RenderCupertinoDialog extends RenderBox {
@override
void
setupParentData
(
RenderBox
child
)
{
if
(
child
.
parentData
is
!
BoxParentData
)
{
if
(
!
isActionSheet
&&
child
.
parentData
is
!
BoxParentData
)
{
child
.
parentData
=
BoxParentData
();
}
else
if
(
child
.
parentData
is
!
MultiChildLayoutParentData
)
{
child
.
parentData
=
MultiChildLayoutParentData
();
}
}
...
...
@@ -595,12 +1141,12 @@ class _RenderCupertinoDialog extends RenderBox {
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
return
_dialogWidth
;
return
isActionSheet
?
constraints
.
minWidth
:
_dialogWidth
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
return
_dialogWidth
;
return
isActionSheet
?
constraints
.
maxWidth
:
_dialogWidth
;
}
@override
...
...
@@ -608,10 +1154,14 @@ class _RenderCupertinoDialog extends RenderBox {
final
double
contentHeight
=
contentSection
!.
getMinIntrinsicHeight
(
width
);
final
double
actionsHeight
=
actionsSection
!.
getMinIntrinsicHeight
(
width
);
final
bool
hasDivider
=
contentHeight
>
0.0
&&
actionsHeight
>
0.0
;
final
double
height
=
contentHeight
+
(
hasDivider
?
_dividerThickness
:
0.0
)
+
actionsHeight
;
double
height
=
contentHeight
+
(
hasDivider
?
_dividerThickness
:
0.0
)
+
actionsHeight
;
if
(
height
.
isFinite
)
if
(
isActionSheet
&&
(
actionsHeight
>
0
||
contentHeight
>
0
))
{
height
-=
2
*
_kActionSheetEdgeVerticalPadding
;
}
if
(
height
.
isFinite
)
{
return
height
;
}
return
0.0
;
}
...
...
@@ -620,10 +1170,14 @@ class _RenderCupertinoDialog extends RenderBox {
final
double
contentHeight
=
contentSection
!.
getMaxIntrinsicHeight
(
width
);
final
double
actionsHeight
=
actionsSection
!.
getMaxIntrinsicHeight
(
width
);
final
bool
hasDivider
=
contentHeight
>
0.0
&&
actionsHeight
>
0.0
;
final
double
height
=
contentHeight
+
(
hasDivider
?
_dividerThickness
:
0.0
)
+
actionsHeight
;
double
height
=
contentHeight
+
(
hasDivider
?
_dividerThickness
:
0.0
)
+
actionsHeight
;
if
(
height
.
isFinite
)
if
(
isActionSheet
&&
(
actionsHeight
>
0
||
contentHeight
>
0
))
{
height
-=
2
*
_kActionSheetEdgeVerticalPadding
;
}
if
(
height
.
isFinite
)
{
return
height
;
}
return
0.0
;
}
...
...
@@ -637,7 +1191,7 @@ class _RenderCupertinoDialog extends RenderBox {
@override
void
performLayout
()
{
final
_DialogSizes
dialogSizes
=
_performLayout
(
final
_
Alert
DialogSizes
dialogSizes
=
_performLayout
(
constraints:
constraints
,
layoutChild:
ChildLayoutHelper
.
layoutChild
,
);
...
...
@@ -645,31 +1199,37 @@ class _RenderCupertinoDialog extends RenderBox {
// Set the position of the actions box to sit at the bottom of the dialog.
// The content box defaults to the top left, which is where we want it.
assert
(
actionsSection
!.
parentData
is
BoxParentData
);
final
BoxParentData
actionParentData
=
actionsSection
!.
parentData
!
as
BoxParentData
;
actionParentData
.
offset
=
Offset
(
0.0
,
dialogSizes
.
actionSectionYOffset
);
assert
((!
isActionSheet
&&
actionsSection
!.
parentData
is
BoxParentData
)
||
(
isActionSheet
&&
actionsSection
!.
parentData
is
MultiChildLayoutParentData
));
if
(
isActionSheet
)
{
final
MultiChildLayoutParentData
actionParentData
=
actionsSection
!.
parentData
!
as
MultiChildLayoutParentData
;
actionParentData
.
offset
=
Offset
(
0.0
,
dialogSizes
.
contentHeight
+
dialogSizes
.
dividerThickness
);
}
else
{
final
BoxParentData
actionParentData
=
actionsSection
!.
parentData
!
as
BoxParentData
;
actionParentData
.
offset
=
Offset
(
0.0
,
dialogSizes
.
contentHeight
+
dialogSizes
.
dividerThickness
);
}
}
_DialogSizes
_performLayout
({
required
BoxConstraints
constraints
,
required
ChildLayouter
layoutChild
})
{
_
Alert
DialogSizes
_performLayout
({
required
BoxConstraints
constraints
,
required
ChildLayouter
layoutChild
})
{
return
isInAccessibilityMode
?
performAccessibilityLayout
(
constraints:
constraints
,
layoutChild:
layoutChild
,
)
:
performRegularLayout
(
constraints:
constraints
,
layoutChild:
layoutChild
,
);
constraints:
constraints
,
layoutChild:
layoutChild
,
)
:
performRegularLayout
(
constraints:
constraints
,
layoutChild:
layoutChild
,
);
}
// When not in accessibility mode, an alert dialog might reduce the space
// for buttons to just over 1 button's height to make room for the content
// section.
_DialogSizes
performRegularLayout
({
required
BoxConstraints
constraints
,
required
ChildLayouter
layoutChild
})
{
final
bool
hasDivider
=
contentSection
!.
getMaxIntrinsicHeight
(
_dialogWidth
)
>
0.0
&&
actionsSection
!.
getMaxIntrinsicHeight
(
_dialogWidth
)
>
0.0
;
_
Alert
DialogSizes
performRegularLayout
({
required
BoxConstraints
constraints
,
required
ChildLayouter
layoutChild
})
{
final
bool
hasDivider
=
contentSection
!.
getMaxIntrinsicHeight
(
computeMaxIntrinsicWidth
(
0
)
)
>
0.0
&&
actionsSection
!.
getMaxIntrinsicHeight
(
computeMaxIntrinsicWidth
(
0
)
)
>
0.0
;
final
double
dividerThickness
=
hasDivider
?
_dividerThickness
:
0.0
;
final
double
minActionsHeight
=
actionsSection
!.
getMinIntrinsicHeight
(
_dialogWidth
);
final
double
minActionsHeight
=
actionsSection
!.
getMinIntrinsicHeight
(
computeMaxIntrinsicWidth
(
0
)
);
final
Size
contentSize
=
layoutChild
(
contentSection
!,
...
...
@@ -683,15 +1243,18 @@ class _RenderCupertinoDialog extends RenderBox {
final
double
dialogHeight
=
contentSize
.
height
+
dividerThickness
+
actionsSize
.
height
;
return
_DialogSizes
(
size:
constraints
.
constrain
(
Size
(
_dialogWidth
,
dialogHeight
)),
actionSectionYOffset:
contentSize
.
height
+
dividerThickness
,
return
_AlertDialogSizes
(
size:
isActionSheet
?
Size
(
constraints
.
maxWidth
,
dialogHeight
)
:
constraints
.
constrain
(
Size
(
_dialogWidth
,
dialogHeight
)),
contentHeight:
contentSize
.
height
,
dividerThickness:
dividerThickness
,
);
}
// When in accessibility mode, an alert dialog will allow buttons to take
// up to 50% of the dialog height, even if the content exceeds available space.
_DialogSizes
performAccessibilityLayout
({
required
BoxConstraints
constraints
,
required
ChildLayouter
layoutChild
})
{
_
Alert
DialogSizes
performAccessibilityLayout
({
required
BoxConstraints
constraints
,
required
ChildLayouter
layoutChild
})
{
final
bool
hasDivider
=
contentSection
!.
getMaxIntrinsicHeight
(
_dialogWidth
)
>
0.0
&&
actionsSection
!.
getMaxIntrinsicHeight
(
_dialogWidth
)
>
0.0
;
final
double
dividerThickness
=
hasDivider
?
_dividerThickness
:
0.0
;
...
...
@@ -702,10 +1265,10 @@ class _RenderCupertinoDialog extends RenderBox {
final
Size
contentSize
;
final
Size
actionsSize
;
if
(
maxContentHeight
+
dividerThickness
+
maxActionsHeight
>
constraints
.
maxHeight
)
{
//
There isn't enough room for everything. Following iOS's accessibility dialog
//
layout policy, first we allow the actions to take up to 50% of the dialog
//
height. Second we fill the rest of the available space with the content
// section.
//
AlertDialog: There isn't enough room for everything. Following iOS's
//
accessibility dialog layout policy, first we allow the actions to take
//
up to 50% of the dialog height. Second we fill the rest of the
//
available space with the content
section.
actionsSize
=
layoutChild
(
actionsSection
!,
...
...
@@ -733,24 +1296,35 @@ class _RenderCupertinoDialog extends RenderBox {
// Calculate overall dialog height.
final
double
dialogHeight
=
contentSize
.
height
+
dividerThickness
+
actionsSize
.
height
;
return
_DialogSizes
(
return
_
Alert
DialogSizes
(
size:
constraints
.
constrain
(
Size
(
_dialogWidth
,
dialogHeight
)),
actionSectionYOffset:
contentSize
.
height
+
dividerThickness
,
contentHeight:
contentSize
.
height
,
dividerThickness:
dividerThickness
,
);
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
BoxParentData
contentParentData
=
contentSection
!.
parentData
!
as
BoxParentData
;
contentSection
!.
paint
(
context
,
offset
+
contentParentData
.
offset
);
if
(
isActionSheet
)
{
final
MultiChildLayoutParentData
contentParentData
=
contentSection
!.
parentData
!
as
MultiChildLayoutParentData
;
contentSection
!.
paint
(
context
,
offset
+
contentParentData
.
offset
);
}
else
{
final
BoxParentData
contentParentData
=
contentSection
!.
parentData
!
as
BoxParentData
;
contentSection
!.
paint
(
context
,
offset
+
contentParentData
.
offset
);
}
final
bool
hasDivider
=
contentSection
!.
size
.
height
>
0.0
&&
actionsSection
!.
size
.
height
>
0.0
;
if
(
hasDivider
)
{
_paintDividerBetweenContentAndActions
(
context
.
canvas
,
offset
);
}
final
BoxParentData
actionsParentData
=
actionsSection
!.
parentData
!
as
BoxParentData
;
actionsSection
!.
paint
(
context
,
offset
+
actionsParentData
.
offset
);
if
(
isActionSheet
)
{
final
MultiChildLayoutParentData
actionsParentData
=
actionsSection
!.
parentData
!
as
MultiChildLayoutParentData
;
actionsSection
!.
paint
(
context
,
offset
+
actionsParentData
.
offset
);
}
else
{
final
BoxParentData
actionsParentData
=
actionsSection
!.
parentData
!
as
BoxParentData
;
actionsSection
!.
paint
(
context
,
offset
+
actionsParentData
.
offset
);
}
}
void
_paintDividerBetweenContentAndActions
(
Canvas
canvas
,
Offset
offset
)
{
...
...
@@ -767,32 +1341,58 @@ class _RenderCupertinoDialog extends RenderBox {
@override
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
required
Offset
position
})
{
if
(
isActionSheet
)
{
final
MultiChildLayoutParentData
contentSectionParentData
=
contentSection
!.
parentData
!
as
MultiChildLayoutParentData
;
final
MultiChildLayoutParentData
actionsSectionParentData
=
actionsSection
!.
parentData
!
as
MultiChildLayoutParentData
;
return
result
.
addWithPaintOffset
(
offset:
contentSectionParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
contentSectionParentData
.
offset
);
return
contentSection
!.
hitTest
(
result
,
position:
transformed
);
},
)
||
result
.
addWithPaintOffset
(
offset:
actionsSectionParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
actionsSectionParentData
.
offset
);
return
actionsSection
!.
hitTest
(
result
,
position:
transformed
);
},
);
}
final
BoxParentData
contentSectionParentData
=
contentSection
!.
parentData
!
as
BoxParentData
;
final
BoxParentData
actionsSectionParentData
=
actionsSection
!.
parentData
!
as
BoxParentData
;
return
result
.
addWithPaintOffset
(
offset:
contentSectionParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
contentSectionParentData
.
offset
);
return
contentSection
!.
hitTest
(
result
,
position:
transformed
);
},
)
||
result
.
addWithPaintOffset
(
offset:
actionsSectionParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
actionsSectionParentData
.
offset
);
return
actionsSection
!.
hitTest
(
result
,
position:
transformed
);
},
);
offset:
contentSectionParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
contentSectionParentData
.
offset
);
return
contentSection
!.
hitTest
(
result
,
position:
transformed
);
},
)
||
result
.
addWithPaintOffset
(
offset:
actionsSectionParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
transformed
)
{
assert
(
transformed
==
position
-
actionsSectionParentData
.
offset
);
return
actionsSection
!.
hitTest
(
result
,
position:
transformed
);
},
);
}
}
class
_DialogSizes
{
const
_DialogSizes
({
required
this
.
size
,
required
this
.
actionSectionYOffset
});
class
_AlertDialogSizes
{
const
_AlertDialogSizes
({
required
this
.
size
,
required
this
.
contentHeight
,
required
this
.
dividerThickness
,
});
final
Size
size
;
final
double
actionSectionYOffset
;
final
double
contentHeight
;
final
double
dividerThickness
;
}
// Visual components of an alert dialog that need to be explicitly sized and
...
...
@@ -811,9 +1411,16 @@ class _CupertinoAlertContentSection extends StatelessWidget {
const
_CupertinoAlertContentSection
({
Key
?
key
,
this
.
title
,
this
.
content
,
this
.
message
,
this
.
scrollController
,
})
:
super
(
key:
key
);
this
.
titlePadding
,
this
.
messagePadding
,
this
.
titleTextStyle
,
this
.
messageTextStyle
,
this
.
additionalPaddingBetweenTitleAndMessage
,
})
:
assert
(
title
==
null
||
titlePadding
!=
null
&&
titleTextStyle
!=
null
),
assert
(
message
==
null
||
messagePadding
!=
null
&&
messageTextStyle
!=
null
),
super
(
key:
key
);
// The (optional) title of the dialog is displayed in a large font at the top
// of the dialog.
...
...
@@ -821,11 +1428,11 @@ class _CupertinoAlertContentSection extends StatelessWidget {
// Typically a Text widget.
final
Widget
?
title
;
// The (optional)
content
of the dialog is displayed in the center of the
// The (optional)
message
of the dialog is displayed in the center of the
// dialog in a lighter font.
//
// Typically a Text widget.
final
Widget
?
content
;
final
Widget
?
message
;
// A scroll controller that can be used to control the scrolling of the
// content in the dialog.
...
...
@@ -834,51 +1441,55 @@ class _CupertinoAlertContentSection extends StatelessWidget {
// are short.
final
ScrollController
?
scrollController
;
// Paddings used around title and message.
// CupertinoAlertDialog and CupertinoActionSheet have different paddings.
final
EdgeInsets
?
titlePadding
;
final
EdgeInsets
?
messagePadding
;
// Additional padding to be inserted between title and message.
// Only used for CupertinoActionSheet.
final
EdgeInsets
?
additionalPaddingBetweenTitleAndMessage
;
// Text styles used for title and message.
// CupertinoAlertDialog and CupertinoActionSheet have different text styles.
final
TextStyle
?
titleTextStyle
;
final
TextStyle
?
messageTextStyle
;
@override
Widget
build
(
BuildContext
context
)
{
if
(
title
==
null
&&
content
==
null
)
{
if
(
title
==
null
&&
message
==
null
)
{
return
SingleChildScrollView
(
controller:
scrollController
,
child:
const
SizedBox
(
width:
0.0
,
height:
0.0
),
);
}
final
double
textScaleFactor
=
MediaQuery
.
of
(
context
).
textScaleFactor
;
final
List
<
Widget
>
titleContentGroup
=
<
Widget
>[
if
(
title
!=
null
)
Padding
(
padding:
EdgeInsets
.
only
(
left:
_kEdgePadding
,
right:
_kEdgePadding
,
bottom:
content
==
null
?
_kEdgePadding
:
1.0
,
top:
_kEdgePadding
*
textScaleFactor
,
),
padding:
titlePadding
!,
child:
DefaultTextStyle
(
style:
_kCupertinoDialogTitleStyle
.
copyWith
(
color:
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
label
,
context
),
),
style:
titleTextStyle
!,
textAlign:
TextAlign
.
center
,
child:
title
!,
),
),
if
(
content
!=
null
)
if
(
message
!=
null
)
Padding
(
padding:
EdgeInsets
.
only
(
left:
_kEdgePadding
,
right:
_kEdgePadding
,
bottom:
_kEdgePadding
*
textScaleFactor
,
top:
title
==
null
?
_kEdgePadding
:
1.0
,
),
padding:
messagePadding
!,
child:
DefaultTextStyle
(
style:
_kCupertinoDialogContentStyle
.
copyWith
(
color:
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
label
,
context
),
),
style:
messageTextStyle
!,
textAlign:
TextAlign
.
center
,
child:
content
!,
child:
message
!,
),
),
];
// Add padding between the widgets if necessary.
if
(
additionalPaddingBetweenTitleAndMessage
!=
null
&&
titleContentGroup
.
length
>
1
)
{
titleContentGroup
.
insert
(
1
,
Padding
(
padding:
additionalPaddingBetweenTitleAndMessage
!));
}
return
CupertinoScrollbar
(
child:
SingleChildScrollView
(
controller:
scrollController
,
...
...
@@ -901,6 +1512,8 @@ class _CupertinoAlertActionSection extends StatefulWidget {
Key
?
key
,
required
this
.
children
,
this
.
scrollController
,
this
.
hasCancelButton
=
false
,
this
.
isActionSheet
=
false
,
})
:
assert
(
children
!=
null
),
super
(
key:
key
);
...
...
@@ -913,11 +1526,21 @@ class _CupertinoAlertActionSection extends StatefulWidget {
// don't have many actions.
final
ScrollController
?
scrollController
;
// Used in ActionSheet to denote if ActionSheet has a separate so-called
// cancel button.
//
// Defaults to false, and is not needed in dialogs.
final
bool
hasCancelButton
;
final
bool
isActionSheet
;
@override
_CupertinoAlertActionSectionState
createState
()
=>
_CupertinoAlertActionSectionState
();
_CupertinoAlertActionSectionState
createState
()
=>
_CupertinoAlertActionSectionState
();
}
class
_CupertinoAlertActionSectionState
extends
State
<
_CupertinoAlertActionSection
>
{
class
_CupertinoAlertActionSectionState
extends
State
<
_CupertinoAlertActionSection
>
{
@override
Widget
build
(
BuildContext
context
)
{
final
double
devicePixelRatio
=
MediaQuery
.
of
(
context
).
devicePixelRatio
;
...
...
@@ -937,6 +1560,8 @@ class _CupertinoAlertActionSectionState extends State<_CupertinoAlertActionSecti
child:
_CupertinoDialogActionsRenderWidget
(
actionButtons:
interactiveButtons
,
dividerThickness:
_kDividerThickness
/
devicePixelRatio
,
hasCancelButton:
widget
.
hasCancelButton
,
isActionSheet:
widget
.
isActionSheet
,
),
),
);
...
...
@@ -995,7 +1620,8 @@ class _PressableActionButtonState extends State<_PressableActionButton> {
// _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for
// updating the pressed state of an _ActionButtonParentData based on the
// incoming [isPressed] property.
class
_ActionButtonParentDataWidget
extends
ParentDataWidget
<
_ActionButtonParentData
>
{
class
_ActionButtonParentDataWidget
extends
ParentDataWidget
<
_ActionButtonParentData
>
{
const
_ActionButtonParentDataWidget
({
Key
?
key
,
required
this
.
isPressed
,
...
...
@@ -1007,7 +1633,8 @@ class _ActionButtonParentDataWidget extends ParentDataWidget<_ActionButtonParent
@override
void
applyParentData
(
RenderObject
renderObject
)
{
assert
(
renderObject
.
parentData
is
_ActionButtonParentData
);
final
_ActionButtonParentData
parentData
=
renderObject
.
parentData
!
as
_ActionButtonParentData
;
final
_ActionButtonParentData
parentData
=
renderObject
.
parentData
!
as
_ActionButtonParentData
;
if
(
parentData
.
isPressed
!=
isPressed
)
{
parentData
.
isPressed
=
isPressed
;
...
...
@@ -1019,7 +1646,8 @@ class _ActionButtonParentDataWidget extends ParentDataWidget<_ActionButtonParent
}
@override
Type
get
debugTypicalAncestorWidgetClass
=>
_CupertinoDialogActionsRenderWidget
;
Type
get
debugTypicalAncestorWidgetClass
=>
_CupertinoDialogActionsRenderWidget
;
}
// ParentData applied to individual action buttons that report whether or not
...
...
@@ -1115,7 +1743,7 @@ class CupertinoDialogAction extends StatelessWidget {
// iOS scale factor) vs the minimum text size that we allow in action
// buttons. This ratio information is used to automatically scale down action
// button text to fit the available space.
final
double
fontSizeRatio
=
(
textScaleFactor
*
textStyle
.
fontSize
!)
/
_kMinButtonFontSize
;
final
double
fontSizeRatio
=
(
textScaleFactor
*
textStyle
.
fontSize
!)
/
_k
Dialog
MinButtonFontSize
;
final
double
padding
=
_calculatePadding
(
context
);
return
IntrinsicHeight
(
...
...
@@ -1162,7 +1790,7 @@ class CupertinoDialogAction extends StatelessWidget {
Widget
build
(
BuildContext
context
)
{
TextStyle
style
=
_kCupertinoDialogActionStyle
.
copyWith
(
color:
CupertinoDynamicColor
.
resolve
(
isDestructiveAction
?
CupertinoColors
.
systemRed
:
CupertinoColors
.
systemBlue
,
isDestructiveAction
?
CupertinoColors
.
systemRed
:
CupertinoColors
.
systemBlue
,
context
,
),
);
...
...
@@ -1183,15 +1811,15 @@ class CupertinoDialogAction extends StatelessWidget {
// wrap instead of ellipsizing. We are consciously not implementing that
// now due to complexity.
final
Widget
sizedContent
=
_isInAccessibilityMode
(
context
)
?
_buildContentWithAccessibilitySizingPolicy
(
textStyle:
style
,
content:
child
,
)
:
_buildContentWithRegularSizingPolicy
(
context:
context
,
textStyle:
style
,
content:
child
,
);
?
_buildContentWithAccessibilitySizingPolicy
(
textStyle:
style
,
content:
child
,
)
:
_buildContentWithRegularSizingPolicy
(
context:
context
,
textStyle:
style
,
content:
child
,
);
return
GestureDetector
(
excludeFromSemantics:
true
,
...
...
@@ -1199,7 +1827,7 @@ class CupertinoDialogAction extends StatelessWidget {
behavior:
HitTestBehavior
.
opaque
,
child:
ConstrainedBox
(
constraints:
const
BoxConstraints
(
minHeight:
_kMinButtonHeight
,
minHeight:
_k
Dialog
MinButtonHeight
,
),
child:
Container
(
alignment:
Alignment
.
center
,
...
...
@@ -1223,34 +1851,49 @@ class _CupertinoDialogActionsRenderWidget extends MultiChildRenderObjectWidget {
Key
?
key
,
required
List
<
Widget
>
actionButtons
,
double
dividerThickness
=
0.0
,
bool
hasCancelButton
=
false
,
bool
isActionSheet
=
false
,
})
:
_dividerThickness
=
dividerThickness
,
_hasCancelButton
=
hasCancelButton
,
_isActionSheet
=
isActionSheet
,
super
(
key:
key
,
children:
actionButtons
);
final
double
_dividerThickness
;
final
bool
_hasCancelButton
;
final
bool
_isActionSheet
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
_RenderCupertinoDialogActions
(
dialogWidth:
_isInAccessibilityMode
(
context
)
?
_kAccessibilityCupertinoDialogWidth
:
_kCupertinoDialogWidth
,
dialogWidth:
_isActionSheet
?
null
:
_isInAccessibilityMode
(
context
)
?
_kAccessibilityCupertinoDialogWidth
:
_kCupertinoDialogWidth
,
dividerThickness:
_dividerThickness
,
dialogColor:
CupertinoDynamicColor
.
resolve
(
_kDialogColor
,
context
),
dialogPressedColor:
CupertinoDynamicColor
.
resolve
(
_kDialogPressedColor
,
context
),
dividerColor:
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
separator
,
context
),
dialogColor:
CupertinoDynamicColor
.
resolve
(
_isActionSheet
?
_kActionSheetBackgroundColor
:
_kDialogColor
,
context
),
dialogPressedColor:
CupertinoDynamicColor
.
resolve
(
_kPressedColor
,
context
),
dividerColor:
CupertinoDynamicColor
.
resolve
(
_isActionSheet
?
_kActionSheetButtonDividerColor
:
CupertinoColors
.
separator
,
context
),
hasCancelButton:
_hasCancelButton
,
isActionSheet:
_isActionSheet
,
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderCupertinoDialogActions
renderObject
)
{
void
updateRenderObject
(
BuildContext
context
,
_RenderCupertinoDialogActions
renderObject
)
{
renderObject
..
dialogWidth
=
_isInAccessibilityMode
(
context
)
?
_kAccessibilityCupertinoDialogWidth
:
_kCupertinoDialogWidth
..
dialogWidth
=
_isActionSheet
?
null
:
_isInAccessibilityMode
(
context
)
?
_kAccessibilityCupertinoDialogWidth
:
_kCupertinoDialogWidth
..
dividerThickness
=
_dividerThickness
..
dialogColor
=
CupertinoDynamicColor
.
resolve
(
_kDialogColor
,
context
)
..
dialogPressedColor
=
CupertinoDynamicColor
.
resolve
(
_kDialogPressedColor
,
context
)
..
dividerColor
=
CupertinoDynamicColor
.
resolve
(
CupertinoColors
.
separator
,
context
);
..
dialogColor
=
CupertinoDynamicColor
.
resolve
(
_isActionSheet
?
_kActionSheetBackgroundColor
:
_kDialogColor
,
context
)
..
dialogPressedColor
=
CupertinoDynamicColor
.
resolve
(
_kPressedColor
,
context
)
..
dividerColor
=
CupertinoDynamicColor
.
resolve
(
_isActionSheet
?
_kActionSheetButtonDividerColor
:
CupertinoColors
.
separator
,
context
)
..
hasCancelButton
=
_hasCancelButton
..
isActionSheet
=
_isActionSheet
;
}
}
...
...
@@ -1293,28 +1936,33 @@ class _RenderCupertinoDialogActions extends RenderBox
RenderBoxContainerDefaultsMixin
<
RenderBox
,
MultiChildLayoutParentData
>
{
_RenderCupertinoDialogActions
({
List
<
RenderBox
>?
children
,
required
double
dialogWidth
,
double
?
dialogWidth
,
double
dividerThickness
=
0.0
,
required
Color
dialogColor
,
required
Color
dialogPressedColor
,
required
Color
dividerColor
,
})
:
_dialogWidth
=
dialogWidth
,
bool
hasCancelButton
=
false
,
bool
isActionSheet
=
false
,
})
:
assert
(
isActionSheet
||
dialogWidth
!=
null
),
_dialogWidth
=
dialogWidth
,
_buttonBackgroundPaint
=
Paint
()
..
color
=
dialogColor
..
style
=
PaintingStyle
.
fill
,
_pressedButtonBackgroundPaint
=
Paint
()
..
color
=
dialogPressedColor
..
style
=
PaintingStyle
.
fill
,
_dividerPaint
=
Paint
()
..
color
=
dividerColor
..
style
=
PaintingStyle
.
fill
,
_dividerThickness
=
dividerThickness
{
..
color
=
dialogColor
..
style
=
PaintingStyle
.
fill
,
_pressedButtonBackgroundPaint
=
Paint
()
..
color
=
dialogPressedColor
..
style
=
PaintingStyle
.
fill
,
_dividerPaint
=
Paint
()
..
color
=
dividerColor
..
style
=
PaintingStyle
.
fill
,
_dividerThickness
=
dividerThickness
,
_hasCancelButton
=
hasCancelButton
,
_isActionSheet
=
isActionSheet
{
addAll
(
children
);
}
double
get
dialogWidth
=>
_dialogWidth
;
double
_dialogWidth
;
set
dialogWidth
(
double
newWidth
)
{
double
?
get
dialogWidth
=>
_dialogWidth
;
double
?
_dialogWidth
;
set
dialogWidth
(
double
?
newWidth
)
{
if
(
newWidth
!=
_dialogWidth
)
{
_dialogWidth
=
newWidth
;
markNeedsLayout
();
...
...
@@ -1331,6 +1979,16 @@ class _RenderCupertinoDialogActions extends RenderBox
}
}
bool
_hasCancelButton
;
bool
get
hasCancelButton
=>
_hasCancelButton
;
set
hasCancelButton
(
bool
newValue
)
{
if
(
newValue
==
_hasCancelButton
)
return
;
_hasCancelButton
=
newValue
;
markNeedsLayout
();
}
final
Paint
_buttonBackgroundPaint
;
set
dialogColor
(
Color
value
)
{
if
(
value
==
_buttonBackgroundPaint
.
color
)
...
...
@@ -1358,6 +2016,16 @@ class _RenderCupertinoDialogActions extends RenderBox
markNeedsPaint
();
}
bool
_isActionSheet
;
bool
get
isActionSheet
=>
_isActionSheet
;
set
isActionSheet
(
bool
value
)
{
if
(
value
==
_isActionSheet
)
return
;
_isActionSheet
=
value
;
markNeedsPaint
();
}
Iterable
<
RenderBox
>
get
_pressedButtons
sync
*
{
RenderBox
?
currentChild
=
firstChild
;
while
(
currentChild
!=
null
)
{
...
...
@@ -1391,33 +2059,49 @@ class _RenderCupertinoDialogActions extends RenderBox
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
return
dialogWidth
;
return
isActionSheet
?
constraints
.
minWidth
:
dialogWidth
!
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
return
dialogWidth
;
return
isActionSheet
?
constraints
.
maxWidth
:
dialogWidth
!
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
final
double
minHeight
;
if
(
childCount
==
0
)
{
minHeight
=
0.0
;
return
0.0
;
}
else
if
(
isActionSheet
)
{
if
(
childCount
==
1
)
return
firstChild
!.
computeMaxIntrinsicHeight
(
width
)
+
dividerThickness
;
if
(
hasCancelButton
&&
childCount
<
4
)
return
_computeMinIntrinsicHeightWithCancel
(
width
);
return
_computeMinIntrinsicHeightStacked
(
width
);
}
else
if
(
childCount
==
1
)
{
// If only 1 button, display the button across the entire dialog.
minHeight
=
_computeMinIntrinsicHeightSideBySide
(
width
);
}
else
{
if
(
childCount
==
2
&&
_isSingleButtonRow
(
width
))
{
// The first 2 buttons fit side-by-side. Display them horizontally.
minHeight
=
_computeMinIntrinsicHeightSideBySide
(
width
);
}
else
{
// 3+ buttons are always stacked. The minimum height when stacked is
// 1.5 buttons tall.
minHeight
=
_computeMinIntrinsicHeightStacked
(
width
);
}
return
_computeMinIntrinsicHeightSideBySide
(
width
);
}
else
if
(
childCount
==
2
&&
_isSingleButtonRow
(
width
))
{
// The first 2 buttons fit side-by-side. Display them horizontally.
return
_computeMinIntrinsicHeightSideBySide
(
width
);
}
return
minHeight
;
// 3+ buttons are always stacked. The minimum height when stacked is
// 1.5 buttons tall.
return
_computeMinIntrinsicHeightStacked
(
width
);
}
// The minimum height for more than 2-3 buttons when a cancel button is
// included is the full height of button stack.
double
_computeMinIntrinsicHeightWithCancel
(
double
width
)
{
assert
(
childCount
==
2
||
childCount
==
3
);
if
(
childCount
==
2
)
{
return
firstChild
!.
getMinIntrinsicHeight
(
width
)
+
childAfter
(
firstChild
!)!.
getMinIntrinsicHeight
(
width
)
+
dividerThickness
;
}
return
firstChild
!.
getMinIntrinsicHeight
(
width
)
+
childAfter
(
firstChild
!)!.
getMinIntrinsicHeight
(
width
)
+
childAfter
(
childAfter
(
firstChild
!)!)!.
getMinIntrinsicHeight
(
width
)
+
(
dividerThickness
*
2
);
}
// The minimum height for a single row of buttons is the larger of the buttons'
...
...
@@ -1438,46 +2122,51 @@ class _RenderCupertinoDialogActions extends RenderBox
return
minHeight
;
}
// The minimum height for 2+ stacked buttons is the height of the 1st button
// + 50% the height of the 2nd button + the divider between the two.
// Dialog: The minimum height for 2+ stacked buttons is the height of the 1st
// button + 50% the height of the 2nd button + the divider between the two.
//
// ActionSheet: The minimum height for more than 2 buttons when no cancel
// button or 4+ buttons when a cancel button is included is the height of the
// 1st button + 50% the height of the 2nd button + 2 dividers.
double
_computeMinIntrinsicHeightStacked
(
double
width
)
{
assert
(
childCount
>=
2
);
return
firstChild
!.
getMinIntrinsicHeight
(
width
)
+
dividerThickness
+
(
0.5
*
childAfter
(
firstChild
!)!.
getMinIntrinsicHeight
(
width
));
+
dividerThickness
+
(
0.5
*
childAfter
(
firstChild
!)!.
getMinIntrinsicHeight
(
width
));
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
final
double
maxHeight
;
if
(
childCount
==
0
)
{
// No buttons. Zero height.
maxHeight
=
0.0
;
return
0.0
;
}
else
if
(
isActionSheet
)
{
if
(
childCount
==
1
)
return
firstChild
!.
computeMaxIntrinsicHeight
(
width
)
+
dividerThickness
;
return
_computeMaxIntrinsicHeightStacked
(
width
);
}
else
if
(
childCount
==
1
)
{
// One button. Our max intrinsic height is equal to the button's.
maxHeight
=
firstChild
!.
getMaxIntrinsicHeight
(
width
);
return
firstChild
!.
getMaxIntrinsicHeight
(
width
);
}
else
if
(
childCount
==
2
)
{
// Two buttons...
if
(
_isSingleButtonRow
(
width
))
{
// The 2 buttons fit side by side so our max intrinsic height is equal
// to the taller of the 2 buttons.
final
double
perButtonWidth
=
(
width
-
dividerThickness
)
/
2.0
;
maxHeight
=
math
.
max
(
return
math
.
max
(
firstChild
!.
getMaxIntrinsicHeight
(
perButtonWidth
),
lastChild
!.
getMaxIntrinsicHeight
(
perButtonWidth
),
);
}
else
{
// The 2 buttons do not fit side by side. Measure total height as a
// vertical stack.
maxHeight
=
_computeMaxIntrinsicHeightStacked
(
width
);
return
_computeMaxIntrinsicHeightStacked
(
width
);
}
}
else
{
// Three+ buttons. Stack the buttons vertically with dividers and measure
// the overall height.
maxHeight
=
_computeMaxIntrinsicHeightStacked
(
width
);
}
return
maxHeight
;
// Three+ buttons. Stack the buttons vertically with dividers and measure
// the overall height.
return
_computeMaxIntrinsicHeightStacked
(
width
);
}
// Max height of a stack of buttons is the sum of all button heights + a
...
...
@@ -1514,20 +2203,20 @@ class _RenderCupertinoDialogActions extends RenderBox
@override
Size
computeDryLayout
(
BoxConstraints
constraints
)
{
return
_
compute
Layout
(
constraints:
constraints
,
dry:
true
);
return
_
perform
Layout
(
constraints:
constraints
,
dry:
true
);
}
@override
void
performLayout
()
{
size
=
_
compute
Layout
(
constraints:
constraints
,
dry:
false
);
size
=
_
perform
Layout
(
constraints:
constraints
,
dry:
false
);
}
Size
_
compute
Layout
({
required
BoxConstraints
constraints
,
bool
dry
=
false
})
{
Size
_
perform
Layout
({
required
BoxConstraints
constraints
,
bool
dry
=
false
})
{
final
ChildLayouter
layoutChild
=
dry
?
ChildLayoutHelper
.
dryLayoutChild
:
ChildLayoutHelper
.
layoutChild
;
if
(
_isSingleButtonRow
(
dialogWidth
))
{
if
(
!
isActionSheet
&&
_isSingleButtonRow
(
dialogWidth
!
))
{
if
(
childCount
==
1
)
{
// We have 1 button. Our size is the width of the dialog and the height
// of the single button.
...
...
@@ -1537,7 +2226,7 @@ class _RenderCupertinoDialogActions extends RenderBox
);
return
constraints
.
constrain
(
Size
(
dialogWidth
,
childSize
.
height
),
Size
(
dialogWidth
!,
childSize
.
height
)
);
}
else
{
// Each button gets half the available width, minus a single divider.
...
...
@@ -1568,7 +2257,7 @@ class _RenderCupertinoDialogActions extends RenderBox
// Calculate our size based on the button sizes.
return
constraints
.
constrain
(
Size
(
dialogWidth
,
dialogWidth
!
,
math
.
max
(
firstChildSize
.
height
,
lastChildSize
.
height
,
...
...
@@ -1609,7 +2298,7 @@ class _RenderCupertinoDialogActions extends RenderBox
// Our height is the accumulated height of all buttons and dividers.
return
constraints
.
constrain
(
Size
(
dialogWidth
,
verticalOffset
),
Size
(
computeMaxIntrinsicWidth
(
0
)
,
verticalOffset
),
);
}
}
...
...
@@ -1618,7 +2307,7 @@ class _RenderCupertinoDialogActions extends RenderBox
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
Canvas
canvas
=
context
.
canvas
;
if
(
_isSingleButtonRow
(
size
.
width
))
{
if
(
!
isActionSheet
&&
_isSingleButtonRow
(
size
.
width
))
{
_drawButtonBackgroundsAndDividersSingleRow
(
canvas
,
offset
);
}
else
{
_drawButtonBackgroundsAndDividersStacked
(
canvas
,
offset
);
...
...
@@ -1632,16 +2321,16 @@ class _RenderCupertinoDialogActions extends RenderBox
// the dialog has 2 buttons). The vertical divider is hidden if either the
// left or right button is pressed.
final
Rect
verticalDivider
=
childCount
==
2
&&
!
_isButtonPressed
?
Rect
.
fromLTWH
(
offset
.
dx
+
firstChild
!.
size
.
width
,
offset
.
dy
,
dividerThickness
,
math
.
max
(
firstChild
!.
size
.
height
,
lastChild
!.
size
.
height
,
),
)
:
Rect
.
zero
;
?
Rect
.
fromLTWH
(
offset
.
dx
+
firstChild
!.
size
.
width
,
offset
.
dy
,
dividerThickness
,
math
.
max
(
firstChild
!.
size
.
height
,
lastChild
!.
size
.
height
,
),
)
:
Rect
.
zero
;
final
List
<
Rect
>
pressedButtonRects
=
_pressedButtons
.
map
<
Rect
>((
RenderBox
pressedButton
)
{
final
MultiChildLayoutParentData
buttonParentData
=
pressedButton
.
parentData
!
as
MultiChildLayoutParentData
;
...
...
@@ -1770,7 +2459,7 @@ class _RenderCupertinoDialogActions extends RenderBox
}
@override
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
required
Offset
position
})
{
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
required
Offset
position
})
{
return
defaultHitTestChildren
(
result
,
position:
position
);
}
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment