Revert "Added CupertinoFormSection, CupertinoSplitFormRow, and CupertinoTextFormField (#70676)" (#71490)
This reverts commit e244724794.
This commit is contained in:
@@ -25,8 +25,6 @@ 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';
|
||||
@@ -45,7 +43,6 @@ 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';
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
// 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!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,282 +0,0 @@
|
||||
// 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,6 @@ 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(
|
||||
@@ -331,148 +326,6 @@ 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].
|
||||
|
||||
@@ -1,380 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
@@ -1,479 +0,0 @@
|
||||
// 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');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user