"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter/lib/src/cupertino/text_field.dart" (13 Nov 2020, 41053 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 // @dart = 2.8
    6 
    7 import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
    8 
    9 import 'package:flutter/gestures.dart';
   10 import 'package:flutter/rendering.dart';
   11 import 'package:flutter/services.dart';
   12 import 'package:flutter/widgets.dart';
   13 
   14 import 'colors.dart';
   15 import 'icons.dart';
   16 import 'text_selection.dart';
   17 import 'theme.dart';
   18 
   19 export 'package:flutter/services.dart' show TextInputType, TextInputAction, TextCapitalization, SmartQuotesType, SmartDashesType;
   20 
   21 // Value inspected from Xcode 11 & iOS 13.0 Simulator.
   22 const BorderSide _kDefaultRoundedBorderSide = BorderSide(
   23   color: CupertinoDynamicColor.withBrightness(
   24     color: Color(0x33000000),
   25     darkColor: Color(0x33FFFFFF),
   26   ),
   27   style: BorderStyle.solid,
   28   width: 0.0,
   29 );
   30 const Border _kDefaultRoundedBorder = Border(
   31   top: _kDefaultRoundedBorderSide,
   32   bottom: _kDefaultRoundedBorderSide,
   33   left: _kDefaultRoundedBorderSide,
   34   right: _kDefaultRoundedBorderSide,
   35 );
   36 
   37 const BoxDecoration _kDefaultRoundedBorderDecoration = BoxDecoration(
   38   color: CupertinoDynamicColor.withBrightness(
   39     color: CupertinoColors.white,
   40     darkColor: CupertinoColors.black,
   41   ),
   42   border: _kDefaultRoundedBorder,
   43   borderRadius: BorderRadius.all(Radius.circular(5.0)),
   44 );
   45 
   46 const Color _kDisabledBackground = CupertinoDynamicColor.withBrightness(
   47   color: Color(0xFFFAFAFA),
   48   darkColor: Color(0xFF050505),
   49 );
   50 
   51 // Value inspected from Xcode 11 & iOS 13.0 Simulator.
   52 // Note it may not be consistent with https://developer.apple.com/design/resources/.
   53 const CupertinoDynamicColor _kClearButtonColor = CupertinoDynamicColor.withBrightness(
   54   color: Color(0xFF636366),
   55   darkColor: Color(0xFFAEAEB2),
   56 );
   57 
   58 // An eyeballed value that moves the cursor slightly left of where it is
   59 // rendered for text on Android so it's positioning more accurately matches the
   60 // native iOS text cursor positioning.
   61 //
   62 // This value is in device pixels, not logical pixels as is typically used
   63 // throughout the codebase.
   64 const int _iOSHorizontalCursorOffsetPixels = -2;
   65 
   66 /// Visibility of text field overlays based on the state of the current text entry.
   67 ///
   68 /// Used to toggle the visibility behavior of the optional decorating widgets
   69 /// surrounding the [EditableText] such as the clear text button.
   70 enum OverlayVisibilityMode {
   71   /// Overlay will never appear regardless of the text entry state.
   72   never,
   73 
   74   /// Overlay will only appear when the current text entry is not empty.
   75   ///
   76   /// This includes prefilled text that the user did not type in manually. But
   77   /// does not include text in placeholders.
   78   editing,
   79 
   80   /// Overlay will only appear when the current text entry is empty.
   81   ///
   82   /// This also includes not having prefilled text that the user did not type
   83   /// in manually. Texts in placeholders are ignored.
   84   notEditing,
   85 
   86   /// Always show the overlay regardless of the text entry state.
   87   always,
   88 }
   89 
   90 class _CupertinoTextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder {
   91   _CupertinoTextFieldSelectionGestureDetectorBuilder({
   92     @required _CupertinoTextFieldState state,
   93   }) : _state = state,
   94        super(delegate: state);
   95 
   96   final _CupertinoTextFieldState _state;
   97 
   98   @override
   99   void onSingleTapUp(TapUpDetails details) {
  100     // Because TextSelectionGestureDetector listens to taps that happen on
  101     // widgets in front of it, tapping the clear button will also trigger
  102     // this handler. If the clear button widget recognizes the up event,
  103     // then do not handle it.
  104     if (_state._clearGlobalKey.currentContext != null) {
  105       final RenderBox renderBox = _state._clearGlobalKey.currentContext.findRenderObject() as RenderBox;
  106       final Offset localOffset = renderBox.globalToLocal(details.globalPosition);
  107       if (renderBox.hitTest(BoxHitTestResult(), position: localOffset)) {
  108         return;
  109       }
  110     }
  111     super.onSingleTapUp(details);
  112     _state._requestKeyboard();
  113     if (_state.widget.onTap != null)
  114       _state.widget.onTap();
  115   }
  116 
  117   @override
  118   void onDragSelectionEnd(DragEndDetails details) {
  119     _state._requestKeyboard();
  120   }
  121 }
  122 
  123 /// An iOS-style text field.
  124 ///
  125 /// A text field lets the user enter text, either with a hardware keyboard or with
  126 /// an onscreen keyboard.
  127 ///
  128 /// This widget corresponds to both a `UITextField` and an editable `UITextView`
  129 /// on iOS.
  130 ///
  131 /// The text field calls the [onChanged] callback whenever the user changes the
  132 /// text in the field. If the user indicates that they are done typing in the
  133 /// field (e.g., by pressing a button on the soft keyboard), the text field
  134 /// calls the [onSubmitted] callback.
  135 ///
  136 /// To control the text that is displayed in the text field, use the
  137 /// [controller]. For example, to set the initial value of the text field, use
  138 /// a [controller] that already contains some text such as:
  139 ///
  140 /// {@tool snippet}
  141 ///
  142 /// ```dart
  143 /// class MyPrefilledText extends StatefulWidget {
  144 ///   @override
  145 ///   _MyPrefilledTextState createState() => _MyPrefilledTextState();
  146 /// }
  147 ///
  148 /// class _MyPrefilledTextState extends State<MyPrefilledText> {
  149 ///   TextEditingController _textController;
  150 ///
  151 ///   @override
  152 ///   void initState() {
  153 ///     super.initState();
  154 ///     _textController = TextEditingController(text: 'initial text');
  155 ///   }
  156 ///
  157 ///   @override
  158 ///   Widget build(BuildContext context) {
  159 ///     return CupertinoTextField(controller: _textController);
  160 ///   }
  161 /// }
  162 /// ```
  163 /// {@end-tool}
  164 ///
  165 /// The [controller] can also control the selection and composing region (and to
  166 /// observe changes to the text, selection, and composing region).
  167 ///
  168 /// The text field has an overridable [decoration] that, by default, draws a
  169 /// rounded rectangle border around the text field. If you set the [decoration]
  170 /// property to null, the decoration will be removed entirely.
  171 ///
  172 /// Remember to call [TextEditingController.dispose] when it is no longer
  173 /// needed. This will ensure we discard any resources used by the object.
  174 ///
  175 /// See also:
  176 ///
  177 ///  * <https://developer.apple.com/documentation/uikit/uitextfield>
  178 ///  * [TextField], an alternative text field widget that follows the Material
  179 ///    Design UI conventions.
  180 ///  * [EditableText], which is the raw text editing control at the heart of a
  181 ///    [TextField].
  182 ///  * Learn how to use a [TextEditingController] in one of our [cookbook recipes](https://flutter.dev/docs/cookbook/forms/text-field-changes#2-use-a-texteditingcontroller).
  183 class CupertinoTextField extends StatefulWidget {
  184   /// Creates an iOS-style text field.
  185   ///
  186   /// To provide a prefilled text entry, pass in a [TextEditingController] with
  187   /// an initial value to the [controller] parameter.
  188   ///
  189   /// To provide a hint placeholder text that appears when the text entry is
  190   /// empty, pass a [String] to the [placeholder] parameter.
  191   ///
  192   /// The [maxLines] property can be set to null to remove the restriction on
  193   /// the number of lines. In this mode, the intrinsic height of the widget will
  194   /// grow as the number of lines of text grows. By default, it is `1`, meaning
  195   /// this is a single-line text field and will scroll horizontally when
  196   /// overflown. [maxLines] must not be zero.
  197   ///
  198   /// The text cursor is not shown if [showCursor] is false or if [showCursor]
  199   /// is null (the default) and [readOnly] is true.
  200   ///
  201   /// If specified, the [maxLength] property must be greater than zero.
  202   ///
  203   /// The [selectionHeightStyle] and [selectionWidthStyle] properties allow
  204   /// changing the shape of the selection highlighting. These properties default
  205   /// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
  206   /// must not be null.
  207   ///
  208   /// The [autocorrect], [autofocus], [clearButtonMode], [dragStartBehavior],
  209   /// [expands], [maxLengthEnforced], [obscureText], [prefixMode], [readOnly],
  210   /// [scrollPadding], [suffixMode], [textAlign], [selectionHeightStyle],
  211   /// [selectionWidthStyle], and [enableSuggestions] properties must not be null.
  212   ///
  213   /// See also:
  214   ///
  215   ///  * [minLines]
  216   ///  * [expands], to allow the widget to size itself to its parent's height.
  217   ///  * [maxLength], which discusses the precise meaning of "number of
  218   ///    characters" and how it may differ from the intuitive meaning.
  219   const CupertinoTextField({
  220     Key key,
  221     this.controller,
  222     this.focusNode,
  223     this.decoration = _kDefaultRoundedBorderDecoration,
  224     this.padding = const EdgeInsets.all(6.0),
  225     this.placeholder,
  226     this.placeholderStyle = const TextStyle(
  227       fontWeight: FontWeight.w400,
  228       color: CupertinoColors.placeholderText,
  229     ),
  230     this.prefix,
  231     this.prefixMode = OverlayVisibilityMode.always,
  232     this.suffix,
  233     this.suffixMode = OverlayVisibilityMode.always,
  234     this.clearButtonMode = OverlayVisibilityMode.never,
  235     TextInputType keyboardType,
  236     this.textInputAction,
  237     this.textCapitalization = TextCapitalization.none,
  238     this.style,
  239     this.strutStyle,
  240     this.textAlign = TextAlign.start,
  241     this.textAlignVertical,
  242     this.readOnly = false,
  243     ToolbarOptions toolbarOptions,
  244     this.showCursor,
  245     this.autofocus = false,
  246     this.obscuringCharacter = '•',
  247     this.obscureText = false,
  248     this.autocorrect = true,
  249     SmartDashesType smartDashesType,
  250     SmartQuotesType smartQuotesType,
  251     this.enableSuggestions = true,
  252     this.maxLines = 1,
  253     this.minLines,
  254     this.expands = false,
  255     this.maxLength,
  256     this.maxLengthEnforced = true,
  257     this.onChanged,
  258     this.onEditingComplete,
  259     this.onSubmitted,
  260     this.inputFormatters,
  261     this.enabled,
  262     this.cursorWidth = 2.0,
  263     this.cursorHeight,
  264     this.cursorRadius = const Radius.circular(2.0),
  265     this.cursorColor,
  266     this.selectionHeightStyle = ui.BoxHeightStyle.tight,
  267     this.selectionWidthStyle = ui.BoxWidthStyle.tight,
  268     this.keyboardAppearance,
  269     this.scrollPadding = const EdgeInsets.all(20.0),
  270     this.dragStartBehavior = DragStartBehavior.start,
  271     this.enableInteractiveSelection = true,
  272     this.onTap,
  273     this.scrollController,
  274     this.scrollPhysics,
  275     this.autofillHints,
  276     this.restorationId,
  277   }) : assert(textAlign != null),
  278        assert(readOnly != null),
  279        assert(autofocus != null),
  280        assert(obscuringCharacter != null && obscuringCharacter.length == 1),
  281        assert(obscureText != null),
  282        assert(autocorrect != null),
  283        smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
  284        smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
  285        assert(enableSuggestions != null),
  286        assert(maxLengthEnforced != null),
  287        assert(scrollPadding != null),
  288        assert(dragStartBehavior != null),
  289        assert(selectionHeightStyle != null),
  290        assert(selectionWidthStyle != null),
  291        assert(maxLines == null || maxLines > 0),
  292        assert(minLines == null || minLines > 0),
  293        assert(
  294          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
  295          "minLines can't be greater than maxLines",
  296        ),
  297        assert(expands != null),
  298        assert(
  299          !expands || (maxLines == null && minLines == null),
  300          'minLines and maxLines must be null when expands is true.',
  301        ),
  302        assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
  303        assert(maxLength == null || maxLength > 0),
  304        assert(clearButtonMode != null),
  305        assert(prefixMode != null),
  306        assert(suffixMode != null),
  307        // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set.
  308        assert(!identical(textInputAction, TextInputAction.newline) ||
  309          maxLines == 1 ||
  310          !identical(keyboardType, TextInputType.text),
  311          'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.'),
  312        keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
  313        toolbarOptions = toolbarOptions ?? (obscureText ?
  314          const ToolbarOptions(
  315            selectAll: true,
  316            paste: true,
  317          ) :
  318          const ToolbarOptions(
  319            copy: true,
  320            cut: true,
  321            selectAll: true,
  322            paste: true,
  323          )),
  324        super(key: key);
  325 
  326   /// Controls the text being edited.
  327   ///
  328   /// If null, this widget will create its own [TextEditingController].
  329   final TextEditingController controller;
  330 
  331   /// {@macro flutter.widgets.Focus.focusNode}
  332   final FocusNode focusNode;
  333 
  334   /// Controls the [BoxDecoration] of the box behind the text input.
  335   ///
  336   /// Defaults to having a rounded rectangle grey border and can be null to have
  337   /// no box decoration.
  338   final BoxDecoration decoration;
  339 
  340   /// Padding around the text entry area between the [prefix] and [suffix]
  341   /// or the clear button when [clearButtonMode] is not never.
  342   ///
  343   /// Defaults to a padding of 6 pixels on all sides and can be null.
  344   final EdgeInsetsGeometry padding;
  345 
  346   /// A lighter colored placeholder hint that appears on the first line of the
  347   /// text field when the text entry is empty.
  348   ///
  349   /// Defaults to having no placeholder text.
  350   ///
  351   /// The text style of the placeholder text matches that of the text field's
  352   /// main text entry except a lighter font weight and a grey font color.
  353   final String placeholder;
  354 
  355   /// The style to use for the placeholder text.
  356   ///
  357   /// The [placeholderStyle] is merged with the [style] [TextStyle] when applied
  358   /// to the [placeholder] text. To avoid merging with [style], specify
  359   /// [TextStyle.inherit] as false.
  360   ///
  361   /// Defaults to the [style] property with w300 font weight and grey color.
  362   ///
  363   /// If specifically set to null, placeholder's style will be the same as [style].
  364   final TextStyle placeholderStyle;
  365 
  366   /// An optional [Widget] to display before the text.
  367   final Widget prefix;
  368 
  369   /// Controls the visibility of the [prefix] widget based on the state of
  370   /// text entry when the [prefix] argument is not null.
  371   ///
  372   /// Defaults to [OverlayVisibilityMode.always] and cannot be null.
  373   ///
  374   /// Has no effect when [prefix] is null.
  375   final OverlayVisibilityMode prefixMode;
  376 
  377   /// An optional [Widget] to display after the text.
  378   final Widget suffix;
  379 
  380   /// Controls the visibility of the [suffix] widget based on the state of
  381   /// text entry when the [suffix] argument is not null.
  382   ///
  383   /// Defaults to [OverlayVisibilityMode.always] and cannot be null.
  384   ///
  385   /// Has no effect when [suffix] is null.
  386   final OverlayVisibilityMode suffixMode;
  387 
  388   /// Show an iOS-style clear button to clear the current text entry.
  389   ///
  390   /// Can be made to appear depending on various text states of the
  391   /// [TextEditingController].
  392   ///
  393   /// Will only appear if no [suffix] widget is appearing.
  394   ///
  395   /// Defaults to never appearing and cannot be null.
  396   final OverlayVisibilityMode clearButtonMode;
  397 
  398   /// {@macro flutter.widgets.editableText.keyboardType}
  399   final TextInputType keyboardType;
  400 
  401   /// The type of action button to use for the keyboard.
  402   ///
  403   /// Defaults to [TextInputAction.newline] if [keyboardType] is
  404   /// [TextInputType.multiline] and [TextInputAction.done] otherwise.
  405   final TextInputAction textInputAction;
  406 
  407   /// {@macro flutter.widgets.editableText.textCapitalization}
  408   final TextCapitalization textCapitalization;
  409 
  410   /// The style to use for the text being edited.
  411   ///
  412   /// Also serves as a base for the [placeholder] text's style.
  413   ///
  414   /// Defaults to the standard iOS font style from [CupertinoTheme] if null.
  415   final TextStyle style;
  416 
  417   /// {@macro flutter.widgets.editableText.strutStyle}
  418   final StrutStyle strutStyle;
  419 
  420   /// {@macro flutter.widgets.editableText.textAlign}
  421   final TextAlign textAlign;
  422 
  423   /// Configuration of toolbar options.
  424   ///
  425   /// If not set, select all and paste will default to be enabled. Copy and cut
  426   /// will be disabled if [obscureText] is true. If [readOnly] is true,
  427   /// paste and cut will be disabled regardless.
  428   final ToolbarOptions toolbarOptions;
  429 
  430   /// {@macro flutter.widgets.inputDecorator.textAlignVertical}
  431   final TextAlignVertical textAlignVertical;
  432 
  433   /// {@macro flutter.widgets.editableText.readOnly}
  434   final bool readOnly;
  435 
  436   /// {@macro flutter.widgets.editableText.showCursor}
  437   final bool showCursor;
  438 
  439   /// {@macro flutter.widgets.editableText.autofocus}
  440   final bool autofocus;
  441 
  442   /// {@macro flutter.widgets.editableText.obscuringCharacter}
  443   final String obscuringCharacter;
  444 
  445   /// {@macro flutter.widgets.editableText.obscureText}
  446   final bool obscureText;
  447 
  448   /// {@macro flutter.widgets.editableText.autocorrect}
  449   final bool autocorrect;
  450 
  451   /// {@macro flutter.services.textInput.smartDashesType}
  452   final SmartDashesType smartDashesType;
  453 
  454   /// {@macro flutter.services.textInput.smartQuotesType}
  455   final SmartQuotesType smartQuotesType;
  456 
  457   /// {@macro flutter.services.textInput.enableSuggestions}
  458   final bool enableSuggestions;
  459 
  460   /// {@macro flutter.widgets.editableText.maxLines}
  461   final int maxLines;
  462 
  463   /// {@macro flutter.widgets.editableText.minLines}
  464   final int minLines;
  465 
  466   /// {@macro flutter.widgets.editableText.expands}
  467   final bool expands;
  468 
  469   /// The maximum number of characters (Unicode scalar values) to allow in the
  470   /// text field.
  471   ///
  472   /// If set, a character counter will be displayed below the
  473   /// field, showing how many characters have been entered and how many are
  474   /// allowed. After [maxLength] characters have been input, additional input
  475   /// is ignored, unless [maxLengthEnforced] is set to false. The TextField
  476   /// enforces the length with a [LengthLimitingTextInputFormatter], which is
  477   /// evaluated after the supplied [inputFormatters], if any.
  478   ///
  479   /// This value must be either null or greater than zero. If set to null
  480   /// (the default), there is no limit to the number of characters allowed.
  481   ///
  482   /// Whitespace characters (e.g. newline, space, tab) are included in the
  483   /// character count.
  484   ///
  485   /// ## Limitations
  486   ///
  487   /// The CupertinoTextField does not currently count Unicode grapheme clusters
  488   /// (i.e. characters visible to the user), it counts Unicode scalar values,
  489   /// which leaves out a number of useful possible characters (like many emoji
  490   /// and composed characters), so this will be inaccurate in the presence of
  491   /// those characters. If you expect to encounter these kinds of characters, be
  492   /// generous in the maxLength used.
  493   ///
  494   /// For instance, the character "ö" can be represented as '\u{006F}\u{0308}',
  495   /// which is the letter "o" followed by a composed diaeresis "¨", or it can
  496   /// be represented as '\u{00F6}', which is the Unicode scalar value "LATIN
  497   /// SMALL LETTER O WITH DIAERESIS". In the first case, the text field will
  498   /// count two characters, and the second case will be counted as one
  499   /// character, even though the user can see no difference in the input.
  500   ///
  501   /// Similarly, some emoji are represented by multiple scalar values. The
  502   /// Unicode "THUMBS UP SIGN + MEDIUM SKIN TONE MODIFIER", "👍🏽", should be
  503   /// counted as a single character, but because it is a combination of two
  504   /// Unicode scalar values, '\u{1F44D}\u{1F3FD}', it is counted as two
  505   /// characters.
  506   ///
  507   /// See also:
  508   ///
  509   ///  * [LengthLimitingTextInputFormatter] for more information on how it
  510   ///    counts characters, and how it may differ from the intuitive meaning.
  511   final int maxLength;
  512 
  513   /// If true, prevents the field from allowing more than [maxLength]
  514   /// characters.
  515   ///
  516   /// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
  517   /// enforce the limit, or merely provide a character counter and warning when
  518   /// [maxLength] is exceeded.
  519   final bool maxLengthEnforced;
  520 
  521   /// {@macro flutter.widgets.editableText.onChanged}
  522   final ValueChanged<String> onChanged;
  523 
  524   /// {@macro flutter.widgets.editableText.onEditingComplete}
  525   final VoidCallback onEditingComplete;
  526 
  527   /// {@macro flutter.widgets.editableText.onSubmitted}
  528   ///
  529   /// See also:
  530   ///
  531   ///  * [EditableText.onSubmitted] for an example of how to handle moving to
  532   ///    the next/previous field when using [TextInputAction.next] and
  533   ///    [TextInputAction.previous] for [textInputAction].
  534   final ValueChanged<String> onSubmitted;
  535 
  536   /// {@macro flutter.widgets.editableText.inputFormatters}
  537   final List<TextInputFormatter> inputFormatters;
  538 
  539   /// Disables the text field when false.
  540   ///
  541   /// Text fields in disabled states have a light grey background and don't
  542   /// respond to touch events including the [prefix], [suffix] and the clear
  543   /// button.
  544   final bool enabled;
  545 
  546   /// {@macro flutter.widgets.editableText.cursorWidth}
  547   final double cursorWidth;
  548 
  549   /// {@macro flutter.widgets.editableText.cursorHeight}
  550   final double cursorHeight;
  551 
  552   /// {@macro flutter.widgets.editableText.cursorRadius}
  553   final Radius cursorRadius;
  554 
  555   /// The color to use when painting the cursor.
  556   ///
  557   /// Defaults to the [CupertinoThemeData.primaryColor] of the ambient theme,
  558   /// which itself defaults to [CupertinoColors.activeBlue] in the light theme
  559   /// and [CupertinoColors.activeOrange] in the dark theme.
  560   final Color cursorColor;
  561 
  562   /// Controls how tall the selection highlight boxes are computed to be.
  563   ///
  564   /// See [ui.BoxHeightStyle] for details on available styles.
  565   final ui.BoxHeightStyle selectionHeightStyle;
  566 
  567   /// Controls how wide the selection highlight boxes are computed to be.
  568   ///
  569   /// See [ui.BoxWidthStyle] for details on available styles.
  570   final ui.BoxWidthStyle selectionWidthStyle;
  571 
  572   /// The appearance of the keyboard.
  573   ///
  574   /// This setting is only honored on iOS devices.
  575   ///
  576   /// If null, defaults to [Brightness.light].
  577   final Brightness keyboardAppearance;
  578 
  579   /// {@macro flutter.widgets.editableText.scrollPadding}
  580   final EdgeInsets scrollPadding;
  581 
  582   /// {@macro flutter.widgets.editableText.enableInteractiveSelection}
  583   final bool enableInteractiveSelection;
  584 
  585   /// {@macro flutter.widgets.scrollable.dragStartBehavior}
  586   final DragStartBehavior dragStartBehavior;
  587 
  588   /// {@macro flutter.widgets.editableText.scrollController}
  589   final ScrollController scrollController;
  590 
  591   /// {@macro flutter.widgets.editableText.scrollPhysics}
  592   final ScrollPhysics scrollPhysics;
  593 
  594   /// {@macro flutter.widgets.editableText.selectionEnabled}
  595   bool get selectionEnabled => enableInteractiveSelection;
  596 
  597   /// {@macro flutter.material.textfield.onTap}
  598   final GestureTapCallback onTap;
  599 
  600   /// {@macro flutter.widgets.editableText.autofillHints}
  601   /// {@macro flutter.services.autofill.autofillHints}
  602   final Iterable<String> autofillHints;
  603 
  604   /// {@macro flutter.material.textfield.restorationId}
  605   final String restorationId;
  606 
  607   @override
  608   _CupertinoTextFieldState createState() => _CupertinoTextFieldState();
  609 
  610   @override
  611   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  612     super.debugFillProperties(properties);
  613     properties.add(DiagnosticsProperty<TextEditingController>('controller', controller, defaultValue: null));
  614     properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
  615     properties.add(DiagnosticsProperty<BoxDecoration>('decoration', decoration));
  616     properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
  617     properties.add(StringProperty('placeholder', placeholder));
  618     properties.add(DiagnosticsProperty<TextStyle>('placeholderStyle', placeholderStyle));
  619     properties.add(DiagnosticsProperty<OverlayVisibilityMode>('prefix', prefix == null ? null : prefixMode));
  620     properties.add(DiagnosticsProperty<OverlayVisibilityMode>('suffix', suffix == null ? null : suffixMode));
  621     properties.add(DiagnosticsProperty<OverlayVisibilityMode>('clearButtonMode', clearButtonMode));
  622     properties.add(DiagnosticsProperty<TextInputType>('keyboardType', keyboardType, defaultValue: TextInputType.text));
  623     properties.add(DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
  624     properties.add(DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
  625     properties.add(DiagnosticsProperty<String>('obscuringCharacter', obscuringCharacter, defaultValue: '•'));
  626     properties.add(DiagnosticsProperty<bool>('obscureText', obscureText, defaultValue: false));
  627     properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect, defaultValue: true));
  628     properties.add(EnumProperty<SmartDashesType>('smartDashesType', smartDashesType, defaultValue: obscureText ? SmartDashesType.disabled : SmartDashesType.enabled));
  629     properties.add(EnumProperty<SmartQuotesType>('smartQuotesType', smartQuotesType, defaultValue: obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled));
  630     properties.add(DiagnosticsProperty<bool>('enableSuggestions', enableSuggestions, defaultValue: true));
  631     properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
  632     properties.add(IntProperty('minLines', minLines, defaultValue: null));
  633     properties.add(DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
  634     properties.add(IntProperty('maxLength', maxLength, defaultValue: null));
  635     properties.add(FlagProperty('maxLengthEnforced', value: maxLengthEnforced, ifTrue: 'max length enforced'));
  636     properties.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0));
  637     properties.add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null));
  638     properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius, defaultValue: null));
  639     properties.add(createCupertinoColorProperty('cursorColor', cursorColor, defaultValue: null));
  640     properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
  641     properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
  642     properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
  643     properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
  644     properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
  645   }
  646 }
  647 
  648 class _CupertinoTextFieldState extends State<CupertinoTextField> with RestorationMixin, AutomaticKeepAliveClientMixin implements TextSelectionGestureDetectorBuilderDelegate {
  649   final GlobalKey _clearGlobalKey = GlobalKey();
  650 
  651   RestorableTextEditingController _controller;
  652   TextEditingController get _effectiveController => widget.controller ?? _controller.value;
  653 
  654   FocusNode _focusNode;
  655   FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode());
  656 
  657   bool _showSelectionHandles = false;
  658 
  659   _CupertinoTextFieldSelectionGestureDetectorBuilder _selectionGestureDetectorBuilder;
  660 
  661   // API for TextSelectionGestureDetectorBuilderDelegate.
  662   @override
  663   bool get forcePressEnabled => true;
  664 
  665   @override
  666   final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
  667 
  668   @override
  669   bool get selectionEnabled => widget.selectionEnabled;
  670   // End of API for TextSelectionGestureDetectorBuilderDelegate.
  671 
  672   @override
  673   void initState() {
  674     super.initState();
  675     _selectionGestureDetectorBuilder = _CupertinoTextFieldSelectionGestureDetectorBuilder(state: this);
  676     if (widget.controller == null) {
  677       _createLocalController();
  678     }
  679   }
  680 
  681   @override
  682   void didUpdateWidget(CupertinoTextField oldWidget) {
  683     super.didUpdateWidget(oldWidget);
  684     if (widget.controller == null && oldWidget.controller != null) {
  685       _createLocalController(oldWidget.controller.value);
  686     } else if (widget.controller != null && oldWidget.controller == null) {
  687       unregisterFromRestoration(_controller);
  688       _controller.dispose();
  689       _controller = null;
  690     }
  691     final bool isEnabled = widget.enabled ?? true;
  692     final bool wasEnabled = oldWidget.enabled ?? true;
  693     if (wasEnabled && !isEnabled) {
  694       _effectiveFocusNode.unfocus();
  695     }
  696   }
  697 
  698   @override
  699   void restoreState(RestorationBucket oldBucket, bool initialRestore) {
  700     if (_controller != null) {
  701       _registerController();
  702     }
  703   }
  704 
  705   void _registerController() {
  706     assert(_controller != null);
  707     registerForRestoration(_controller, 'controller');
  708     _controller.value.addListener(updateKeepAlive);
  709   }
  710 
  711   void _createLocalController([TextEditingValue value]) {
  712     assert(_controller == null);
  713     _controller = value == null
  714         ? RestorableTextEditingController()
  715         : RestorableTextEditingController.fromValue(value);
  716     if (!restorePending) {
  717       _registerController();
  718     }
  719   }
  720 
  721   @override
  722   String get restorationId => widget.restorationId;
  723 
  724   @override
  725   void dispose() {
  726     _focusNode?.dispose();
  727     _controller?.dispose();
  728     super.dispose();
  729   }
  730 
  731   EditableTextState get _editableText => editableTextKey.currentState;
  732 
  733   void _requestKeyboard() {
  734     _editableText?.requestKeyboard();
  735   }
  736 
  737   bool _shouldShowSelectionHandles(SelectionChangedCause cause) {
  738     // When the text field is activated by something that doesn't trigger the
  739     // selection overlay, we shouldn't show the handles either.
  740     if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar)
  741       return false;
  742 
  743     // On iOS, we don't show handles when the selection is collapsed.
  744     if (_effectiveController.selection.isCollapsed)
  745       return false;
  746 
  747     if (cause == SelectionChangedCause.keyboard)
  748       return false;
  749 
  750     if (_effectiveController.text.isNotEmpty)
  751       return true;
  752 
  753     return false;
  754   }
  755 
  756   void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
  757     if (cause == SelectionChangedCause.longPress) {
  758       _editableText?.bringIntoView(selection.base);
  759     }
  760     final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
  761     if (willShowSelectionHandles != _showSelectionHandles) {
  762       setState(() {
  763         _showSelectionHandles = willShowSelectionHandles;
  764       });
  765     }
  766   }
  767 
  768   @override
  769   bool get wantKeepAlive => _controller?.value?.text?.isNotEmpty == true;
  770 
  771   bool _shouldShowAttachment({
  772     OverlayVisibilityMode attachment,
  773     bool hasText,
  774   }) {
  775     switch (attachment) {
  776       case OverlayVisibilityMode.never:
  777         return false;
  778       case OverlayVisibilityMode.always:
  779         return true;
  780       case OverlayVisibilityMode.editing:
  781         return hasText;
  782       case OverlayVisibilityMode.notEditing:
  783         return !hasText;
  784     }
  785     assert(false);
  786     return null;
  787   }
  788 
  789   bool _showPrefixWidget(TextEditingValue text) {
  790     return widget.prefix != null && _shouldShowAttachment(
  791       attachment: widget.prefixMode,
  792       hasText: text.text.isNotEmpty,
  793     );
  794   }
  795 
  796   bool _showSuffixWidget(TextEditingValue text) {
  797     return widget.suffix != null && _shouldShowAttachment(
  798       attachment: widget.suffixMode,
  799       hasText: text.text.isNotEmpty,
  800     );
  801   }
  802 
  803   bool _showClearButton(TextEditingValue text) {
  804     return _shouldShowAttachment(
  805       attachment: widget.clearButtonMode,
  806       hasText: text.text.isNotEmpty,
  807     );
  808   }
  809 
  810   // True if any surrounding decoration widgets will be shown.
  811   bool get _hasDecoration {
  812     return widget.placeholder != null ||
  813       widget.clearButtonMode != OverlayVisibilityMode.never ||
  814       widget.prefix != null ||
  815       widget.suffix != null;
  816   }
  817 
  818   // Provide default behavior if widget.textAlignVertical is not set.
  819   // CupertinoTextField has top alignment by default, unless it has decoration
  820   // like a prefix or suffix, in which case it's aligned to the center.
  821   TextAlignVertical get _textAlignVertical {
  822     if (widget.textAlignVertical != null) {
  823       return widget.textAlignVertical;
  824     }
  825     return _hasDecoration ? TextAlignVertical.center : TextAlignVertical.top;
  826   }
  827 
  828   Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) {
  829     assert(editableText != null);
  830     assert(textStyle != null);
  831     assert(placeholderStyle != null);
  832     // If there are no surrounding widgets, just return the core editable text
  833     // part.
  834     if (!_hasDecoration) {
  835       return editableText;
  836     }
  837 
  838     // Otherwise, listen to the current state of the text entry.
  839     return ValueListenableBuilder<TextEditingValue>(
  840       valueListenable: _effectiveController,
  841       child: editableText,
  842       builder: (BuildContext context, TextEditingValue text, Widget child) {
  843         return Row(children: <Widget>[
  844           // Insert a prefix at the front if the prefix visibility mode matches
  845           // the current text state.
  846           if (_showPrefixWidget(text)) widget.prefix,
  847           // In the middle part, stack the placeholder on top of the main EditableText
  848           // if needed.
  849           Expanded(
  850             child: Stack(
  851               children: <Widget>[
  852                 if (widget.placeholder != null && text.text.isEmpty)
  853                   SizedBox(
  854                     width: double.infinity,
  855                     child: Padding(
  856                       padding: widget.padding,
  857                       child: Text(
  858                         widget.placeholder,
  859                         maxLines: widget.maxLines,
  860                         overflow: TextOverflow.ellipsis,
  861                         style: placeholderStyle,
  862                         textAlign: widget.textAlign,
  863                       ),
  864                     ),
  865                   ),
  866                 child,
  867               ],
  868             ),
  869           ),
  870           // First add the explicit suffix if the suffix visibility mode matches.
  871           if (_showSuffixWidget(text))
  872             widget.suffix
  873           // Otherwise, try to show a clear button if its visibility mode matches.
  874           else if (_showClearButton(text))
  875             GestureDetector(
  876               key: _clearGlobalKey,
  877               onTap: widget.enabled ?? true ? () {
  878                 // Special handle onChanged for ClearButton
  879                 // Also call onChanged when the clear button is tapped.
  880                 final bool textChanged = _effectiveController.text.isNotEmpty;
  881                 _effectiveController.clear();
  882                 if (widget.onChanged != null && textChanged)
  883                   widget.onChanged(_effectiveController.text);
  884               } : null,
  885               child: Padding(
  886                 padding: const EdgeInsets.symmetric(horizontal: 6.0),
  887                 child: Icon(
  888                   CupertinoIcons.clear_thick_circled,
  889                   size: 18.0,
  890                   color: CupertinoDynamicColor.resolve(_kClearButtonColor, context),
  891                 ),
  892               ),
  893             ),
  894         ]);
  895       },
  896     );
  897   }
  898 
  899   @override
  900   Widget build(BuildContext context) {
  901     super.build(context); // See AutomaticKeepAliveClientMixin.
  902     assert(debugCheckHasDirectionality(context));
  903     final TextEditingController controller = _effectiveController;
  904     final List<TextInputFormatter> formatters = widget.inputFormatters ?? <TextInputFormatter>[];
  905     final bool enabled = widget.enabled ?? true;
  906     final Offset cursorOffset = Offset(_iOSHorizontalCursorOffsetPixels / MediaQuery.of(context).devicePixelRatio, 0);
  907     if (widget.maxLength != null && widget.maxLengthEnforced) {
  908       formatters.add(LengthLimitingTextInputFormatter(widget.maxLength));
  909     }
  910     final CupertinoThemeData themeData = CupertinoTheme.of(context);
  911 
  912     final TextStyle resolvedStyle = widget.style?.copyWith(
  913       color: CupertinoDynamicColor.resolve(widget.style?.color, context),
  914       backgroundColor: CupertinoDynamicColor.resolve(widget.style?.backgroundColor, context),
  915     );
  916 
  917     final TextStyle textStyle = themeData.textTheme.textStyle.merge(resolvedStyle);
  918 
  919     final TextStyle resolvedPlaceholderStyle = widget.placeholderStyle?.copyWith(
  920       color: CupertinoDynamicColor.resolve(widget.placeholderStyle?.color, context),
  921       backgroundColor: CupertinoDynamicColor.resolve(widget.placeholderStyle?.backgroundColor, context),
  922     );
  923 
  924     final TextStyle placeholderStyle = textStyle.merge(resolvedPlaceholderStyle);
  925 
  926     final Brightness keyboardAppearance = widget.keyboardAppearance ?? CupertinoTheme.brightnessOf(context);
  927     final Color cursorColor = CupertinoDynamicColor.resolve(widget.cursorColor, context) ?? themeData.primaryColor;
  928     final Color disabledColor = CupertinoDynamicColor.resolve(_kDisabledBackground, context);
  929 
  930     final Color decorationColor = CupertinoDynamicColor.resolve(widget.decoration?.color, context);
  931 
  932     final BoxBorder border = widget.decoration?.border;
  933     Border resolvedBorder = border as Border;
  934     if (border is Border) {
  935       BorderSide resolveBorderSide(BorderSide side) {
  936         return side == BorderSide.none
  937           ? side
  938           : side.copyWith(color: CupertinoDynamicColor.resolve(side.color, context));
  939       }
  940       resolvedBorder = border == null || border.runtimeType != Border
  941         ? border
  942         : Border(
  943           top: resolveBorderSide(border.top),
  944           left: resolveBorderSide(border.left),
  945           bottom: resolveBorderSide(border.bottom),
  946           right: resolveBorderSide(border.right),
  947         );
  948     }
  949 
  950     final BoxDecoration effectiveDecoration = widget.decoration?.copyWith(
  951       border: resolvedBorder,
  952       color: enabled ? decorationColor : (decorationColor ?? disabledColor),
  953     );
  954 
  955     final Color selectionColor = CupertinoTheme.of(context).primaryColor.withOpacity(0.2);
  956 
  957     final Widget paddedEditable = Padding(
  958       padding: widget.padding,
  959       child: RepaintBoundary(
  960         child: UnmanagedRestorationScope(
  961           bucket: bucket,
  962           child: EditableText(
  963             key: editableTextKey,
  964             controller: controller,
  965             readOnly: widget.readOnly,
  966             toolbarOptions: widget.toolbarOptions,
  967             showCursor: widget.showCursor,
  968             showSelectionHandles: _showSelectionHandles,
  969             focusNode: _effectiveFocusNode,
  970             keyboardType: widget.keyboardType,
  971             textInputAction: widget.textInputAction,
  972             textCapitalization: widget.textCapitalization,
  973             style: textStyle,
  974             strutStyle: widget.strutStyle,
  975             textAlign: widget.textAlign,
  976             autofocus: widget.autofocus,
  977             obscuringCharacter: widget.obscuringCharacter,
  978             obscureText: widget.obscureText,
  979             autocorrect: widget.autocorrect,
  980             smartDashesType: widget.smartDashesType,
  981             smartQuotesType: widget.smartQuotesType,
  982             enableSuggestions: widget.enableSuggestions,
  983             maxLines: widget.maxLines,
  984             minLines: widget.minLines,
  985             expands: widget.expands,
  986             selectionColor: selectionColor,
  987             selectionControls: widget.selectionEnabled
  988               ? cupertinoTextSelectionControls : null,
  989             onChanged: widget.onChanged,
  990             onSelectionChanged: _handleSelectionChanged,
  991             onEditingComplete: widget.onEditingComplete,
  992             onSubmitted: widget.onSubmitted,
  993             inputFormatters: formatters,
  994             rendererIgnoresPointer: true,
  995             cursorWidth: widget.cursorWidth,
  996             cursorHeight: widget.cursorHeight,
  997             cursorRadius: widget.cursorRadius,
  998             cursorColor: cursorColor,
  999             cursorOpacityAnimates: true,
 1000             cursorOffset: cursorOffset,
 1001             paintCursorAboveText: true,
 1002             autocorrectionTextRectColor: selectionColor,
 1003             backgroundCursorColor: CupertinoDynamicColor.resolve(CupertinoColors.inactiveGray, context),
 1004             selectionHeightStyle: widget.selectionHeightStyle,
 1005             selectionWidthStyle: widget.selectionWidthStyle,
 1006             scrollPadding: widget.scrollPadding,
 1007             keyboardAppearance: keyboardAppearance,
 1008             dragStartBehavior: widget.dragStartBehavior,
 1009             scrollController: widget.scrollController,
 1010             scrollPhysics: widget.scrollPhysics,
 1011             enableInteractiveSelection: widget.enableInteractiveSelection,
 1012             autofillHints: widget.autofillHints,
 1013             restorationId: 'editable',
 1014           ),
 1015         ),
 1016       ),
 1017     );
 1018 
 1019     return Semantics(
 1020       enabled: enabled,
 1021       onTap: !enabled ? null : () {
 1022         if (!controller.selection.isValid) {
 1023           controller.selection = TextSelection.collapsed(offset: controller.text.length);
 1024         }
 1025         _requestKeyboard();
 1026       },
 1027       child: IgnorePointer(
 1028         ignoring: !enabled,
 1029         child: Container(
 1030           decoration: effectiveDecoration,
 1031           child: _selectionGestureDetectorBuilder.buildGestureDetector(
 1032             behavior: HitTestBehavior.translucent,
 1033             child: Align(
 1034               alignment: Alignment(-1.0, _textAlignVertical.y),
 1035               widthFactor: 1.0,
 1036               heightFactor: 1.0,
 1037               child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle),
 1038             ),
 1039           ),
 1040         ),
 1041       ),
 1042     );
 1043   }
 1044 }