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
4d4d017a
Unverified
Commit
4d4d017a
authored
Dec 04, 2020
by
Daniel Edrisian
Committed by
GitHub
Dec 04, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Re-land CupertinoFormSection, CupertinoFormRow, and CupertinoTextFormFieldRow (#71522)
parent
94d574fa
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1834 additions
and
0 deletions
+1834
-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
+386
-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 @
4d4d017a
...
@@ -25,6 +25,8 @@ export 'src/cupertino/context_menu.dart';
...
@@ -25,6 +25,8 @@ export 'src/cupertino/context_menu.dart';
export
'src/cupertino/context_menu_action.dart'
;
export
'src/cupertino/context_menu_action.dart'
;
export
'src/cupertino/date_picker.dart'
;
export
'src/cupertino/date_picker.dart'
;
export
'src/cupertino/dialog.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/icon_theme_data.dart'
;
export
'src/cupertino/icons.dart'
;
export
'src/cupertino/icons.dart'
;
export
'src/cupertino/interface_level.dart'
;
export
'src/cupertino/interface_level.dart'
;
...
@@ -43,6 +45,7 @@ export 'src/cupertino/switch.dart';
...
@@ -43,6 +45,7 @@ export 'src/cupertino/switch.dart';
export
'src/cupertino/tab_scaffold.dart'
;
export
'src/cupertino/tab_scaffold.dart'
;
export
'src/cupertino/tab_view.dart'
;
export
'src/cupertino/tab_view.dart'
;
export
'src/cupertino/text_field.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_selection.dart'
;
export
'src/cupertino/text_theme.dart'
;
export
'src/cupertino/text_theme.dart'
;
export
'src/cupertino/theme.dart'
;
export
'src/cupertino/theme.dart'
;
...
...
packages/flutter/lib/src/cupertino/form_row.dart
0 → 100644
View file @
4d4d017a
// 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 @
4d4d017a
// 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 @
4d4d017a
...
@@ -16,6 +16,11 @@ import 'theme.dart';
...
@@ -16,6 +16,11 @@ import 'theme.dart';
export
'package:flutter/services.dart'
show
TextInputType
,
TextInputAction
,
TextCapitalization
,
SmartQuotesType
,
SmartDashesType
;
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.
// Value inspected from Xcode 11 & iOS 13.0 Simulator.
const
BorderSide
_kDefaultRoundedBorderSide
=
BorderSide
(
const
BorderSide
_kDefaultRoundedBorderSide
=
BorderSide
(
color:
CupertinoDynamicColor
.
withBrightness
(
color:
CupertinoDynamicColor
.
withBrightness
(
...
@@ -326,6 +331,148 @@ class CupertinoTextField extends StatefulWidget {
...
@@ -326,6 +331,148 @@ class CupertinoTextField extends StatefulWidget {
)),
)),
super
(
key:
key
);
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.
/// Controls the text being edited.
///
///
/// If null, this widget will create its own [TextEditingController].
/// If null, this widget will create its own [TextEditingController].
...
...
packages/flutter/lib/src/cupertino/text_form_field_row.dart
0 → 100644
View file @
4d4d017a
// 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
(
value
!=
null
&&
_effectiveController
!.
text
!=
value
)
{
_effectiveController
!.
text
=
value
;
}
}
@override
void
reset
()
{
super
.
reset
();
if
(
widget
.
initialValue
!=
null
)
{
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 @
4d4d017a
// 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 @
4d4d017a
// 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 @
4d4d017a
// 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