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
e2447247
Unverified
Commit
e2447247
authored
Dec 01, 2020
by
Daniel Edrisian
Committed by
GitHub
Dec 01, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added CupertinoFormSection, CupertinoSplitFormRow, and CupertinoTextFormField (#70676)
parent
59cf0d22
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1828 additions
and
0 deletions
+1828
-0
cupertino.dart
packages/flutter/lib/cupertino.dart
+3
-0
form_row.dart
packages/flutter/lib/src/cupertino/form_row.dart
+200
-0
form_section.dart
packages/flutter/lib/src/cupertino/form_section.dart
+282
-0
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+147
-0
text_form_field_row.dart
packages/flutter/lib/src/cupertino/text_form_field_row.dart
+380
-0
form_row_test.dart
packages/flutter/test/cupertino/form_row_test.dart
+175
-0
form_section_test.dart
packages/flutter/test/cupertino/form_section_test.dart
+162
-0
text_form_field_row_test.dart
...ages/flutter/test/cupertino/text_form_field_row_test.dart
+479
-0
No files found.
packages/flutter/lib/cupertino.dart
View file @
e2447247
...
...
@@ -25,6 +25,8 @@ export 'src/cupertino/context_menu.dart';
export
'src/cupertino/context_menu_action.dart'
;
export
'src/cupertino/date_picker.dart'
;
export
'src/cupertino/dialog.dart'
;
export
'src/cupertino/form_row.dart'
;
export
'src/cupertino/form_section.dart'
;
export
'src/cupertino/icon_theme_data.dart'
;
export
'src/cupertino/icons.dart'
;
export
'src/cupertino/interface_level.dart'
;
...
...
@@ -43,6 +45,7 @@ export 'src/cupertino/switch.dart';
export
'src/cupertino/tab_scaffold.dart'
;
export
'src/cupertino/tab_view.dart'
;
export
'src/cupertino/text_field.dart'
;
export
'src/cupertino/text_form_field_row.dart'
;
export
'src/cupertino/text_selection.dart'
;
export
'src/cupertino/text_theme.dart'
;
export
'src/cupertino/theme.dart'
;
...
...
packages/flutter/lib/src/cupertino/form_row.dart
0 → 100644
View file @
e2447247
// 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
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
import
'theme.dart'
;
// Content padding determined via SwiftUI's `Form` view in the iOS 14.2 SDK.
const
EdgeInsetsGeometry
_kDefaultPadding
=
EdgeInsetsDirectional
.
fromSTEB
(
16.0
,
6.0
,
6.0
,
6.0
);
/// An iOS-style form row.
///
/// Creates an iOS-style split form row with a standard prefix and child widget.
/// Also provides a space for error and helper widgets that appear underneath.
///
/// The [child] parameter is required. This widget is displayed at the end of
/// the row.
///
/// The [prefix] parameter is optional and is displayed at the start of the
/// row. Standard iOS guidelines encourage passing a [Text] widget to [prefix]
/// to detail the nature of the row's [child] widget.
///
/// The [padding] parameter is used to pad the contents of the row. It defaults
/// to the standard iOS padding. If no edge insets are intended, explicitly pass
/// [EdgeInsets.zero] to [padding].
///
/// The [helper] and [error] parameters are both optional widgets targeted at
/// displaying more information about the row. Both widgets are placed
/// underneath the [prefix] and [child], and will expand the row's height to
/// accomodate for their presence. When a [Text] is given to [error], it will
/// be shown in [CupertinoColors.destructiveRed] coloring and
/// medium-weighted font.
///
/// {@tool snippet}
///
/// Creates a [CupertinoFormSection] containing a [CupertinoFormRow] with the
/// [prefix], [child], [helper] and [error] widgets.
///
/// ```dart
/// class FlutterDemo extends StatefulWidget {
/// FlutterDemo({Key key}) : super(key: key);
///
/// @override
/// _FlutterDemoState createState() => _FlutterDemoState();
/// }
///
/// class _FlutterDemoState extends State<FlutterDemo> {
/// bool toggleValue = false;
///
/// @override
/// Widget build(BuildContext context) {
/// return CupertinoPageScaffold(
/// child: Center(
/// child: CupertinoFormSection(
/// header: Text('SECTION 1'),
/// children: <Widget>[
/// CupertinoFormRow(
/// child: CupertinoSwitch(
/// value: this.toggleValue,
/// onChanged: (value) {
/// setState(() {
/// this.toggleValue = value;
/// });
/// },
/// ),
/// prefix: Text('Toggle'),
/// helper: Text('Use your instincts'),
/// error: toggleValue ? Text('Cannot be true') : null,
/// ),
/// ],
/// ),
/// ),
/// );
/// }
/// }
/// ```
/// {@end-tool}
class
CupertinoFormRow
extends
StatelessWidget
{
/// Creates an iOS-style split form row with a standard prefix and child widget.
/// Also provides a space for error and helper widgets that appear underneath.
///
/// The [child] parameter is required. This widget is displayed at the end of
/// the row.
///
/// The [prefix] parameter is optional and is displayed at the start of the
/// row. Standard iOS guidelines encourage passing a [Text] widget to [prefix]
/// to detail the nature of the row's [child] widget.
///
/// The [padding] parameter is used to pad the contents of the row. It defaults
/// to the standard iOS padding. If no edge insets are intended, explicitly
/// pass [EdgeInsets.zero] to [padding].
///
/// The [helper] and [error] parameters are both optional widgets targeted at
/// displaying more information about the row. Both widgets are placed
/// underneath the [prefix] and [child], and will expand the row's height to
/// accomodate for their presence. When a [Text] is given to [error], it will
/// be shown in [CupertinoColors.destructiveRed] coloring and
/// medium-weighted font.
const
CupertinoFormRow
({
Key
?
key
,
required
this
.
child
,
this
.
prefix
,
this
.
padding
,
this
.
helper
,
this
.
error
,
})
:
super
(
key:
key
);
/// A widget that is displayed at the start of the row.
///
/// The [prefix] parameter is displayed at the start of the row. Standard iOS
/// guidelines encourage passing a [Text] widget to [prefix] to detail the
/// nature of the row's [child] widget. If null, the [child] widget will take
/// up all horizontal space in the row.
final
Widget
?
prefix
;
/// Content padding for the row.
///
/// Defaults to the standard iOS padding for form rows. If no edge insets are
/// intended, explicitly pass [EdgeInsets.zero] to [padding].
final
EdgeInsetsGeometry
?
padding
;
/// A widget that is displayed underneath the [prefix] and [child] widgets.
///
/// The [helper] appears in primary label coloring, and is meant to inform the
/// user about interaction with the child widget. The row becomes taller in
/// order to display the [helper] widget underneath [prefix] and [child]. If
/// null, the row is shorter.
final
Widget
?
helper
;
/// A widget that is displayed underneath the [prefix] and [child] widgets.
///
/// The [error] widget is primarily used to inform users of input errors. When
/// a [Text] is given to [error], it will be shown in
/// [CupertinoColors.destructiveRed] coloring and medium-weighted font. The
/// row becomes taller in order to display the [helper] widget underneath
/// [prefix] and [child]. If null, the row is shorter.
final
Widget
?
error
;
/// Child widget.
///
/// The [child] widget is primarily used for input. It end-aligned and
/// horizontally flexible, taking up the entire space trailing past the
/// [prefix] widget.
final
Widget
child
;
@override
Widget
build
(
BuildContext
context
)
{
final
CupertinoThemeData
themeData
=
CupertinoTheme
.
of
(
context
);
final
TextStyle
textStyle
=
themeData
.
textTheme
.
textStyle
;
final
List
<
Widget
>
rowChildren
=
<
Widget
>[
if
(
prefix
!=
null
)
DefaultTextStyle
(
style:
textStyle
,
child:
prefix
!,
),
Flexible
(
child:
Align
(
alignment:
AlignmentDirectional
.
centerEnd
,
child:
child
,
),
),
];
return
Padding
(
padding:
padding
??
_kDefaultPadding
,
child:
Column
(
children:
<
Widget
>[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
rowChildren
,
),
if
(
helper
!=
null
)
Align
(
alignment:
AlignmentDirectional
.
centerStart
,
child:
DefaultTextStyle
(
style:
textStyle
,
child:
helper
!,
),
),
if
(
error
!=
null
)
Align
(
alignment:
AlignmentDirectional
.
centerStart
,
child:
DefaultTextStyle
(
style:
const
TextStyle
(
color:
CupertinoColors
.
destructiveRed
,
fontWeight:
FontWeight
.
w500
,
),
child:
error
!,
),
),
],
),
);
}
}
packages/flutter/lib/src/cupertino/form_section.dart
0 → 100644
View file @
e2447247
// 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
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
// Standard header margin, determined from SwiftUI's Forms in iOS 14.2 SDK.
const
EdgeInsetsDirectional
_kDefaultHeaderMargin
=
EdgeInsetsDirectional
.
fromSTEB
(
16.5
,
16.0
,
16.5
,
10.0
);
// Used for iOS "Inset Grouped" margin, determined from SwiftUI's Forms in
// iOS 14.2 SDK.
const
EdgeInsetsDirectional
_kDefaultInsetGroupedRowsMargin
=
EdgeInsetsDirectional
.
fromSTEB
(
16.5
,
0.0
,
16.5
,
16.5
);
// Used for iOS "Inset Grouped" border radius, estimated from SwiftUI's Forms in
// iOS 14.2 SDK.
// TODO(edrisian): This should be a rounded rectangle once that shape is added.
const
BorderRadius
_kDefaultInsetGroupedBorderRadius
=
BorderRadius
.
all
(
Radius
.
circular
(
10.0
));
// Used to differentiate the edge-to-edge section with the centered section.
enum
_CupertinoFormSectionType
{
base
,
insetGrouped
}
/// An iOS-style form section.
///
/// The base constructor for [CupertinoFormSection] constructs an
/// edge-to-edge style section which includes an iOS-style header, rows,
/// the dividers between rows, and borders on top and bottom of the rows.
///
/// The [CupertinoFormSection.insetGrouped] constructor creates a round-edged and
/// padded section that is commonly seen in notched-displays like iPhone X and
/// beyond. Creates an iOS-style header, rows, and the dividers
/// between rows. Does not create borders on top and bottom of the rows.
///
/// The [header] parameter sets the form section header. The section header lies
/// above the [children] rows, with margins that match the iOS style.
///
/// The [children] parameter is required and sets the list of rows shown in
/// the section. The [children] parameter takes a list, as opposed to a more
/// efficient builder function that lazy builds, because forms are intended to
/// be short in row count. It is recommended that only [CupertinoFormRow] and
/// [CupertinoTextFormFieldRow] widgets be included in the [children] list in
/// order to retain the iOS look.
///
/// The [margin] parameter sets the spacing around the content area of the
/// section encapsulating [children].
///
/// The [decoration] parameter sets the decoration around [children].
/// If null, defaults to [CupertinoColors.secondarySystemGroupedBackground].
/// If null, defaults to 10.0 circular radius when constructing with
/// [CupertinoFormSection.insetGrouped]. Defaults to zero radius for the
/// standard [CupertinoFormSection] constructor.
///
/// The [backgroundColor] parameter sets the background color behind the section.
/// If null, defaults to [CupertinoColors.systemGroupedBackground].
///
/// {@macro flutter.material.Material.clipBehavior}
class
CupertinoFormSection
extends
StatelessWidget
{
/// Creates a section that mimicks standard iOS forms.
///
/// The base constructor for [CupertinoFormSection] constructs an
/// edge-to-edge style section which includes an iOS-style header,
/// rows, the dividers between rows, and borders on top and bottom of the rows.
///
/// The [header] parameter sets the form section header. The section header
/// lies above the [children] rows, with margins that match the iOS style.
///
/// The [children] parameter is required and sets the list of rows shown in
/// the section. The [children] parameter takes a list, as opposed to a more
/// efficient builder function that lazy builds, because forms are intended to
/// be short in row count. It is recommended that only [CupertinoFormRow] and
/// [CupertinoTextFormFieldRow] widgets be included in the [children] list in
/// order to retain the iOS look.
///
/// The [margin] parameter sets the spacing around the content area of the
/// section encapsulating [children], and defaults to zero padding.
///
/// The [decoration] parameter sets the decoration around [children].
/// If null, defaults to [CupertinoColors.secondarySystemGroupedBackground].
/// If null, defaults to 10.0 circular radius when constructing with
/// [CupertinoFormSection.insetGrouped]. Defaults to zero radius for the
/// standard [CupertinoFormSection] constructor.
///
/// The [backgroundColor] parameter sets the background color behind the
/// section. If null, defaults to [CupertinoColors.systemGroupedBackground].
///
/// {@macro flutter.material.Material.clipBehavior}
const
CupertinoFormSection
({
Key
?
key
,
required
this
.
children
,
this
.
header
,
this
.
margin
=
EdgeInsets
.
zero
,
this
.
backgroundColor
=
CupertinoColors
.
systemGroupedBackground
,
this
.
decoration
,
this
.
clipBehavior
=
Clip
.
none
,
})
:
_type
=
_CupertinoFormSectionType
.
base
,
assert
(
children
.
length
>
0
),
super
(
key:
key
);
/// Creates a section that mimicks standard "Inset Grouped" iOS forms.
///
/// The [CupertinoFormSection.insetGrouped] constructor creates a round-edged and
/// padded section that is commonly seen in notched-displays like iPhone X and
/// beyond. Creates an iOS-style header, rows, and the dividers
/// between rows. Does not create borders on top and bottom of the rows.
///
/// The [header] parameter sets the form section header. The section header
/// lies above the [children] rows, with margins that match the iOS style.
///
/// The [children] parameter is required and sets the list of rows shown in
/// the section. The [children] parameter takes a list, as opposed to a more
/// efficient builder function that lazy builds, because forms are intended to
/// be short in row count. It is recommended that only [CupertinoFormRow] and
/// [CupertinoTextFormFieldRow] widgets be included in the [children] list in
/// order to retain the iOS look.
///
/// The [margin] parameter sets the spacing around the content area of the
/// section encapsulating [children], and defaults to the standard
/// notched-style iOS form padding.
///
/// The [decoration] parameter sets the decoration around [children].
/// If null, defaults to [CupertinoColors.secondarySystemGroupedBackground].
/// If null, defaults to 10.0 circular radius when constructing with
/// [CupertinoFormSection.insetGrouped]. Defaults to zero radius for the
/// standard [CupertinoFormSection] constructor.
///
/// The [backgroundColor] parameter sets the background color behind the
/// section. If null, defaults to [CupertinoColors.systemGroupedBackground].
///
/// {@macro flutter.material.Material.clipBehavior}
const
CupertinoFormSection
.
insetGrouped
({
Key
?
key
,
required
this
.
children
,
this
.
header
,
this
.
margin
=
_kDefaultInsetGroupedRowsMargin
,
this
.
backgroundColor
=
CupertinoColors
.
systemGroupedBackground
,
this
.
decoration
,
this
.
clipBehavior
=
Clip
.
none
,
})
:
_type
=
_CupertinoFormSectionType
.
insetGrouped
,
assert
(
children
.
length
>
0
),
super
(
key:
key
);
final
_CupertinoFormSectionType
_type
;
/// Sets the form section header. The section header lies above the
/// [children] rows.
final
Widget
?
header
;
/// Margin around the content area of the section encapsulating [children].
///
/// Defaults to zero padding if constructed with standard
/// [CupertinoFormSection] constructor. Defaults to the standard notched-style
/// iOS margin when constructing with [CupertinoFormSection.insetGrouped].
final
EdgeInsetsGeometry
margin
;
/// The list of rows in the section.
///
/// This takes a list, as opposed to a more efficient builder function that
/// lazy builds, because forms are intended to be short in row count. It is
/// recommended that only [CupertinoFormRow] and [CupertinoTextFormFieldRow]
/// widgets be included in the [children] list in order to retain the iOS look.
final
List
<
Widget
>
children
;
/// Sets the decoration around [children].
///
/// If null, background color defaults to
/// [CupertinoColors.secondarySystemGroupedBackground].
///
/// If null, border radius defaults to 10.0 circular radius when constructing
/// with [CupertinoFormSection.insetGrouped]. Defaults to zero radius for the
/// standard [CupertinoFormSection] constructor.
final
BoxDecoration
?
decoration
;
/// Sets the background color behind the section.
///
/// Defaults to [CupertinoColors.systemGroupedBackground].
final
Color
backgroundColor
;
/// {@macro flutter.material.Material.clipBehavior}
///
/// Defaults to [Clip.none], and must not be null.
final
Clip
clipBehavior
;
@override
Widget
build
(
BuildContext
context
)
{
final
Color
dividerColor
=
CupertinoColors
.
separator
.
resolveFrom
(
context
);
final
double
dividerHeight
=
1.0
/
MediaQuery
.
of
(
context
).
devicePixelRatio
;
// Long divider is used for wrapping the top and bottom of rows.
// Only used in _CupertinoFormSectionType.base mode
final
Widget
longDivider
=
Container
(
color:
dividerColor
,
height:
dividerHeight
,
);
// Short divider is used between rows.
// The value of the starting inset (15.0) is determined using SwiftUI's Form
// seperators in the iOS 14.2 SDK.
final
Widget
shortDivider
=
Container
(
margin:
const
EdgeInsetsDirectional
.
only
(
start:
15.0
),
color:
dividerColor
,
height:
dividerHeight
,
);
// We construct childrenWithDividers as follows:
// Insert a short divider between all rows.
// If it is a `_CupertinoFormSectionType.base` type, add a long divider
// to the top and bottom of the rows.
assert
(
children
.
isNotEmpty
);
final
List
<
Widget
>
childrenWithDividers
=
<
Widget
>[];
if
(
_type
==
_CupertinoFormSectionType
.
base
)
{
childrenWithDividers
.
add
(
longDivider
);
}
children
.
sublist
(
0
,
children
.
length
-
1
).
forEach
((
Widget
widget
)
{
childrenWithDividers
.
add
(
widget
);
childrenWithDividers
.
add
(
shortDivider
);
});
childrenWithDividers
.
add
(
children
.
last
);
if
(
_type
==
_CupertinoFormSectionType
.
base
)
{
childrenWithDividers
.
add
(
longDivider
);
}
// Refactored the decorate children group in one place to avoid repeating it
// twice down bellow in the returned widget.
final
DecoratedBox
decoratedChildrenGroup
=
DecoratedBox
(
decoration:
decoration
??
BoxDecoration
(
color:
CupertinoDynamicColor
.
resolve
(
decoration
?.
color
??
CupertinoColors
.
secondarySystemGroupedBackground
,
context
),
borderRadius:
_kDefaultInsetGroupedBorderRadius
,
),
child:
Column
(
children:
childrenWithDividers
,
),
);
return
DecoratedBox
(
decoration:
BoxDecoration
(
color:
CupertinoDynamicColor
.
resolve
(
backgroundColor
,
context
),
),
child:
Column
(
children:
<
Widget
>[
Align
(
alignment:
AlignmentDirectional
.
centerStart
,
child:
header
==
null
?
null
:
DefaultTextStyle
(
style:
TextStyle
(
fontSize:
13.5
,
color:
CupertinoColors
.
secondaryLabel
.
resolveFrom
(
context
),
),
child:
Padding
(
padding:
_kDefaultHeaderMargin
,
child:
header
!,
),
),
),
Padding
(
padding:
margin
,
child:
clipBehavior
==
Clip
.
none
?
decoratedChildrenGroup
:
ClipRRect
(
borderRadius:
_kDefaultInsetGroupedBorderRadius
,
clipBehavior:
clipBehavior
,
child:
decoratedChildrenGroup
),
),
],
),
);
}
}
packages/flutter/lib/src/cupertino/text_field.dart
View file @
e2447247
...
...
@@ -16,6 +16,11 @@ import 'theme.dart';
export
'package:flutter/services.dart'
show
TextInputType
,
TextInputAction
,
TextCapitalization
,
SmartQuotesType
,
SmartDashesType
;
const
TextStyle
_kDefaultPlaceholderStyle
=
TextStyle
(
fontWeight:
FontWeight
.
w400
,
color:
CupertinoColors
.
placeholderText
,
);
// Value inspected from Xcode 11 & iOS 13.0 Simulator.
const
BorderSide
_kDefaultRoundedBorderSide
=
BorderSide
(
color:
CupertinoDynamicColor
.
withBrightness
(
...
...
@@ -326,6 +331,148 @@ class CupertinoTextField extends StatefulWidget {
)),
super
(
key:
key
);
/// Creates a borderless iOS-style text field.
///
/// To provide a prefilled text entry, pass in a [TextEditingController] with
/// an initial value to the [controller] parameter.
///
/// To provide a hint placeholder text that appears when the text entry is
/// empty, pass a [String] to the [placeholder] parameter.
///
/// The [maxLines] property can be set to null to remove the restriction on
/// the number of lines. In this mode, the intrinsic height of the widget will
/// grow as the number of lines of text grows. By default, it is `1`, meaning
/// this is a single-line text field and will scroll horizontally when
/// overflown. [maxLines] must not be zero.
///
/// The text cursor is not shown if [showCursor] is false or if [showCursor]
/// is null (the default) and [readOnly] is true.
///
/// If specified, the [maxLength] property must be greater than zero.
///
/// The [selectionHeightStyle] and [selectionWidthStyle] properties allow
/// changing the shape of the selection highlighting. These properties default
/// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
/// must not be null.
///
/// The [autocorrect], [autofocus], [clearButtonMode], [dragStartBehavior],
/// [expands], [maxLengthEnforced], [obscureText], [prefixMode], [readOnly],
/// [scrollPadding], [suffixMode], [textAlign], [selectionHeightStyle],
/// [selectionWidthStyle], and [enableSuggestions] properties must not be null.
///
/// See also:
///
/// * [minLines], which is the minimum number of lines to occupy when the
/// content spans fewer lines.
/// * [expands], to allow the widget to size itself to its parent's height.
/// * [maxLength], which discusses the precise meaning of "number of
/// characters" and how it may differ from the intuitive meaning.
const
CupertinoTextField
.
borderless
({
Key
?
key
,
this
.
controller
,
this
.
focusNode
,
this
.
decoration
,
this
.
padding
=
const
EdgeInsets
.
all
(
6.0
),
this
.
placeholder
,
this
.
placeholderStyle
=
_kDefaultPlaceholderStyle
,
this
.
prefix
,
this
.
prefixMode
=
OverlayVisibilityMode
.
always
,
this
.
suffix
,
this
.
suffixMode
=
OverlayVisibilityMode
.
always
,
this
.
clearButtonMode
=
OverlayVisibilityMode
.
never
,
TextInputType
?
keyboardType
,
this
.
textInputAction
,
this
.
textCapitalization
=
TextCapitalization
.
none
,
this
.
style
,
this
.
strutStyle
,
this
.
textAlign
=
TextAlign
.
start
,
this
.
textAlignVertical
,
this
.
readOnly
=
false
,
ToolbarOptions
?
toolbarOptions
,
this
.
showCursor
,
this
.
autofocus
=
false
,
this
.
obscuringCharacter
=
'•'
,
this
.
obscureText
=
false
,
this
.
autocorrect
=
true
,
SmartDashesType
?
smartDashesType
,
SmartQuotesType
?
smartQuotesType
,
this
.
enableSuggestions
=
true
,
this
.
maxLines
=
1
,
this
.
minLines
,
this
.
expands
=
false
,
this
.
maxLength
,
this
.
maxLengthEnforced
=
true
,
this
.
onChanged
,
this
.
onEditingComplete
,
this
.
onSubmitted
,
this
.
inputFormatters
,
this
.
enabled
,
this
.
cursorWidth
=
2.0
,
this
.
cursorHeight
,
this
.
cursorRadius
=
const
Radius
.
circular
(
2.0
),
this
.
cursorColor
,
this
.
selectionHeightStyle
=
ui
.
BoxHeightStyle
.
tight
,
this
.
selectionWidthStyle
=
ui
.
BoxWidthStyle
.
tight
,
this
.
keyboardAppearance
,
this
.
scrollPadding
=
const
EdgeInsets
.
all
(
20.0
),
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
enableInteractiveSelection
=
true
,
this
.
selectionControls
,
this
.
onTap
,
this
.
scrollController
,
this
.
scrollPhysics
,
this
.
autofillHints
,
this
.
restorationId
,
})
:
assert
(
textAlign
!=
null
),
assert
(
readOnly
!=
null
),
assert
(
autofocus
!=
null
),
// TODO(a14n): uncomment when issue is fixed, https://github.com/dart-lang/sdk/issues/43407
assert
(
obscuringCharacter
!=
null
/* && obscuringCharacter.length == 1*/
),
assert
(
obscureText
!=
null
),
assert
(
autocorrect
!=
null
),
smartDashesType
=
smartDashesType
??
(
obscureText
?
SmartDashesType
.
disabled
:
SmartDashesType
.
enabled
),
smartQuotesType
=
smartQuotesType
??
(
obscureText
?
SmartQuotesType
.
disabled
:
SmartQuotesType
.
enabled
),
assert
(
enableSuggestions
!=
null
),
assert
(
maxLengthEnforced
!=
null
),
assert
(
scrollPadding
!=
null
),
assert
(
dragStartBehavior
!=
null
),
assert
(
selectionHeightStyle
!=
null
),
assert
(
selectionWidthStyle
!=
null
),
assert
(
maxLines
==
null
||
maxLines
>
0
),
assert
(
minLines
==
null
||
minLines
>
0
),
assert
(
(
maxLines
==
null
)
||
(
minLines
==
null
)
||
(
maxLines
>=
minLines
),
"minLines can't be greater than maxLines"
,
),
assert
(
expands
!=
null
),
assert
(
!
expands
||
(
maxLines
==
null
&&
minLines
==
null
),
'minLines and maxLines must be null when expands is true.'
,
),
assert
(!
obscureText
||
maxLines
==
1
,
'Obscured fields cannot be multiline.'
),
assert
(
maxLength
==
null
||
maxLength
>
0
),
assert
(
clearButtonMode
!=
null
),
assert
(
prefixMode
!=
null
),
assert
(
suffixMode
!=
null
),
// Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set.
assert
(!
identical
(
textInputAction
,
TextInputAction
.
newline
)
||
maxLines
==
1
||
!
identical
(
keyboardType
,
TextInputType
.
text
),
'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.'
),
keyboardType
=
keyboardType
??
(
maxLines
==
1
?
TextInputType
.
text
:
TextInputType
.
multiline
),
toolbarOptions
=
toolbarOptions
??
(
obscureText
?
const
ToolbarOptions
(
selectAll:
true
,
paste:
true
,
)
:
const
ToolbarOptions
(
copy:
true
,
cut:
true
,
selectAll:
true
,
paste:
true
,
)),
super
(
key:
key
);
/// Controls the text being edited.
///
/// If null, this widget will create its own [TextEditingController].
...
...
packages/flutter/lib/src/cupertino/text_form_field_row.dart
0 → 100644
View file @
e2447247
// 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
'package:flutter/rendering.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'colors.dart'
;
import
'form_row.dart'
;
import
'text_field.dart'
;
import
'theme.dart'
;
/// Creates a [CupertinoFormRow] containing a [FormField] that wraps
/// a [CupertinoTextField].
///
/// A [Form] ancestor is not required. The [Form] simply makes it easier to
/// save, reset, or validate multiple fields at once. To use without a [Form],
/// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
/// save or reset the form field.
///
/// When a [controller] is specified, its [TextEditingController.text]
/// defines the [initialValue]. If this [FormField] is part of a scrolling
/// container that lazily constructs its children, like a [ListView] or a
/// [CustomScrollView], then a [controller] should be specified.
/// The controller's lifetime should be managed by a stateful widget ancestor
/// of the scrolling container.
///
/// The [prefix] parameter is displayed at the start of the row. Standard iOS
/// guidelines encourage passing a [Text] widget to [prefix] to detail the
/// nature of the input.
///
/// The [padding] parameter is used to pad the contents of the row. It is
/// directly passed to [CupertinoFormRow]. If the [padding]
/// parameter is null, [CupertinoFormRow] constructs its own default
/// padding (which is the standard form row padding in iOS.) If no edge
/// insets are intended, explicitly pass [EdgeInsets.zero] to [padding].
///
/// If a [controller] is not specified, [initialValue] can be used to give
/// the automatically generated controller an initial value.
///
/// Consider calling [TextEditingController.dispose] of the [controller], if one
/// is specified, when it is no longer needed. This will ensure we discard any
/// resources used by the object.
///
/// For documentation about the various parameters, see the
/// [CupertinoTextField] class and [new CupertinoTextField.borderless],
/// the constructor.
///
/// {@tool snippet}
///
/// Creates a [CupertinoTextFormFieldRow] with a leading text and validator
/// function.
///
/// If the user enters valid text, the CupertinoTextField appears normally
/// without any warnings to the user.
///
/// If the user enters invalid text, the error message returned from the
/// validator function is displayed in dark red underneath the input.
///
/// ```dart
/// CupertinoTextFormFieldRow(
/// prefix: Text('Username'),
/// onSaved: (String value) {
/// // This optional block of code can be used to run
/// // code when the user saves the form.
/// },
/// validator: (String value) {
/// return value.contains('@') ? 'Do not use the @ char.' : null;
/// },
/// )
/// ```
/// {@end-tool}
///
/// {@tool dartpad --template=stateful_widget_material}
/// This example shows how to move the focus to the next field when the user
/// presses the SPACE key.
///
/// ```dart imports
/// import 'package:flutter/cupertino.dart';
/// ```
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CupertinoPageScaffold(
/// child: Center(
/// child: Form(
/// autovalidateMode: AutovalidateMode.always,
/// onChanged: () {
/// Form.of(primaryFocus.context).save();
/// },
/// child: CupertinoFormSection.insetGrouped(
/// header: Text('SECTION 1'),
/// children: List<Widget>.generate(5, (int index) {
/// return CupertinoTextFormFieldRow(
/// prefix: Text('Enter text'),
/// placeholder: 'Enter text',
/// validator: (value) {
/// if (value.isEmpty) {
/// return 'Please enter a value';
/// }
/// return null;
/// },
/// );
/// }),
/// ),
/// ),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
class
CupertinoTextFormFieldRow
extends
FormField
<
String
>
{
/// Creates a [CupertinoFormRow] containing a [FormField] that wraps
/// a [CupertinoTextField].
///
/// When a [controller] is specified, [initialValue] must be null (the
/// default). If [controller] is null, then a [TextEditingController]
/// will be constructed automatically and its `text` will be initialized
/// to [initialValue] or the empty string.
///
/// The [prefix] parameter is displayed at the start of the row. Standard iOS
/// guidelines encourage passing a [Text] widget to [prefix] to detail the
/// nature of the input.
///
/// The [padding] parameter is used to pad the contents of the row. It is
/// directly passed to [CupertinoFormRow]. If the [padding]
/// parameter is null, [CupertinoFormRow] constructs its own default
/// padding (which is the standard form row padding in iOS.) If no edge
/// insets are intended, explicitly pass [EdgeInsets.zero] to [padding].
///
/// For documentation about the various parameters, see the
/// [CupertinoTextField] class and [new CupertinoTextField.borderless],
/// the constructor.
CupertinoTextFormFieldRow
({
Key
?
key
,
this
.
prefix
,
this
.
padding
,
this
.
controller
,
String
?
initialValue
,
FocusNode
?
focusNode
,
BoxDecoration
?
decoration
,
TextInputType
?
keyboardType
,
TextCapitalization
textCapitalization
=
TextCapitalization
.
none
,
TextInputAction
?
textInputAction
,
TextStyle
?
style
,
StrutStyle
?
strutStyle
,
TextAlign
textAlign
=
TextAlign
.
start
,
TextAlignVertical
?
textAlignVertical
,
bool
autofocus
=
false
,
bool
readOnly
=
false
,
ToolbarOptions
?
toolbarOptions
,
bool
?
showCursor
,
String
obscuringCharacter
=
'•'
,
bool
obscureText
=
false
,
bool
autocorrect
=
true
,
SmartDashesType
?
smartDashesType
,
SmartQuotesType
?
smartQuotesType
,
bool
enableSuggestions
=
true
,
int
?
maxLines
=
1
,
int
?
minLines
,
bool
expands
=
false
,
int
?
maxLength
,
ValueChanged
<
String
>?
onChanged
,
GestureTapCallback
?
onTap
,
VoidCallback
?
onEditingComplete
,
ValueChanged
<
String
>?
onFieldSubmitted
,
FormFieldSetter
<
String
>?
onSaved
,
FormFieldValidator
<
String
>?
validator
,
List
<
TextInputFormatter
>?
inputFormatters
,
bool
?
enabled
,
double
cursorWidth
=
2.0
,
double
?
cursorHeight
,
Color
?
cursorColor
,
Brightness
?
keyboardAppearance
,
EdgeInsets
scrollPadding
=
const
EdgeInsets
.
all
(
20.0
),
bool
enableInteractiveSelection
=
true
,
TextSelectionControls
?
selectionControls
,
ScrollPhysics
?
scrollPhysics
,
Iterable
<
String
>?
autofillHints
,
AutovalidateMode
autovalidateMode
=
AutovalidateMode
.
disabled
,
String
?
placeholder
,
TextStyle
?
placeholderStyle
=
const
TextStyle
(
fontWeight:
FontWeight
.
w400
,
color:
CupertinoColors
.
placeholderText
,
),
})
:
assert
(
initialValue
==
null
||
controller
==
null
),
assert
(
textAlign
!=
null
),
assert
(
autofocus
!=
null
),
assert
(
readOnly
!=
null
),
assert
(
obscuringCharacter
!=
null
&&
obscuringCharacter
.
length
==
1
),
assert
(
obscureText
!=
null
),
assert
(
autocorrect
!=
null
),
assert
(
enableSuggestions
!=
null
),
assert
(
scrollPadding
!=
null
),
assert
(
maxLines
==
null
||
maxLines
>
0
),
assert
(
minLines
==
null
||
minLines
>
0
),
assert
(
(
maxLines
==
null
)
||
(
minLines
==
null
)
||
(
maxLines
>=
minLines
),
"minLines can't be greater than maxLines"
,
),
assert
(
expands
!=
null
),
assert
(
!
expands
||
(
maxLines
==
null
&&
minLines
==
null
),
'minLines and maxLines must be null when expands is true.'
,
),
assert
(!
obscureText
||
maxLines
==
1
,
'Obscured fields cannot be multiline.'
),
assert
(
maxLength
==
null
||
maxLength
>
0
),
assert
(
enableInteractiveSelection
!=
null
),
super
(
key:
key
,
initialValue:
controller
?.
text
??
initialValue
??
''
,
onSaved:
onSaved
,
validator:
validator
,
autovalidateMode:
autovalidateMode
,
builder:
(
FormFieldState
<
String
>
field
)
{
final
_CupertinoTextFormFieldRowState
state
=
field
as
_CupertinoTextFormFieldRowState
;
void
onChangedHandler
(
String
value
)
{
field
.
didChange
(
value
);
if
(
onChanged
!=
null
)
{
onChanged
(
value
);
}
}
return
CupertinoFormRow
(
prefix:
prefix
,
padding:
padding
,
error:
(
field
.
errorText
==
null
)
?
null
:
Text
(
field
.
errorText
!),
child:
CupertinoTextField
.
borderless
(
controller:
state
.
_effectiveController
,
focusNode:
focusNode
,
keyboardType:
keyboardType
,
decoration:
decoration
,
textInputAction:
textInputAction
,
style:
style
,
strutStyle:
strutStyle
,
textAlign:
textAlign
,
textAlignVertical:
textAlignVertical
,
textCapitalization:
textCapitalization
,
autofocus:
autofocus
,
toolbarOptions:
toolbarOptions
,
readOnly:
readOnly
,
showCursor:
showCursor
,
obscuringCharacter:
obscuringCharacter
,
obscureText:
obscureText
,
autocorrect:
autocorrect
,
smartDashesType:
smartDashesType
,
smartQuotesType:
smartQuotesType
,
enableSuggestions:
enableSuggestions
,
maxLines:
maxLines
,
minLines:
minLines
,
expands:
expands
,
maxLength:
maxLength
,
onChanged:
onChangedHandler
,
onTap:
onTap
,
onEditingComplete:
onEditingComplete
,
onSubmitted:
onFieldSubmitted
,
inputFormatters:
inputFormatters
,
enabled:
enabled
,
cursorWidth:
cursorWidth
,
cursorHeight:
cursorHeight
,
cursorColor:
cursorColor
,
scrollPadding:
scrollPadding
,
scrollPhysics:
scrollPhysics
,
keyboardAppearance:
keyboardAppearance
,
enableInteractiveSelection:
enableInteractiveSelection
,
selectionControls:
selectionControls
,
autofillHints:
autofillHints
,
placeholder:
placeholder
,
placeholderStyle:
placeholderStyle
,
),
);
},
);
/// A widget that is displayed at the start of the row.
///
/// The [prefix] widget is displayed at the start of the row. Standard iOS
/// guidelines encourage passing a [Text] widget to [prefix] to detail the
/// nature of the input.
final
Widget
?
prefix
;
/// Content padding for the row.
///
/// The [padding] widget is passed to [CupertinoFormRow]. If the [padding]
/// parameter is null, [CupertinoFormRow] constructs its own default
/// padding, which is the standard form row padding in iOS.
///
/// If no edge insets are intended, explicitly pass [EdgeInsets.zero] to
/// [padding].
final
EdgeInsetsGeometry
?
padding
;
/// Controls the text being edited.
///
/// If null, this widget will create its own [TextEditingController] and
/// initialize its [TextEditingController.text] with [initialValue].
final
TextEditingController
?
controller
;
@override
_CupertinoTextFormFieldRowState
createState
()
=>
_CupertinoTextFormFieldRowState
();
}
class
_CupertinoTextFormFieldRowState
extends
FormFieldState
<
String
>
{
TextEditingController
?
_controller
;
TextEditingController
?
get
_effectiveController
=>
widget
.
controller
??
_controller
;
@override
CupertinoTextFormFieldRow
get
widget
=>
super
.
widget
as
CupertinoTextFormFieldRow
;
@override
void
initState
()
{
super
.
initState
();
if
(
widget
.
controller
==
null
)
{
_controller
=
TextEditingController
(
text:
widget
.
initialValue
);
}
else
{
widget
.
controller
!.
addListener
(
_handleControllerChanged
);
}
}
@override
void
didUpdateWidget
(
CupertinoTextFormFieldRow
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
widget
.
controller
!=
oldWidget
.
controller
)
{
oldWidget
.
controller
?.
removeListener
(
_handleControllerChanged
);
widget
.
controller
?.
addListener
(
_handleControllerChanged
);
if
(
oldWidget
.
controller
!=
null
&&
widget
.
controller
==
null
)
_controller
=
TextEditingController
.
fromValue
(
oldWidget
.
controller
!.
value
);
if
(
widget
.
controller
!=
null
)
{
setValue
(
widget
.
controller
!.
text
);
if
(
oldWidget
.
controller
==
null
)
{
_controller
=
null
;
}
}
}
}
@override
void
dispose
()
{
widget
.
controller
?.
removeListener
(
_handleControllerChanged
);
super
.
dispose
();
}
@override
void
didChange
(
String
?
value
)
{
super
.
didChange
(
value
);
if
(
_effectiveController
!.
text
!=
value
)
{
_effectiveController
!.
text
=
value
;
}
}
@override
void
reset
()
{
super
.
reset
();
setState
(()
{
_effectiveController
!.
text
=
widget
.
initialValue
;
});
}
void
_handleControllerChanged
()
{
// Suppress changes that originated from within this class.
//
// In the case where a controller has been passed in to this widget, we
// register this change listener. In these cases, we'll also receive change
// notifications for changes originating from within this class -- for
// example, the reset() method. In such cases, the FormField value will
// already have been set.
if
(
_effectiveController
!.
text
!=
value
)
didChange
(
_effectiveController
!.
text
);
}
}
packages/flutter/test/cupertino/form_row_test.dart
0 → 100644
View file @
e2447247
// 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
'package:flutter/cupertino.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
void
main
(
)
{
testWidgets
(
'Shows prefix'
,
(
WidgetTester
tester
)
async
{
const
Widget
prefix
=
Text
(
'Enter Value'
);
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoFormRow
(
prefix:
prefix
,
child:
CupertinoTextField
(),
),
),
),
);
expect
(
prefix
,
tester
.
widget
(
find
.
byType
(
Text
)));
});
testWidgets
(
'Shows child'
,
(
WidgetTester
tester
)
async
{
const
Widget
child
=
CupertinoTextField
();
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoFormRow
(
child:
child
,
),
),
),
);
expect
(
child
,
tester
.
widget
(
find
.
byType
(
CupertinoTextField
)));
});
testWidgets
(
'RTL puts prefix after child'
,
(
WidgetTester
tester
)
async
{
const
Widget
prefix
=
Text
(
'Enter Value'
);
const
Widget
child
=
CupertinoTextField
();
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
Directionality
(
textDirection:
TextDirection
.
rtl
,
child:
CupertinoFormRow
(
prefix:
prefix
,
child:
child
,
),
),
),
),
);
expect
(
tester
.
getTopLeft
(
find
.
byType
(
Text
)).
dx
>
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
)).
dx
,
true
);
});
testWidgets
(
'LTR puts child after prefix'
,
(
WidgetTester
tester
)
async
{
const
Widget
prefix
=
Text
(
'Enter Value'
);
const
Widget
child
=
CupertinoTextField
();
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CupertinoFormRow
(
prefix:
prefix
,
child:
child
,
),
),
),
),
);
expect
(
tester
.
getTopLeft
(
find
.
byType
(
Text
)).
dx
>
tester
.
getTopLeft
(
find
.
byType
(
CupertinoTextField
)).
dx
,
false
);
});
testWidgets
(
'Shows error widget'
,
(
WidgetTester
tester
)
async
{
const
Widget
error
=
Text
(
'Error'
);
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoFormRow
(
child:
CupertinoTextField
(),
error:
error
,
),
),
),
);
expect
(
error
,
tester
.
widget
(
find
.
byType
(
Text
)));
});
testWidgets
(
'Shows helper widget'
,
(
WidgetTester
tester
)
async
{
const
Widget
helper
=
Text
(
'Helper'
);
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoFormRow
(
child:
CupertinoTextField
(),
helper:
helper
,
),
),
),
);
expect
(
helper
,
tester
.
widget
(
find
.
byType
(
Text
)));
});
testWidgets
(
'Shows helper text above error text'
,
(
WidgetTester
tester
)
async
{
const
Widget
helper
=
Text
(
'Helper'
);
const
Widget
error
=
CupertinoActivityIndicator
();
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoFormRow
(
child:
CupertinoTextField
(),
helper:
helper
,
error:
error
,
),
),
),
);
expect
(
tester
.
getTopLeft
(
find
.
byType
(
CupertinoActivityIndicator
)).
dy
>
tester
.
getTopLeft
(
find
.
byType
(
Text
)).
dy
,
true
);
});
testWidgets
(
'Shows helper in label color and error text in red color'
,
(
WidgetTester
tester
)
async
{
const
Widget
helper
=
Text
(
'Helper'
);
const
Widget
error
=
Text
(
'Error'
);
await
tester
.
pumpWidget
(
const
CupertinoApp
(
home:
Center
(
child:
CupertinoFormRow
(
child:
CupertinoTextField
(),
helper:
helper
,
error:
error
,
),
),
),
);
final
DefaultTextStyle
helperTextStyle
=
tester
.
widget
(
find
.
byType
(
DefaultTextStyle
).
first
);
expect
(
helperTextStyle
.
style
.
color
,
CupertinoColors
.
label
);
final
DefaultTextStyle
errorTextStyle
=
tester
.
widget
(
find
.
byType
(
DefaultTextStyle
).
last
);
expect
(
errorTextStyle
.
style
.
color
,
CupertinoColors
.
destructiveRed
);
});
}
packages/flutter/test/cupertino/form_section_test.dart
0 → 100644
View file @
e2447247
// 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
'package:flutter/cupertino.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
void
main
(
)
{
testWidgets
(
'Shows header'
,
(
WidgetTester
tester
)
async
{
const
Widget
header
=
Text
(
'Enter Value'
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoFormSection
(
header:
header
,
children:
<
Widget
>[
CupertinoTextFormFieldRow
()],
),
),
),
);
expect
(
header
,
tester
.
widget
(
find
.
byType
(
Text
)));
});
testWidgets
(
'Shows long dividers in edge-to-edge section part 1'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoFormSection
(
children:
<
Widget
>[
CupertinoTextFormFieldRow
()],
),
),
),
);
// Since the children list is reconstructed with dividers in it, the column
// retrieved should have 3 items for an input [children] param with 1 child.
final
Column
childrenColumn
=
tester
.
widget
(
find
.
byType
(
Column
).
at
(
1
));
expect
(
childrenColumn
.
children
.
length
,
3
);
});
testWidgets
(
'Shows long dividers in edge-to-edge section part 2'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoFormSection
(
children:
<
Widget
>[
CupertinoTextFormFieldRow
(),
CupertinoTextFormFieldRow
()
],
),
),
),
);
// Since the children list is reconstructed with dividers in it, the column
// retrieved should have 5 items for an input [children] param with 2
// children. Two long dividers, two rows, and one short divider.
final
Column
childrenColumn
=
tester
.
widget
(
find
.
byType
(
Column
).
at
(
1
));
expect
(
childrenColumn
.
children
.
length
,
5
);
});
testWidgets
(
'Does not show long dividers in insetGrouped section part 1'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoFormSection
.
insetGrouped
(
children:
<
Widget
>[
CupertinoTextFormFieldRow
()],
),
),
),
);
// Since the children list is reconstructed without long dividers in it, the
// column retrieved should have 1 item for an input [children] param with 1
// child.
final
Column
childrenColumn
=
tester
.
widget
(
find
.
byType
(
Column
).
at
(
1
));
expect
(
childrenColumn
.
children
.
length
,
1
);
});
testWidgets
(
'Does not show long dividers in insetGrouped section part 2'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
restorationScopeId:
'App'
,
home:
Center
(
child:
CupertinoFormSection
.
insetGrouped
(
children:
<
Widget
>[
CupertinoTextFormFieldRow
(),
CupertinoTextFormFieldRow
()
],
),
),
),
);
// Since the children list is reconstructed with short dividers in it, the
// column retrieved should have 3 items for an input [children] param with 2
// children. Two long dividers, two rows, and one short divider.
final
Column
childrenColumn
=
tester
.
widget
(
find
.
byType
(
Column
).
at
(
1
));
expect
(
childrenColumn
.
children
.
length
,
3
);
});
testWidgets
(
'Sets background color for section'
,
(
WidgetTester
tester
)
async
{
const
Color
backgroundColor
=
CupertinoColors
.
systemBlue
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
const
MediaQueryData
(),
child:
CupertinoFormSection
(
children:
<
Widget
>[
CupertinoTextFormFieldRow
()],
backgroundColor:
backgroundColor
,
),
),
),
);
final
DecoratedBox
decoratedBox
=
tester
.
widget
(
find
.
byType
(
DecoratedBox
).
first
);
final
BoxDecoration
boxDecoration
=
decoratedBox
.
decoration
as
BoxDecoration
;
expect
(
boxDecoration
.
color
,
backgroundColor
);
});
testWidgets
(
'Setting clipBehavior clips children section'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoFormSection
(
children:
<
Widget
>[
CupertinoTextFormFieldRow
()],
clipBehavior:
Clip
.
antiAlias
,
),
),
),
);
expect
(
find
.
byType
(
ClipRRect
),
findsOneWidget
);
});
testWidgets
(
'Not setting clipBehavior does not clip children section'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoFormSection
(
children:
<
Widget
>[
CupertinoTextFormFieldRow
()],
),
),
),
);
expect
(
find
.
byType
(
ClipRRect
),
findsNothing
);
});
}
packages/flutter/test/cupertino/text_form_field_row_test.dart
0 → 100644
View file @
e2447247
// 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
'package:flutter/cupertino.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/rendering.dart'
;
import
'../rendering/mock_canvas.dart'
;
void
main
(
)
{
testWidgets
(
'Passes textAlign to underlying CupertinoTextField'
,
(
WidgetTester
tester
)
async
{
const
TextAlign
alignment
=
TextAlign
.
center
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
textAlign:
alignment
,
),
),
),
);
final
Finder
textFieldFinder
=
find
.
byType
(
CupertinoTextField
);
expect
(
textFieldFinder
,
findsOneWidget
);
final
CupertinoTextField
textFieldWidget
=
tester
.
widget
(
textFieldFinder
);
expect
(
textFieldWidget
.
textAlign
,
alignment
);
});
testWidgets
(
'Passes scrollPhysics to underlying TextField'
,
(
WidgetTester
tester
)
async
{
const
ScrollPhysics
scrollPhysics
=
ScrollPhysics
();
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
scrollPhysics:
scrollPhysics
,
),
),
),
);
final
Finder
textFieldFinder
=
find
.
byType
(
CupertinoTextField
);
expect
(
textFieldFinder
,
findsOneWidget
);
final
CupertinoTextField
textFieldWidget
=
tester
.
widget
(
textFieldFinder
);
expect
(
textFieldWidget
.
scrollPhysics
,
scrollPhysics
);
});
testWidgets
(
'Passes textAlignVertical to underlying CupertinoTextField'
,
(
WidgetTester
tester
)
async
{
const
TextAlignVertical
textAlignVertical
=
TextAlignVertical
.
bottom
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
textAlignVertical:
textAlignVertical
,
),
),
),
);
final
Finder
textFieldFinder
=
find
.
byType
(
CupertinoTextField
);
expect
(
textFieldFinder
,
findsOneWidget
);
final
CupertinoTextField
textFieldWidget
=
tester
.
widget
(
textFieldFinder
);
expect
(
textFieldWidget
.
textAlignVertical
,
textAlignVertical
);
});
testWidgets
(
'Passes textInputAction to underlying CupertinoTextField'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
textInputAction:
TextInputAction
.
next
,
),
),
),
);
final
Finder
textFieldFinder
=
find
.
byType
(
CupertinoTextField
);
expect
(
textFieldFinder
,
findsOneWidget
);
final
CupertinoTextField
textFieldWidget
=
tester
.
widget
(
textFieldFinder
);
expect
(
textFieldWidget
.
textInputAction
,
TextInputAction
.
next
);
});
testWidgets
(
'Passes onEditingComplete to underlying CupertinoTextField'
,
(
WidgetTester
tester
)
async
{
final
VoidCallback
onEditingComplete
=
()
{};
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
onEditingComplete:
onEditingComplete
,
),
),
),
);
final
Finder
textFieldFinder
=
find
.
byType
(
CupertinoTextField
);
expect
(
textFieldFinder
,
findsOneWidget
);
final
CupertinoTextField
textFieldWidget
=
tester
.
widget
(
textFieldFinder
);
expect
(
textFieldWidget
.
onEditingComplete
,
onEditingComplete
);
});
testWidgets
(
'Passes cursor attributes to underlying CupertinoTextField'
,
(
WidgetTester
tester
)
async
{
const
double
cursorWidth
=
3.14
;
const
double
cursorHeight
=
6.28
;
const
Radius
cursorRadius
=
Radius
.
circular
(
2
);
const
Color
cursorColor
=
CupertinoColors
.
systemPurple
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
cursorWidth:
cursorWidth
,
cursorHeight:
cursorHeight
,
cursorColor:
cursorColor
,
),
),
),
);
final
Finder
textFieldFinder
=
find
.
byType
(
CupertinoTextField
);
expect
(
textFieldFinder
,
findsOneWidget
);
final
CupertinoTextField
textFieldWidget
=
tester
.
widget
(
textFieldFinder
);
expect
(
textFieldWidget
.
cursorWidth
,
cursorWidth
);
expect
(
textFieldWidget
.
cursorHeight
,
cursorHeight
);
expect
(
textFieldWidget
.
cursorRadius
,
cursorRadius
);
expect
(
textFieldWidget
.
cursorColor
,
cursorColor
);
});
testWidgets
(
'onFieldSubmit callbacks are called'
,
(
WidgetTester
tester
)
async
{
bool
_called
=
false
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
onFieldSubmitted:
(
String
value
)
{
_called
=
true
;
},
),
),
),
);
await
tester
.
showKeyboard
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
testTextInput
.
receiveAction
(
TextInputAction
.
done
);
await
tester
.
pump
();
expect
(
_called
,
true
);
});
testWidgets
(
'onChanged callbacks are called'
,
(
WidgetTester
tester
)
async
{
late
String
_value
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
onChanged:
(
String
value
)
{
_value
=
value
;
},
),
),
),
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'Soup'
);
await
tester
.
pump
();
expect
(
_value
,
'Soup'
);
});
testWidgets
(
'autovalidateMode is passed to super'
,
(
WidgetTester
tester
)
async
{
int
_validateCalled
=
0
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
autovalidateMode:
AutovalidateMode
.
always
,
validator:
(
String
?
value
)
{
_validateCalled
++;
return
null
;
},
),
),
),
);
expect
(
_validateCalled
,
1
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'a'
);
await
tester
.
pump
();
expect
(
_validateCalled
,
2
);
});
testWidgets
(
'validate is called if widget is enabled'
,
(
WidgetTester
tester
)
async
{
int
_validateCalled
=
0
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
enabled:
true
,
autovalidateMode:
AutovalidateMode
.
always
,
validator:
(
String
?
value
)
{
_validateCalled
+=
1
;
return
null
;
},
),
),
),
);
expect
(
_validateCalled
,
1
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'a'
);
await
tester
.
pump
();
expect
(
_validateCalled
,
2
);
});
testWidgets
(
'readonly text form field will hide cursor by default'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
initialValue:
'readonly'
,
readOnly:
true
,
),
),
),
);
await
tester
.
showKeyboard
(
find
.
byType
(
CupertinoTextFormFieldRow
));
expect
(
tester
.
testTextInput
.
hasAnyClients
,
false
);
await
tester
.
tap
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
pump
();
expect
(
tester
.
testTextInput
.
hasAnyClients
,
false
);
await
tester
.
longPress
(
find
.
byType
(
CupertinoTextFormFieldRow
));
await
tester
.
pump
();
// Context menu should not have paste.
expect
(
find
.
text
(
'Paste'
),
findsNothing
);
final
EditableTextState
editableTextState
=
tester
.
firstState
(
find
.
byType
(
EditableText
));
final
RenderEditable
renderEditable
=
editableTextState
.
renderEditable
;
// Make sure it does not paint caret for a period of time.
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
expect
(
renderEditable
,
paintsExactlyCountTimes
(
#drawRect
,
0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
expect
(
renderEditable
,
paintsExactlyCountTimes
(
#drawRect
,
0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
expect
(
renderEditable
,
paintsExactlyCountTimes
(
#drawRect
,
0
));
},
skip:
isBrowser
);
// We do not use Flutter-rendered context menu on the Web
testWidgets
(
'onTap is called upon tap'
,
(
WidgetTester
tester
)
async
{
int
tapCount
=
0
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
onTap:
()
{
tapCount
+=
1
;
},
),
),
),
);
expect
(
tapCount
,
0
);
await
tester
.
tap
(
find
.
byType
(
CupertinoTextField
));
// Wait a bit so they're all single taps and not double taps.
await
tester
.
pump
(
const
Duration
(
milliseconds:
300
));
await
tester
.
tap
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
300
));
await
tester
.
tap
(
find
.
byType
(
CupertinoTextField
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
300
));
expect
(
tapCount
,
3
);
});
// Regression test for https://github.com/flutter/flutter/issues/54472.
testWidgets
(
'reset resets the text fields value to the initialValue'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
initialValue:
'initialValue'
,
),
),
));
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextFormFieldRow
),
'changedValue'
);
final
FormFieldState
<
String
>
state
=
tester
.
state
<
FormFieldState
<
String
>>(
find
.
byType
(
CupertinoTextFormFieldRow
));
state
.
reset
();
expect
(
find
.
text
(
'changedValue'
),
findsNothing
);
expect
(
find
.
text
(
'initialValue'
),
findsOneWidget
);
});
// Regression test for https://github.com/flutter/flutter/issues/54472.
testWidgets
(
'didChange changes text fields value'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
initialValue:
'initialValue'
,
),
),
));
expect
(
find
.
text
(
'initialValue'
),
findsOneWidget
);
final
FormFieldState
<
String
>
state
=
tester
.
state
<
FormFieldState
<
String
>>(
find
.
byType
(
CupertinoTextFormFieldRow
));
state
.
didChange
(
'changedValue'
);
expect
(
find
.
text
(
'initialValue'
),
findsNothing
);
expect
(
find
.
text
(
'changedValue'
),
findsOneWidget
);
});
testWidgets
(
'onChanged callbacks value and FormFieldState.value are sync'
,
(
WidgetTester
tester
)
async
{
bool
_called
=
false
;
late
FormFieldState
<
String
>
state
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
onChanged:
(
String
value
)
{
_called
=
true
;
expect
(
value
,
state
.
value
);
},
),
),
),
);
state
=
tester
.
state
<
FormFieldState
<
String
>>(
find
.
byType
(
CupertinoTextFormFieldRow
));
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'Soup'
);
expect
(
_called
,
true
);
});
testWidgets
(
'autofillHints is passed to super'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
autofillHints:
const
<
String
>[
AutofillHints
.
countryName
],
),
),
),
);
final
CupertinoTextField
widget
=
tester
.
widget
(
find
.
byType
(
CupertinoTextField
));
expect
(
widget
.
autofillHints
,
equals
(
const
<
String
>[
AutofillHints
.
countryName
]));
});
testWidgets
(
'autovalidateMode is passed to super'
,
(
WidgetTester
tester
)
async
{
int
_validateCalled
=
0
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
CupertinoPageScaffold
(
child:
CupertinoTextFormFieldRow
(
autovalidateMode:
AutovalidateMode
.
onUserInteraction
,
validator:
(
String
?
value
)
{
_validateCalled
++;
return
null
;
},
),
),
),
);
expect
(
_validateCalled
,
0
);
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
'a'
);
await
tester
.
pump
();
expect
(
_validateCalled
,
1
);
});
testWidgets
(
'AutovalidateMode.always mode shows error from the start'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
initialValue:
'Value'
,
autovalidateMode:
AutovalidateMode
.
always
,
validator:
(
String
?
value
)
=>
'Error'
,
),
),
),
);
final
Finder
errorTextFinder
=
find
.
byType
(
Text
);
expect
(
errorTextFinder
,
findsOneWidget
);
final
Text
errorText
=
tester
.
widget
(
errorTextFinder
);
expect
(
errorText
.
data
,
'Error'
);
});
testWidgets
(
'Shows error text upon invalid input'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
(
text:
''
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
controller:
controller
,
autovalidateMode:
AutovalidateMode
.
onUserInteraction
,
validator:
(
String
?
value
)
=>
'Error'
,
),
),
),
);
expect
(
find
.
byType
(
Text
),
findsNothing
);
controller
.
text
=
'Value'
;
await
tester
.
pumpAndSettle
();
final
Finder
errorTextFinder
=
find
.
byType
(
Text
);
expect
(
errorTextFinder
,
findsOneWidget
);
final
Text
errorText
=
tester
.
widget
(
errorTextFinder
);
expect
(
errorText
.
data
,
'Error'
);
});
testWidgets
(
'Shows prefix'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextFormFieldRow
(
prefix:
const
Text
(
'Enter Value'
),
),
),
),
);
final
Finder
errorTextFinder
=
find
.
byType
(
Text
);
expect
(
errorTextFinder
,
findsOneWidget
);
final
Text
errorText
=
tester
.
widget
(
errorTextFinder
);
expect
(
errorText
.
data
,
'Enter Value'
);
});
}
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