From 720f366ff5bece5af913f40466bcf67e73d530f2 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 1 Dec 2020 10:11:41 -0800 Subject: [PATCH] Revert "Added CupertinoFormSection, CupertinoSplitFormRow, and CupertinoTextFormField (#70676)" (#71490) This reverts commit e244724794b0cb700b5d3c0096c9a1463e06c8e9. --- packages/flutter/lib/cupertino.dart | 3 - .../flutter/lib/src/cupertino/form_row.dart | 200 -------- .../lib/src/cupertino/form_section.dart | 282 ----------- .../flutter/lib/src/cupertino/text_field.dart | 147 ------ .../src/cupertino/text_form_field_row.dart | 380 -------------- .../flutter/test/cupertino/form_row_test.dart | 175 ------- .../test/cupertino/form_section_test.dart | 162 ------ .../cupertino/text_form_field_row_test.dart | 479 ------------------ 8 files changed, 1828 deletions(-) delete mode 100644 packages/flutter/lib/src/cupertino/form_row.dart delete mode 100644 packages/flutter/lib/src/cupertino/form_section.dart delete mode 100644 packages/flutter/lib/src/cupertino/text_form_field_row.dart delete mode 100644 packages/flutter/test/cupertino/form_row_test.dart delete mode 100644 packages/flutter/test/cupertino/form_section_test.dart delete mode 100644 packages/flutter/test/cupertino/text_form_field_row_test.dart diff --git a/packages/flutter/lib/cupertino.dart b/packages/flutter/lib/cupertino.dart index 9c0ef65c2a..03eadeab5d 100644 --- a/packages/flutter/lib/cupertino.dart +++ b/packages/flutter/lib/cupertino.dart @@ -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'; diff --git a/packages/flutter/lib/src/cupertino/form_row.dart b/packages/flutter/lib/src/cupertino/form_row.dart deleted file mode 100644 index 827c7b279b..0000000000 --- a/packages/flutter/lib/src/cupertino/form_row.dart +++ /dev/null @@ -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 { -/// bool toggleValue = false; -/// -/// @override -/// Widget build(BuildContext context) { -/// return CupertinoPageScaffold( -/// child: Center( -/// child: CupertinoFormSection( -/// header: Text('SECTION 1'), -/// children: [ -/// 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 rowChildren = [ - if (prefix != null) - DefaultTextStyle( - style: textStyle, - child: prefix!, - ), - Flexible( - child: Align( - alignment: AlignmentDirectional.centerEnd, - child: child, - ), - ), - ]; - - return Padding( - padding: padding ?? _kDefaultPadding, - child: Column( - children: [ - 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!, - ), - ), - ], - ), - ); - } -} diff --git a/packages/flutter/lib/src/cupertino/form_section.dart b/packages/flutter/lib/src/cupertino/form_section.dart deleted file mode 100644 index 2d2e24177e..0000000000 --- a/packages/flutter/lib/src/cupertino/form_section.dart +++ /dev/null @@ -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 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 childrenWithDividers = []; - - 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: [ - 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), - ), - ], - ), - ); - } -} diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index 4a8ac61587..8df402b238 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -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]. diff --git a/packages/flutter/lib/src/cupertino/text_form_field_row.dart b/packages/flutter/lib/src/cupertino/text_form_field_row.dart deleted file mode 100644 index 6e255142dc..0000000000 --- a/packages/flutter/lib/src/cupertino/text_form_field_row.dart +++ /dev/null @@ -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.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 { - /// 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? onChanged, - GestureTapCallback? onTap, - VoidCallback? onEditingComplete, - ValueChanged? onFieldSubmitted, - FormFieldSetter? onSaved, - FormFieldValidator? validator, - List? 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? 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 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 { - 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); - } -} diff --git a/packages/flutter/test/cupertino/form_row_test.dart b/packages/flutter/test/cupertino/form_row_test.dart deleted file mode 100644 index f10a8b35e7..0000000000 --- a/packages/flutter/test/cupertino/form_row_test.dart +++ /dev/null @@ -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); - }); -} diff --git a/packages/flutter/test/cupertino/form_section_test.dart b/packages/flutter/test/cupertino/form_section_test.dart deleted file mode 100644 index 8360f1f23e..0000000000 --- a/packages/flutter/test/cupertino/form_section_test.dart +++ /dev/null @@ -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: [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: [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: [ - 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: [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: [ - 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: [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: [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: [CupertinoTextFormFieldRow()], - ), - ), - ), - ); - - expect(find.byType(ClipRRect), findsNothing); - }); -} diff --git a/packages/flutter/test/cupertino/text_form_field_row_test.dart b/packages/flutter/test/cupertino/text_form_field_row_test.dart deleted file mode 100644 index 55a59a5ed2..0000000000 --- a/packages/flutter/test/cupertino/text_form_field_row_test.dart +++ /dev/null @@ -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 state = tester - .state>(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 state = tester - .state>(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 state; - - await tester.pumpWidget( - CupertinoApp( - home: Center( - child: CupertinoTextFormFieldRow( - onChanged: (String value) { - _called = true; - expect(value, state.value); - }, - ), - ), - ), - ); - - state = tester - .state>(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 [AutofillHints.countryName], - ), - ), - ), - ); - - final CupertinoTextField widget = - tester.widget(find.byType(CupertinoTextField)); - expect(widget.autofillHints, - equals(const [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'); - }); -}