LCOV - code coverage report
Current view: top level - editing - mongol_text_field.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 326 364 89.6 %
Date: 2021-08-02 17:55:49 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2014 The Flutter Authors.
       2             : // Copyright 2021 Suragch.
       3             : // All rights reserved.
       4             : // Use of this source code is governed by a BSD-style license that can be
       5             : // found in the LICENSE file.
       6             : 
       7             : import 'package:flutter/cupertino.dart' show CupertinoTheme;
       8             : import 'package:flutter/foundation.dart';
       9             : import 'package:flutter/gestures.dart';
      10             : import 'package:flutter/rendering.dart';
      11             : import 'package:flutter/services.dart';
      12             : import 'package:flutter/widgets.dart' hide EditableTextState;
      13             : import 'package:flutter/material.dart'
      14             :     show
      15             :         InputCounterWidgetBuilder,
      16             :         Theme,
      17             :         Feedback,
      18             :         InputDecoration,
      19             :         MaterialLocalizations,
      20             :         ThemeData,
      21             :         debugCheckHasMaterial,
      22             :         debugCheckHasMaterialLocalizations,
      23             :         TextSelectionThemeData,
      24             :         TextSelectionTheme,
      25             :         iOSHorizontalOffset,
      26             :         MaterialStateProperty,
      27             :         MaterialStateMouseCursor,
      28             :         MaterialState;
      29             : import 'package:mongol/src/base/mongol_text_align.dart';
      30             : 
      31             : import 'alignment.dart';
      32             : import 'mongol_editable_text.dart';
      33             : import 'mongol_input_decorator.dart';
      34             : import 'text_selection/mongol_text_selection.dart';
      35             : import 'text_selection/mongol_text_selection_controls.dart';
      36             : 
      37             : class _TextFieldSelectionGestureDetectorBuilder
      38             :     extends MongolTextSelectionGestureDetectorBuilder {
      39           1 :   _TextFieldSelectionGestureDetectorBuilder({
      40             :     required _TextFieldState state,
      41             :   })  : _state = state,
      42           1 :         super(delegate: state);
      43             : 
      44             :   final _TextFieldState _state;
      45             : 
      46           0 :   @override
      47             :   void onForcePressStart(ForcePressDetails details) {
      48           0 :     super.onForcePressStart(details);
      49           0 :     if (delegate.selectionEnabled && shouldShowSelectionToolbar) {
      50           0 :       editableText.showToolbar();
      51             :     }
      52             :   }
      53             : 
      54           0 :   @override
      55             :   void onForcePressEnd(ForcePressDetails details) {
      56             :     // Not required.
      57             :   }
      58             : 
      59           1 :   @override
      60             :   void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
      61           2 :     if (delegate.selectionEnabled) {
      62           4 :       switch (Theme.of(_state.context).platform) {
      63           1 :         case TargetPlatform.iOS:
      64           1 :         case TargetPlatform.macOS:
      65           0 :           renderEditable.selectPositionAt(
      66           0 :             from: details.globalPosition,
      67             :             cause: SelectionChangedCause.longPress,
      68             :           );
      69             :           break;
      70           1 :         case TargetPlatform.android:
      71           0 :         case TargetPlatform.fuchsia:
      72           0 :         case TargetPlatform.linux:
      73           0 :         case TargetPlatform.windows:
      74           2 :           renderEditable.selectWordsInRange(
      75           3 :             from: details.globalPosition - details.offsetFromOrigin,
      76           1 :             to: details.globalPosition,
      77             :             cause: SelectionChangedCause.longPress,
      78             :           );
      79             :           break;
      80             :       }
      81             :     }
      82             :   }
      83             : 
      84           1 :   @override
      85             :   void onSingleTapUp(TapUpDetails details) {
      86           2 :     editableText.hideToolbar();
      87           2 :     if (delegate.selectionEnabled) {
      88           4 :       switch (Theme.of(_state.context).platform) {
      89           1 :         case TargetPlatform.iOS:
      90           1 :         case TargetPlatform.macOS:
      91             :         // Disabling this because of issue #12
      92             :         // https://github.com/suragch/mongol/issues/12
      93             :         // switch (details.kind) {
      94             :         //   case PointerDeviceKind.mouse:
      95             :         //   case PointerDeviceKind.stylus:
      96             :         //   case PointerDeviceKind.invertedStylus:
      97             :         //     // Precise devices should place the cursor at a precise position.
      98             :         //     renderEditable.selectPosition(cause: SelectionChangedCause.tap);
      99             :         //     break;
     100             :         //   case PointerDeviceKind.touch:
     101             :         //   case PointerDeviceKind.unknown:
     102             :         //     // On macOS/iOS/iPadOS a touch tap places the cursor at the edge
     103             :         //     // of the word.
     104             :         //     renderEditable.selectWordEdge(cause: SelectionChangedCause.tap);
     105             :         //     break;
     106             :         // }
     107             :         // break;
     108           1 :         case TargetPlatform.android:
     109           1 :         case TargetPlatform.fuchsia:
     110           1 :         case TargetPlatform.linux:
     111           1 :         case TargetPlatform.windows:
     112           2 :           renderEditable.selectPosition(cause: SelectionChangedCause.tap);
     113             :           break;
     114             :       }
     115             :     }
     116           2 :     _state._requestKeyboard();
     117           3 :     if (_state.widget.onTap != null) {
     118           4 :       _state.widget.onTap!();
     119             :     }
     120             :   }
     121             : 
     122           1 :   @override
     123             :   void onSingleLongTapStart(LongPressStartDetails details) {
     124           2 :     if (delegate.selectionEnabled) {
     125           4 :       switch (Theme.of(_state.context).platform) {
     126           1 :         case TargetPlatform.iOS:
     127           1 :         case TargetPlatform.macOS:
     128           0 :           renderEditable.selectPositionAt(
     129           0 :             from: details.globalPosition,
     130             :             cause: SelectionChangedCause.longPress,
     131             :           );
     132             :           break;
     133           1 :         case TargetPlatform.android:
     134           0 :         case TargetPlatform.fuchsia:
     135           0 :         case TargetPlatform.linux:
     136           0 :         case TargetPlatform.windows:
     137           2 :           renderEditable.selectWord(cause: SelectionChangedCause.longPress);
     138           3 :           Feedback.forLongPress(_state.context);
     139             :           break;
     140             :       }
     141             :     }
     142             :   }
     143             : }
     144             : 
     145             : /// A material design text field for vertical Mongolian script.
     146             : ///
     147             : /// A text field lets the user enter text, either with hardware keyboard or with
     148             : /// an onscreen keyboard.
     149             : ///
     150             : /// The text field calls the [onChanged] callback whenever the user changes the
     151             : /// text in the field. If the user indicates that they are done typing in the
     152             : /// field (e.g., by pressing a button on the soft keyboard), the text field
     153             : /// calls the [onSubmitted] callback.
     154             : ///
     155             : /// To control the text that is displayed in the text field, use the
     156             : /// [controller]. For example, to set the initial value of the text field, use
     157             : /// a [controller] that already contains some text. The [controller] can also
     158             : /// control the selection and composing region (and to observe changes to the
     159             : /// text, selection, and composing region).
     160             : ///
     161             : /// By default, a text field has a [decoration] that draws a divider to the
     162             : /// right of the text field. You can use the [decoration] property to control
     163             : /// the decoration, for example by adding a label or an icon. If you set the
     164             : /// [decoration] property to null, the decoration will be removed entirely,
     165             : /// including the extra padding introduced by the decoration to save space for
     166             : /// the labels.
     167             : ///
     168             : /// If [decoration] is non-null (which is the default), the text field requires
     169             : /// one of its ancestors to be a [Material] widget.
     170             : ///
     171             : /// To integrate the [MongolTextField] into a [Form] with other [FormField] widgets,
     172             : /// consider using [MongolTextFormField].
     173             : ///
     174             : /// Remember to call [TextEditingController.dispose] of the [TextEditingController]
     175             : /// when it is no longer needed. This will ensure we discard any resources used
     176             : /// by the object.
     177             : ///
     178             : /// {@tool snippet}
     179             : /// This example shows how to create a [MongolTextField] that will obscure input. The
     180             : /// [InputDecoration] surrounds the field in a border using [OutlineInputBorder]
     181             : /// and adds a label.
     182             : ///
     183             : /// ![](https://flutter.github.io/assets-for-api-docs/assets/material/text_field.png)
     184             : ///
     185             : /// ```dart
     186             : /// MongolTextField(
     187             : ///   obscureText: true,
     188             : ///   decoration: InputDecoration(
     189             : ///     border: OutlineInputBorder(),
     190             : ///     labelText: 'Password',
     191             : ///   ),
     192             : /// )
     193             : /// ```
     194             : /// {@end-tool}
     195             : ///
     196             : /// ## Reading values
     197             : ///
     198             : /// A common way to read a value from a MongolTextField is to use the [onSubmitted]
     199             : /// callback. This callback is applied to the text field's current value when
     200             : /// the user finishes editing.
     201             : ///
     202             : /// {@tool dartpad --template=stateful_widget_material}
     203             : ///
     204             : /// This sample shows how to get a value from a MongolTextField via the [onSubmitted]
     205             : /// callback.
     206             : ///
     207             : /// ```dart
     208             : /// // TODO: update the example with Mongol alert and Mongol button
     209             : ///
     210             : /// late TextEditingController _controller;
     211             : ///
     212             : /// void initState() {
     213             : ///   super.initState();
     214             : ///   _controller = TextEditingController();
     215             : /// }
     216             : ///
     217             : /// void dispose() {
     218             : ///   _controller.dispose();
     219             : ///   super.dispose();
     220             : /// }
     221             : ///
     222             : /// Widget build(BuildContext context) {
     223             : ///   return Scaffold(
     224             : ///     body: Center(
     225             : ///       child: MongolTextField(
     226             : ///         controller: _controller,
     227             : ///         onSubmitted: (String value) async {
     228             : ///           await showDialog<void>(
     229             : ///             context: context,
     230             : ///             builder: (BuildContext context) {
     231             : ///               return AlertDialog(
     232             : ///                 title: const Text('Thanks!'),
     233             : ///                 content: Text ('You typed "$value", which has length ${value.characters.length}.'),
     234             : ///                 actions: <Widget>[
     235             : ///                   TextButton(
     236             : ///                     onPressed: () { Navigator.pop(context); },
     237             : ///                     child: const Text('OK'),
     238             : ///                   ),
     239             : ///                 ],
     240             : ///               );
     241             : ///             },
     242             : ///           );
     243             : ///         },
     244             : ///       ),
     245             : ///     ),
     246             : ///   );
     247             : /// }
     248             : /// ```
     249             : /// {@end-tool}
     250             : ///
     251             : /// For most applications the [onSubmitted] callback will be sufficient for
     252             : /// reacting to user input.
     253             : ///
     254             : /// The [onEditingComplete] callback also runs when the user finishes editing.
     255             : /// It's different from [onSubmitted] because it has a default value which
     256             : /// updates the text controller and yields the keyboard focus. Applications that
     257             : /// require different behavior can override the default [onEditingComplete]
     258             : /// callback.
     259             : ///
     260             : /// Keep in mind you can also always read the current string from a MongolTextField's
     261             : /// [TextEditingController] using [TextEditingController.text].
     262             : ///
     263             : /// ## Handling emojis and other complex characters
     264             : /// {@macro flutter.widgets.EditableText.onChanged}
     265             : ///
     266             : /// In the live Dartpad example above, try typing the emoji 👨‍👩‍👦
     267             : /// into the field and submitting. Because the example code measures the length
     268             : /// with `value.characters.length`, the emoji is correctly counted as a single
     269             : /// character.
     270             : ///
     271             : /// See also:
     272             : ///
     273             : ///  * [MongolTextFormField], which integrates with the [Form] widget.
     274             : ///  * [InputDecorator], which shows the labels and other visual elements that
     275             : ///    surround the actual text editing widget.
     276             : ///  * [MongolEditableText], which is the raw text editing control at the heart of a
     277             : ///    [MongolTextField]. The [MongolEditableText] widget is rarely used directly unless
     278             : ///    you are implementing an entirely different design language, such as
     279             : ///    Cupertino.
     280             : class MongolTextField extends StatefulWidget {
     281             :   /// Creates a Material Design text field for vertical Mongolian text.
     282             :   ///
     283             :   /// If [decoration] is non-null (which is the default), the text field requires
     284             :   /// one of its ancestors to be a [Material] widget.
     285             :   ///
     286             :   /// To remove the decoration entirely (including the extra padding introduced
     287             :   /// by the decoration to save space for the labels), set the [decoration] to
     288             :   /// null.
     289             :   ///
     290             :   /// The [maxLines] property can be set to null to remove the restriction on
     291             :   /// the number of lines. By default, it is one, meaning this is a single-line
     292             :   /// text field. [maxLines] must not be zero.
     293             :   ///
     294             :   /// The [maxLength] property is set to null by default, which means the
     295             :   /// number of characters allowed in the text field is not restricted. If
     296             :   /// [maxLength] is set a character counter will be displayed to the right of the
     297             :   /// field showing how many characters have been entered. If the value is
     298             :   /// set to a positive integer it will also display the maximum allowed
     299             :   /// number of characters to be entered.  If the value is set to
     300             :   /// [TextField.noMaxLength] then only the current length is displayed.
     301             :   ///
     302             :   /// After [maxLength] characters have been input, additional input
     303             :   /// is ignored, unless [maxLengthEnforcement] is set to
     304             :   /// [MaxLengthEnforcement.none].
     305             :   /// The text field enforces the length with a [LengthLimitingTextInputFormatter],
     306             :   /// which is evaluated after the supplied [inputFormatters], if any.
     307             :   /// The [maxLength] value must be either null or greater than zero.
     308             :   ///
     309             :   /// The text cursor is not shown if [showCursor] is false or if [showCursor]
     310             :   /// is null (the default) and [readOnly] is true.
     311             :   ///
     312             :   /// See also:
     313             :   ///
     314             :   ///  * [maxLength], which discusses the precise meaning of "number of
     315             :   ///    characters" and how it may differ from the intuitive meaning.
     316           2 :   const MongolTextField({
     317             :     Key? key,
     318             :     this.controller,
     319             :     this.focusNode,
     320             :     this.decoration = const InputDecoration(),
     321             :     TextInputType? keyboardType,
     322             :     this.textInputAction,
     323             :     this.style,
     324             :     this.textAlign = MongolTextAlign.top,
     325             :     this.textAlignHorizontal,
     326             :     this.readOnly = false,
     327             :     ToolbarOptions? toolbarOptions,
     328             :     this.showCursor,
     329             :     this.autofocus = false,
     330             :     this.obscuringCharacter = '•',
     331             :     this.obscureText = false,
     332             :     this.autocorrect = true,
     333             :     this.enableSuggestions = true,
     334             :     this.maxLines = 1,
     335             :     this.minLines,
     336             :     this.expands = false,
     337             :     this.maxLength,
     338             :     this.onChanged,
     339             :     this.onEditingComplete,
     340             :     this.onSubmitted,
     341             :     this.onAppPrivateCommand,
     342             :     this.inputFormatters,
     343             :     this.enabled,
     344             :     this.cursorHeight = 2.0,
     345             :     this.cursorWidth,
     346             :     this.cursorRadius,
     347             :     this.cursorColor,
     348             :     this.keyboardAppearance,
     349             :     this.scrollPadding = const EdgeInsets.all(20.0),
     350             :     this.dragStartBehavior = DragStartBehavior.start,
     351             :     this.enableInteractiveSelection = true,
     352             :     this.selectionControls,
     353             :     this.onTap,
     354             :     this.mouseCursor,
     355             :     this.buildCounter,
     356             :     this.scrollController,
     357             :     this.scrollPhysics,
     358             :     this.autofillHints,
     359             :     this.restorationId,
     360           2 :   })  : assert(obscuringCharacter.length == 1),
     361           1 :         assert(maxLines == null || maxLines > 0),
     362           0 :         assert(minLines == null || minLines > 0),
     363             :         assert(
     364           0 :           (maxLines == null) || (minLines == null) || (maxLines >= minLines),
     365             :           "minLines can't be greater than maxLines",
     366             :         ),
     367             :         assert(
     368           0 :           !expands || (maxLines == null && minLines == null),
     369             :           'minLines and maxLines must be null when expands is true.',
     370             :         ),
     371           1 :         assert(!obscureText || maxLines == 1,
     372             :             'Obscured fields cannot be multiline.'),
     373           0 :         assert(maxLength == null ||
     374           1 :             maxLength == MongolTextField.noMaxLength ||
     375           1 :             maxLength > 0),
     376             :         // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set.
     377             :         assert(
     378           0 :             !identical(textInputAction, TextInputAction.newline) ||
     379           0 :                 maxLines == 1 ||
     380             :                 !identical(keyboardType, TextInputType.text),
     381             :             'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.'),
     382             :         keyboardType = keyboardType ??
     383           1 :             (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
     384             :         toolbarOptions = toolbarOptions ??
     385             :             (obscureText
     386             :                 ? const ToolbarOptions(
     387             :                     selectAll: true,
     388             :                     paste: true,
     389             :                   )
     390             :                 : const ToolbarOptions(
     391             :                     copy: true,
     392             :                     cut: true,
     393             :                     selectAll: true,
     394             :                     paste: true,
     395             :                   )),
     396           1 :         super(key: key);
     397             : 
     398             :   /// Controls the text being edited.
     399             :   ///
     400             :   /// If null, this widget will create its own [TextEditingController].
     401             :   final TextEditingController? controller;
     402             : 
     403             :   /// Defines the keyboard focus for this widget.
     404             :   ///
     405             :   /// The [focusNode] is a long-lived object that's typically managed by a
     406             :   /// [StatefulWidget] parent. See [FocusNode] for more information.
     407             :   ///
     408             :   /// To give the keyboard focus to this widget, provide a [focusNode] and then
     409             :   /// use the current [FocusScope] to request the focus:
     410             :   ///
     411             :   /// ```dart
     412             :   /// FocusScope.of(context).requestFocus(myFocusNode);
     413             :   /// ```
     414             :   ///
     415             :   /// This happens automatically when the widget is tapped.
     416             :   ///
     417             :   /// To be notified when the widget gains or loses the focus, add a listener
     418             :   /// to the [focusNode]:
     419             :   ///
     420             :   /// ```dart
     421             :   /// focusNode.addListener(() { print(myFocusNode.hasFocus); });
     422             :   /// ```
     423             :   ///
     424             :   /// If null, this widget will create its own [FocusNode].
     425             :   ///
     426             :   /// ## Keyboard
     427             :   ///
     428             :   /// Requesting the focus will typically cause the keyboard to be shown
     429             :   /// if it's not showing already.
     430             :   ///
     431             :   /// On Android, the user can hide the keyboard - without changing the focus -
     432             :   /// with the system back button. They can restore the keyboard's visibility
     433             :   /// by tapping on a text field.  The user might hide the keyboard and
     434             :   /// switch to a physical keyboard, or they might just need to get it
     435             :   /// out of the way for a moment, to expose something it's
     436             :   /// obscuring. In this case requesting the focus again will not
     437             :   /// cause the focus to change, and will not make the keyboard visible.
     438             :   ///
     439             :   /// This widget builds a [MongolEditableText] and will ensure that the keyboard is
     440             :   /// showing when it is tapped by calling [MongolEditableTextState.requestKeyboard()].
     441             :   final FocusNode? focusNode;
     442             : 
     443             :   /// The decoration to show around the text field.
     444             :   ///
     445             :   /// By default, draws a vertical line to the right of the text field but can be
     446             :   /// configured to show an icon, label, hint text, and error text.
     447             :   ///
     448             :   /// Specify null to remove the decoration entirely (including the
     449             :   /// extra padding introduced by the decoration to save space for the labels).
     450             :   final InputDecoration? decoration;
     451             : 
     452             :   /// The type of keyboard to use for editing the text.
     453             :   ///
     454             :   /// Defaults to [TextInputType.text] if [maxLines] is one and
     455             :   /// [TextInputType.multiline] otherwise.
     456             :   final TextInputType keyboardType;
     457             : 
     458             :   /// The type of action button to use for the keyboard.
     459             :   ///
     460             :   /// Defaults to [TextInputAction.newline] if [keyboardType] is
     461             :   /// [TextInputType.multiline] and [TextInputAction.done] otherwise.
     462             :   final TextInputAction? textInputAction;
     463             : 
     464             :   /// The style to use for the text being edited.
     465             :   ///
     466             :   /// This text style is also used as the base style for the [decoration].
     467             :   ///
     468             :   /// If null, defaults to the `subtitle1` text style from the current [Theme].
     469             :   final TextStyle? style;
     470             : 
     471             :   /// How the text should be aligned vertically.
     472             :   ///
     473             :   /// Defaults to [MongolTextAlign.top].
     474             :   final MongolTextAlign textAlign;
     475             : 
     476             :   /// The horizontal alignment of verical Mongolian text within an input box.
     477             :   final TextAlignHorizontal? textAlignHorizontal;
     478             : 
     479             :   /// Whether this text field should focus itself if nothing else is already
     480             :   /// focused.
     481             :   ///
     482             :   /// If true, the keyboard will open as soon as this text field obtains focus.
     483             :   /// Otherwise, the keyboard is only shown after the user taps the text field.
     484             :   ///
     485             :   /// Defaults to false.
     486             :   final bool autofocus;
     487             : 
     488             :   /// Character used for obscuring text if [obscureText] is true.
     489             :   ///
     490             :   /// Must be only a single character.
     491             :   ///
     492             :   /// Defaults to the character U+2022 BULLET (•).
     493             :   final String obscuringCharacter;
     494             : 
     495             :   /// Whether to hide the text being edited (e.g., for passwords).
     496             :   ///
     497             :   /// When this is set to true, all the characters in the text field are
     498             :   /// replaced by [obscuringCharacter].
     499             :   ///
     500             :   /// Defaults to false.
     501             :   final bool obscureText;
     502             : 
     503             :   /// Whether to enable autocorrection.
     504             :   ///
     505             :   /// Defaults to true.
     506             :   final bool autocorrect;
     507             : 
     508             :   /// Whether to show input suggestions as the user types.
     509             :   ///
     510             :   /// This flag only affects Android. On iOS, suggestions are tied directly to
     511             :   /// [autocorrect], so that suggestions are only shown when [autocorrect] is
     512             :   /// true. On Android autocorrection and suggestion are controlled separately.
     513             :   ///
     514             :   /// Defaults to true.
     515             :   ///
     516             :   /// See also:
     517             :   ///
     518             :   ///  * <https://developer.android.com/reference/android/text/InputType.html#TYPE_TEXT_FLAG_NO_SUGGESTIONS>
     519             :   final bool enableSuggestions;
     520             : 
     521             :   /// The maximum number of lines for the text to span, wrapping if necessary.
     522             :   ///
     523             :   /// If this is 1 (the default), the text will not wrap, but will scroll
     524             :   /// vertically instead.
     525             :   ///
     526             :   /// If this is null, there is no limit to the number of lines, and the text
     527             :   /// container will start with enough horizontal space for one line and
     528             :   /// automatically grow to accommodate additional lines as they are entered.
     529             :   ///
     530             :   /// If this is not null, the value must be greater than zero, and it will lock
     531             :   /// the input to the given number of lines and take up enough vertical space
     532             :   /// to accommodate that number of lines. Setting [minLines] as well allows the
     533             :   /// input to grow between the indicated range.
     534             :   ///
     535             :   /// The full set of behaviors possible with [minLines] and [maxLines] are as
     536             :   /// follows. These examples apply equally to `MongolTextField`,
     537             :   /// `MongolTextFormField`, and `MongolEditableText`.
     538             :   ///
     539             :   /// Input that occupies a single line and scrolls vertically as needed.
     540             :   /// ```dart
     541             :   /// MongolTextField()
     542             :   /// ```
     543             :   ///
     544             :   /// Input whose width grows from one line up to as many lines as needed for
     545             :   /// the text that was entered. If a width limit is imposed by its parent, it
     546             :   /// will scroll horizontally when its width reaches that limit.
     547             :   /// ```dart
     548             :   /// MongolTextField(maxLines: null)
     549             :   /// ```
     550             :   ///
     551             :   /// The input's width is large enough for the given number of lines. If
     552             :   /// additional lines are entered the input scrolls horizontally.
     553             :   /// ```dart
     554             :   /// MongolTextField(maxLines: 2)
     555             :   /// ```
     556             :   ///
     557             :   /// Input whose width grows with content between a min and max. An infinite
     558             :   /// max is possible with `maxLines: null`.
     559             :   /// ```dart
     560             :   /// MongolTextField(minLines: 2, maxLines: 4)
     561             :   /// ```
     562             :   final int? maxLines;
     563             : 
     564             :   /// The minimum number of lines to occupy when the content spans fewer lines.
     565             :   ///
     566             :   /// If this is null (default), text container starts with enough horizontal space
     567             :   /// for one line and grows to accommodate additional lines as they are entered.
     568             :   ///
     569             :   /// This can be used in combination with [maxLines] for a varying set of behaviors.
     570             :   ///
     571             :   /// If the value is set, it must be greater than zero. If the value is greater
     572             :   /// than 1, [maxLines] should also be set to either null or greater than
     573             :   /// this value.
     574             :   ///
     575             :   /// When [maxLines] is set as well, the width will grow between the indicated
     576             :   /// range of lines. When [maxLines] is null, it will grow as wide as needed,
     577             :   /// starting from [minLines].
     578             :   ///
     579             :   /// A few examples of behaviors possible with [minLines] and [maxLines] are as follows.
     580             :   /// These apply equally to `MongolTextField`, `MongolTextFormField`,
     581             :   /// and `MongolEditableText`.
     582             :   ///
     583             :   /// Input that always occupies at least 2 lines and has an infinite max.
     584             :   /// Expands horizontally as needed.
     585             :   /// ```dart
     586             :   /// MongolTextField(minLines: 2)
     587             :   /// ```
     588             :   ///
     589             :   /// Input whose width starts from 2 lines and grows up to 4 lines at which
     590             :   /// point the width limit is reached. If additional lines are entered it will
     591             :   /// scroll horizontally.
     592             :   /// ```dart
     593             :   /// MongolTextField(minLines:2, maxLines: 4)
     594             :   /// ```
     595             :   ///
     596             :   /// See the examples in [maxLines] for the complete picture of how [maxLines]
     597             :   /// and [minLines] interact to produce various behaviors.
     598             :   ///
     599             :   /// Defaults to null.
     600             :   final int? minLines;
     601             : 
     602             :   /// Whether this widget's width will be sized to fill its parent.
     603             :   ///
     604             :   /// If set to true and wrapped in a parent widget like [Expanded] or
     605             :   /// [SizedBox], the input will expand to fill the parent.
     606             :   ///
     607             :   /// [maxLines] and [minLines] must both be null when this is set to true,
     608             :   /// otherwise an error is thrown.
     609             :   ///
     610             :   /// Defaults to false.
     611             :   ///
     612             :   /// See the examples in [maxLines] for the complete picture of how [maxLines],
     613             :   /// [minLines], and [expands] interact to produce various behaviors.
     614             :   ///
     615             :   /// Input that matches the width of its parent:
     616             :   /// ```dart
     617             :   /// Expanded(
     618             :   ///   child: MongolTextField(maxLines: null, expands: true),
     619             :   /// )
     620             :   /// ```
     621             :   final bool expands;
     622             : 
     623             :   /// Whether the text can be changed.
     624             :   ///
     625             :   /// When this is set to true, the text cannot be modified
     626             :   /// by any shortcut or keyboard operation. The text is still selectable.
     627             :   ///
     628             :   /// Defaults to false.
     629             :   final bool readOnly;
     630             : 
     631             :   /// Configuration of toolbar options.
     632             :   ///
     633             :   /// If not set, select all and paste will default to be enabled. Copy and cut
     634             :   /// will be disabled if [obscureText] is true. If [readOnly] is true,
     635             :   /// paste and cut will be disabled regardless.
     636             :   final ToolbarOptions toolbarOptions;
     637             : 
     638             :   /// Whether to show cursor.
     639             :   ///
     640             :   /// The cursor refers to the blinking caret when the [MongolEditableText] is
     641             :   /// focused.
     642             :   ///
     643             :   /// See also:
     644             :   ///
     645             :   ///  * [showSelectionHandles], which controls the visibility of the selection
     646             :   ///    handles.
     647             :   final bool? showCursor;
     648             : 
     649             :   /// If [maxLength] is set to this value, only the "current input length"
     650             :   /// part of the character counter is shown.
     651             :   static const int noMaxLength = -1;
     652             : 
     653             :   /// The maximum number of characters (Unicode scalar values) to allow in the
     654             :   /// text field.
     655             :   ///
     656             :   /// TODO: add support for maxLengthEnforcement.
     657             :   ///
     658             :   /// If set, a character counter will be displayed to the right of the
     659             :   /// field showing how many characters have been entered. If set to a number
     660             :   /// greater than 0, it will also display the maximum number allowed. If set
     661             :   /// to [MongolTextField.noMaxLength] then only the current character count
     662             :   /// is displayed.
     663             :   ///
     664             :   /// After [maxLength] characters have been input, additional input
     665             :   /// is ignored, unless [maxLengthEnforcement] is set to
     666             :   /// [MaxLengthEnforcement.none].
     667             :   ///
     668             :   /// The text field enforces the length with a [LengthLimitingTextInputFormatter],
     669             :   /// which is evaluated after the supplied [inputFormatters], if any.
     670             :   ///
     671             :   /// This value must be either null, [MongolTextField.noMaxLength], or greater than 0.
     672             :   /// If null (the default) then there is no limit to the number of characters
     673             :   /// that can be entered. If set to [MongolTextField.noMaxLength], then no limit will
     674             :   /// be enforced, but the number of characters entered will still be displayed.
     675             :   ///
     676             :   /// Whitespace characters (e.g. newline, space, tab) are included in the
     677             :   /// character count.
     678             :   ///
     679             :   /// If [maxLengthEnforcement] is
     680             :   /// [MaxLengthEnforcement.none], then more than [maxLength]
     681             :   /// characters may be entered, but the error counter and divider will switch
     682             :   /// to the [decoration]'s [InputDecoration.errorStyle] when the limit is
     683             :   /// exceeded.
     684             :   ///
     685             :   /// ## Characters
     686             :   ///
     687             :   /// For a specific definition of what is considered a character, see the
     688             :   /// [characters](https://pub.dev/packages/characters) package on Pub, which is
     689             :   /// what Flutter uses to delineate characters. In general, even complex
     690             :   /// characters like surrogate pairs and extended grapheme clusters are
     691             :   /// correctly interpreted by Flutter as each being a single user-perceived
     692             :   /// character.
     693             :   ///
     694             :   /// For instance, the character "ö" can be represented as '\u{006F}\u{0308}',
     695             :   /// which is the letter "o" followed by a composed diaeresis "¨", or it can
     696             :   /// be represented as '\u{00F6}', which is the Unicode scalar value "LATIN
     697             :   /// SMALL LETTER O WITH DIAERESIS". It will be counted as a single character
     698             :   /// in both cases.
     699             :   ///
     700             :   /// Similarly, some emoji are represented by multiple scalar values. The
     701             :   /// Unicode "THUMBS UP SIGN + MEDIUM SKIN TONE MODIFIER", "👍🏽"is counted as
     702             :   /// a single character, even though it is a combination of two Unicode scalar
     703             :   /// values, '\u{1F44D}\u{1F3FD}'.
     704             :   final int? maxLength;
     705             : 
     706             :   /// Called when the user initiates a change to the MongolTextField's
     707             :   /// value: when they have inserted or deleted text.
     708             :   ///
     709             :   /// This callback doesn't run when the MongolTextField's text is changed
     710             :   /// programmatically, via the MongolTextField's [controller]. Typically it
     711             :   /// isn't necessary to be notified of such changes, since they're
     712             :   /// initiated by the app itself.
     713             :   ///
     714             :   /// To be notified of all changes to the MongolTextField's text, cursor,
     715             :   /// and selection, one can add a listener to its [controller] with
     716             :   /// [TextEditingController.addListener].
     717             :   ///
     718             :   /// {@tool dartpad --template=stateful_widget_material}
     719             :   ///
     720             :   /// This example shows how onChanged could be used to check the MongolTextField's
     721             :   /// current value each time the user inserts or deletes a character.
     722             :   ///
     723             :   /// ```dart
     724             :   /// // TODO: test this snippet to make sure it works
     725             :   ///
     726             :   /// final TextEditingController _controller = TextEditingController();
     727             :   ///
     728             :   /// void dispose() {
     729             :   ///   _controller.dispose();
     730             :   ///   super.dispose();
     731             :   /// }
     732             :   ///
     733             :   /// Widget build(BuildContext context) {
     734             :   ///   return Scaffold(
     735             :   ///     body: Row(
     736             :   ///       mainAxisAlignment: MainAxisAlignment.center,
     737             :   ///       children: <Widget>[
     738             :   ///         const MongolText('What number comes next in the sequence?'),
     739             :   ///         const MongolText('1, 1, 2, 3, 5, 8...?'),
     740             :   ///         MongolTextField(
     741             :   ///           controller: _controller,
     742             :   ///           onChanged: (String value) async {
     743             :   ///             if (value != '13') {
     744             :   ///               return;
     745             :   ///             }
     746             :   ///             await showDialog<void>(
     747             :   ///               context: context,
     748             :   ///               builder: (BuildContext context) {
     749             :   ///                 return MongolAlertDialog(
     750             :   ///                   title: const Text('That is correct!'),
     751             :   ///                   content: Text ('13 is the right answer.'),
     752             :   ///                   actions: <Widget>[
     753             :   ///                     MongolTextButton(
     754             :   ///                       onPressed: () { Navigator.pop(context); },
     755             :   ///                       child: const Text('OK'),
     756             :   ///                     ),
     757             :   ///                   ],
     758             :   ///                 );
     759             :   ///               },
     760             :   ///             );
     761             :   ///           },
     762             :   ///         ),
     763             :   ///       ],
     764             :   ///     ),
     765             :   ///   );
     766             :   /// }
     767             :   /// ```
     768             :   /// {@end-tool}
     769             :   ///
     770             :   /// ## Handling emojis and other complex characters
     771             :   ///
     772             :   /// It's important to always use
     773             :   /// [characters](https://pub.dev/packages/characters) when dealing with user
     774             :   /// input text that may contain complex characters. This will ensure that
     775             :   /// extended grapheme clusters and surrogate pairs are treated as single
     776             :   /// characters, as they appear to the user.
     777             :   ///
     778             :   /// For example, when finding the length of some user input, use
     779             :   /// `string.characters.length`. Do NOT use `string.length` or even
     780             :   /// `string.runes.length`. For the complex character "👨‍👩‍👦", this
     781             :   /// appears to the user as a single character, and `string.characters.length`
     782             :   /// intuitively returns 1. On the other hand, `string.length` returns 8, and
     783             :   /// `string.runes.length` returns 5!
     784             :   ///
     785             :   /// See also:
     786             :   ///
     787             :   ///  * [inputFormatters], which are called before [onChanged]
     788             :   ///    runs and can validate and change ("format") the input value.
     789             :   ///  * [onEditingComplete], [onSubmitted]:
     790             :   ///    which are more specialized input change notifications.
     791             :   final ValueChanged<String>? onChanged;
     792             : 
     793             :   /// Called when the user submits editable content (e.g., user presses the "done"
     794             :   /// button on the keyboard).
     795             :   ///
     796             :   /// The default implementation of [onEditingComplete] executes 2 different
     797             :   /// behaviors based on the situation:
     798             :   ///
     799             :   ///  - When a completion action is pressed, such as "done", "go", "send", or
     800             :   ///    "search", the user's content is submitted to the [controller] and then
     801             :   ///    focus is given up.
     802             :   ///
     803             :   ///  - When a non-completion action is pressed, such as "next" or "previous",
     804             :   ///    the user's content is submitted to the [controller], but focus is not
     805             :   ///    given up because developers may want to immediately move focus to
     806             :   ///    another input widget within [onSubmitted].
     807             :   ///
     808             :   /// Providing [onEditingComplete] prevents the aforementioned default behavior.
     809             :   final VoidCallback? onEditingComplete;
     810             : 
     811             :   /// Called when the user indicates that they are done editing the text in the
     812             :   /// field.
     813             :   ///
     814             :   /// See also:
     815             :   ///
     816             :   ///  * [TextInputAction.next] and [TextInputAction.previous], which
     817             :   ///    automatically shift the focus to the next/previous focusable item when
     818             :   ///    the user is done editing.
     819             :   final ValueChanged<String>? onSubmitted;
     820             : 
     821             :   /// This is used to receive a private command from the input method.
     822             :   ///
     823             :   /// Called when the result of [TextInputClient.performPrivateCommand] is
     824             :   /// received.
     825             :   ///
     826             :   /// This can be used to provide domain-specific features that are only known
     827             :   /// between certain input methods and their clients.
     828             :   ///
     829             :   /// See also:
     830             :   ///   * [https://developer.android.com/reference/android/view/inputmethod/InputConnection#performPrivateCommand(java.lang.String,%20android.os.Bundle)],
     831             :   ///     which is the Android documentation for performPrivateCommand, used to
     832             :   ///     send a command from the input method.
     833             :   ///   * [https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#sendAppPrivateCommand],
     834             :   ///     which is the Android documentation for sendAppPrivateCommand, used to
     835             :   ///     send a command to the input method.
     836             :   final AppPrivateCommandCallback? onAppPrivateCommand;
     837             : 
     838             :   /// Optional input validation and formatting overrides.
     839             :   ///
     840             :   /// Formatters are run in the provided order when the text input changes. When
     841             :   /// this parameter changes, the new formatters will not be applied until the
     842             :   /// next time the user inserts or deletes text.
     843             :   final List<TextInputFormatter>? inputFormatters;
     844             : 
     845             :   /// If false the text field is "disabled": it ignores taps and its
     846             :   /// [decoration] is rendered in grey.
     847             :   ///
     848             :   /// If non-null this property overrides the [decoration]'s
     849             :   /// [InputDecoration.enabled] property.
     850             :   final bool? enabled;
     851             : 
     852             :   /// How wide the cursor will be.
     853             :   ///
     854             :   /// If this property is null, [MongolRenderEditable.preferredLineWidth] will
     855             :   /// be used.
     856             :   final double? cursorWidth;
     857             : 
     858             :   /// How thick the cursor will be.
     859             :   ///
     860             :   /// Defaults to 2.0.
     861             :   ///
     862             :   /// The cursor will draw above the text. The cursor height will extend
     863             :   /// down from the boundary between characters. This corresponds to extending
     864             :   /// downstream relative to the selected position. Negative values may be used
     865             :   /// to reverse this behavior.
     866             :   final double cursorHeight;
     867             : 
     868             :   /// How rounded the corners of the cursor should be.
     869             :   ///
     870             :   /// By default, the cursor has no radius.
     871             :   final Radius? cursorRadius;
     872             : 
     873             :   /// The color of the cursor.
     874             :   ///
     875             :   /// The cursor indicates the current location of text insertion point in
     876             :   /// the field.
     877             :   ///
     878             :   /// If this is null it will default to the ambient
     879             :   /// [TextSelectionThemeData.cursorColor]. If that is null, and the
     880             :   /// [ThemeData.platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS]
     881             :   /// it will use [CupertinoThemeData.primaryColor]. Otherwise it will use
     882             :   /// the value of [ColorScheme.primary] of [ThemeData.colorScheme].
     883             :   final Color? cursorColor;
     884             : 
     885             :   /// The appearance of the keyboard.
     886             :   ///
     887             :   /// This setting is only honored on iOS devices.
     888             :   ///
     889             :   /// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness].
     890             :   final Brightness? keyboardAppearance;
     891             : 
     892             :   /// Configures padding to edges surrounding a [Scrollable] when the
     893             :   /// MongolTextField scrolls into view.
     894             :   ///
     895             :   /// When this widget receives focus and is not completely visible (for
     896             :   /// example scrolled partially off the screen or overlapped by the keyboard)
     897             :   /// then it will attempt to make itself visible by scrolling a surrounding
     898             :   /// [Scrollable], if one is present. This value controls how far from the
     899             :   /// edges of a [Scrollable] the MongolTextField will be positioned after the
     900             :   /// scroll.
     901             :   ///
     902             :   /// Defaults to EdgeInsets.all(20.0).
     903             :   final EdgeInsets scrollPadding;
     904             : 
     905             :   /// Whether to enable user interface affordances for changing the
     906             :   /// text selection.
     907             :   ///
     908             :   /// For example, setting this to true will enable features such as
     909             :   /// long-pressing the MongolTextField to select text and show the
     910             :   /// cut/copy/paste menu, and tapping to move the text caret.
     911             :   ///
     912             :   /// When this is false, the text selection cannot be adjusted by
     913             :   /// the user, text cannot be copied, and the user cannot paste into
     914             :   /// the text field from the clipboard.
     915             :   final bool enableInteractiveSelection;
     916             : 
     917             :   /// Optional delegate for building the text selection handles and toolbar.
     918             :   ///
     919             :   /// The [MongolEditableText] widget used on its own will not trigger the display
     920             :   /// of the selection toolbar by itself. The toolbar is shown by calling
     921             :   /// [EditableTextState.showToolbar] in response to an appropriate user event.
     922             :   ///
     923             :   /// See also:
     924             :   ///
     925             :   ///  * [MongolTextField], a Material Design themed wrapper of
     926             :   ///    [MongolEditableText], which shows the selection toolbar upon
     927             :   ///    appropriate user events based on the user's platform set in
     928             :   ///    [ThemeData.platform].
     929             :   ///
     930             :   final TextSelectionControls? selectionControls;
     931             : 
     932             :   /// Determines the way that drag start behavior is handled.
     933             :   ///
     934             :   /// If set to [DragStartBehavior.start], scrolling drag behavior will
     935             :   /// begin upon the detection of a drag gesture. If set to
     936             :   /// [DragStartBehavior.down] it will begin when a down event is first detected.
     937             :   ///
     938             :   /// In general, setting this to [DragStartBehavior.start] will make drag
     939             :   /// animation smoother and setting it to [DragStartBehavior.down] will make
     940             :   /// drag behavior feel slightly more reactive.
     941             :   ///
     942             :   /// By default, the drag start behavior is [DragStartBehavior.start].
     943             :   ///
     944             :   /// See also:
     945             :   ///
     946             :   ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for
     947             :   ///    the different behaviors.
     948             :   final DragStartBehavior dragStartBehavior;
     949             : 
     950             :   /// Same as [enableInteractiveSelection].
     951             :   ///
     952             :   /// This getter exists primarily for consistency with
     953             :   /// [MongolRenderEditable.selectionEnabled].
     954           2 :   bool get selectionEnabled => enableInteractiveSelection;
     955             : 
     956             :   /// Called for each distinct tap except for every second tap of a double tap.
     957             :   ///
     958             :   /// The text field builds a [GestureDetector] to handle input events like tap,
     959             :   /// to trigger focus requests, to move the caret, adjust the selection, etc.
     960             :   /// Handling some of those events by wrapping the text field with a competing
     961             :   /// GestureDetector is problematic.
     962             :   ///
     963             :   /// To unconditionally handle taps, without interfering with the text field's
     964             :   /// internal gesture detector, provide this callback.
     965             :   ///
     966             :   /// If the text field is created with [enabled] false, taps will not be
     967             :   /// recognized.
     968             :   ///
     969             :   /// To be notified when the text field gains or loses the focus, provide a
     970             :   /// [focusNode] and add a listener to that.
     971             :   ///
     972             :   /// To listen to arbitrary pointer events without competing with the
     973             :   /// text field's internal gesture detector, use a [Listener].
     974             :   final GestureTapCallback? onTap;
     975             : 
     976             :   /// The cursor for a mouse pointer when it enters or is hovering over the
     977             :   /// widget.
     978             :   ///
     979             :   /// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
     980             :   /// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
     981             :   ///
     982             :   ///  * [MaterialState.error].
     983             :   ///  * [MaterialState.hovered].
     984             :   ///  * [MaterialState.focused].
     985             :   ///  * [MaterialState.disabled].
     986             :   ///
     987             :   /// If this property is null, [MaterialStateMouseCursor.textable] will be used.
     988             :   ///
     989             :   /// The [mouseCursor] is the only property of [MongolTextField] that controls the
     990             :   /// appearance of the mouse pointer. All other properties related to "cursor"
     991             :   /// stand for the text cursor, which is usually a blinking horizontal line at
     992             :   /// the editing position.
     993             :   final MouseCursor? mouseCursor;
     994             : 
     995             :   /// Callback that generates a custom [InputDecoration.counter] widget.
     996             :   ///
     997             :   /// See [InputCounterWidgetBuilder] for an explanation of the passed in
     998             :   /// arguments.  The returned widget will be placed below the line in place of
     999             :   /// the default widget built when [InputDecoration.counterText] is specified.
    1000             :   ///
    1001             :   /// The returned widget will be wrapped in a [Semantics] widget for
    1002             :   /// accessibility, but it also needs to be accessible itself. For example,
    1003             :   /// if returning a MongolText widget, set the [MongolText.semanticsLabel] property.
    1004             :   ///
    1005             :   /// {@tool snippet}
    1006             :   /// ```dart
    1007             :   /// Widget counter(
    1008             :   ///   BuildContext context,
    1009             :   ///   {
    1010             :   ///     required int currentLength,
    1011             :   ///     required int? maxLength,
    1012             :   ///     required bool isFocused,
    1013             :   ///   }
    1014             :   /// ) {
    1015             :   ///   return MongolText(
    1016             :   ///     '$currentLength of $maxLength characters',
    1017             :   ///     semanticsLabel: 'character count',
    1018             :   ///   );
    1019             :   /// }
    1020             :   /// ```
    1021             :   /// {@end-tool}
    1022             :   ///
    1023             :   /// If buildCounter returns null, then no counter and no Semantics widget will
    1024             :   /// be created at all.
    1025             :   final InputCounterWidgetBuilder? buildCounter;
    1026             : 
    1027             :   /// The [ScrollPhysics] to use when horizontally scrolling the input.
    1028             :   ///
    1029             :   /// If not specified, it will behave according to the current platform.
    1030             :   ///
    1031             :   /// See [Scrollable.physics].
    1032             :   final ScrollPhysics? scrollPhysics;
    1033             : 
    1034             :   /// The [ScrollController] to use when horizontally scrolling the input.
    1035             :   ///
    1036             :   /// If null, it will instantiate a new ScrollController.
    1037             :   ///
    1038             :   /// See [Scrollable.controller].
    1039             :   final ScrollController? scrollController;
    1040             : 
    1041             :   /// A list of strings that helps the autofill service identify the type of this
    1042             :   /// text input.
    1043             :   ///
    1044             :   /// When set to null or empty, this text input will not send its autofill
    1045             :   /// information to the platform, preventing it from participating in
    1046             :   /// autofills triggered by a different [AutofillClient], even if they're in the
    1047             :   /// same [AutofillScope]. Additionally, on Android and web, setting this to
    1048             :   /// null or empty will disable autofill for this text field.
    1049             :   ///
    1050             :   /// The minimum platform SDK version that supports Autofill is API level 26
    1051             :   /// for Android, and iOS 10.0 for iOS.
    1052             :   ///
    1053             :   /// ### Setting up iOS autofill:
    1054             :   ///
    1055             :   /// To provide the best user experience and ensure your app fully supports
    1056             :   /// password autofill on iOS, follow these steps:
    1057             :   ///
    1058             :   /// * Set up your iOS app's
    1059             :   ///   [associated domains](https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app).
    1060             :   /// * Some autofill hints only work with specific [keyboardType]s. For example,
    1061             :   ///   [AutofillHints.name] requires [TextInputType.name] and [AutofillHints.email]
    1062             :   ///   works only with [TextInputType.emailAddress]. Make sure the input field has a
    1063             :   ///   compatible [keyboardType]. Empirically, [TextInputType.name] works well
    1064             :   ///   with many autofill hints that are predefined on iOS.
    1065             :   ///
    1066             :   /// ### Troubleshooting Autofill
    1067             :   ///
    1068             :   /// Autofill service providers rely heavily on [autofillHints]. Make sure the
    1069             :   /// entries in [autofillHints] are supported by the autofill service currently
    1070             :   /// in use (the name of the service can typically be found in your mobile
    1071             :   /// device's system settings).
    1072             :   ///
    1073             :   /// #### Autofill UI refuses to show up when I tap on the text field
    1074             :   ///
    1075             :   /// Check the device's system settings and make sure autofill is turned on,
    1076             :   /// and there're available credentials stored in the autofill service.
    1077             :   ///
    1078             :   /// * iOS password autofill: Go to Settings -> Password, turn on "Autofill
    1079             :   ///   Passwords", and add new passwords for testing by pressing the top right
    1080             :   ///   "+" button. Use an arbitrary "website" if you don't have associated
    1081             :   ///   domains set up for your app. As long as there's at least one password
    1082             :   ///   stored, you should be able to see a key-shaped icon in the quick type
    1083             :   ///   bar on the software keyboard, when a password related field is focused.
    1084             :   ///
    1085             :   /// * iOS contact information autofill: iOS seems to pull contact info from
    1086             :   ///   the Apple ID currently associated with the device. Go to Settings ->
    1087             :   ///   Apple ID (usually the first entry, or "Sign in to your iPhone" if you
    1088             :   ///   haven't set up one on the device), and fill out the relevant fields. If
    1089             :   ///   you wish to test more contact info types, try adding them in Contacts ->
    1090             :   ///   My Card.
    1091             :   ///
    1092             :   /// * Android autofill: Go to Settings -> System -> Languages & input ->
    1093             :   ///   Autofill service. Enable the autofill service of your choice, and make
    1094             :   ///   sure there're available credentials associated with your app.
    1095             :   ///
    1096             :   /// #### I called `TextInput.finishAutofillContext` but the autofill save
    1097             :   /// prompt isn't showing
    1098             :   ///
    1099             :   /// * iOS: iOS may not show a prompt or any other visual indication when it
    1100             :   ///   saves user password. Go to Settings -> Password and check if your new
    1101             :   ///   password is saved. Neither saving password nor auto-generating strong
    1102             :   ///   password works without properly setting up associated domains in your
    1103             :   ///   app. To set up associated domains, follow the instructions in
    1104             :   ///   <https://developer.apple.com/documentation/safariservices/supporting_associated_domains_in_your_app>.
    1105             :   ///
    1106             :   /// For the best results, hint strings need to be understood by the platform's
    1107             :   /// autofill service. The common values of hint strings can be found in
    1108             :   /// [AutofillHints], as well as their availability on different platforms.
    1109             :   ///
    1110             :   /// If an autofillable input field needs to use a custom hint that translates to
    1111             :   /// different strings on different platforms, the easiest way to achieve that
    1112             :   /// is to return different hint strings based on the value of
    1113             :   /// [defaultTargetPlatform].
    1114             :   ///
    1115             :   /// Each hint in the list, if not ignored, will be translated to the platform's
    1116             :   /// autofill hint type understood by its autofill services:
    1117             :   ///
    1118             :   /// * On iOS, only the first hint in the list is accounted for. The hint will
    1119             :   ///   be translated to a
    1120             :   ///   [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype).
    1121             :   ///
    1122             :   /// * On Android, all hints in the list are translated to Android hint strings.
    1123             :   ///
    1124             :   /// * On web, only the first hint is accounted for and will be translated to
    1125             :   ///   an "autocomplete" string.
    1126             :   ///
    1127             :   /// Providing an autofill hint that is predefined on the platform does not
    1128             :   /// automatically grant the input field eligibility for autofill. Ultimately,
    1129             :   /// it comes down to the autofill service currently in charge to determine
    1130             :   /// whether an input field is suitable for autofill and what the autofill
    1131             :   /// candidates are.
    1132             :   ///
    1133             :   /// See also:
    1134             :   ///
    1135             :   /// * [AutofillHints], a list of autofill hint strings that is predefined on at
    1136             :   ///   least one platform.
    1137             :   ///
    1138             :   /// * [UITextContentType](https://developer.apple.com/documentation/uikit/uitextcontenttype),
    1139             :   ///   the iOS equivalent.
    1140             :   ///
    1141             :   /// * Android [autofillHints](https://developer.android.com/reference/android/view/View#setAutofillHints(java.lang.String...)),
    1142             :   ///   the Android equivalent.
    1143             :   ///
    1144             :   /// * The [autocomplete](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) attribute,
    1145             :   ///   the web equivalent.
    1146             :   final Iterable<String>? autofillHints;
    1147             : 
    1148             :   /// Restoration ID to save and restore the state of the text field.
    1149             :   ///
    1150             :   /// If non-null, the text field will persist and restore its current scroll
    1151             :   /// offset and - if no [controller] has been provided - the content of the
    1152             :   /// text field. If a [controller] has been provided, it is the responsibility
    1153             :   /// of the owner of that controller to persist and restore it, e.g. by using
    1154             :   /// a [RestorableTextEditingController].
    1155             :   ///
    1156             :   /// The state of this widget is persisted in a [RestorationBucket] claimed
    1157             :   /// from the surrounding [RestorationScope] using the provided restoration ID.
    1158             :   ///
    1159             :   /// See also:
    1160             :   ///
    1161             :   ///  * [RestorationManager], which explains how state restoration works in
    1162             :   ///    Flutter.
    1163             :   final String? restorationId;
    1164             : 
    1165           1 :   @override
    1166           1 :   _TextFieldState createState() => _TextFieldState();
    1167             : 
    1168           1 :   @override
    1169             :   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    1170           1 :     super.debugFillProperties(properties);
    1171           2 :     properties.add(DiagnosticsProperty<TextEditingController>(
    1172           1 :         'controller', controller,
    1173             :         defaultValue: null));
    1174           3 :     properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode,
    1175             :         defaultValue: null));
    1176             :     properties
    1177           3 :         .add(DiagnosticsProperty<bool>('enabled', enabled, defaultValue: null));
    1178           2 :     properties.add(DiagnosticsProperty<InputDecoration>(
    1179           1 :         'decoration', decoration,
    1180             :         defaultValue: const InputDecoration()));
    1181           2 :     properties.add(DiagnosticsProperty<TextInputType>(
    1182           1 :         'keyboardType', keyboardType,
    1183             :         defaultValue: TextInputType.text));
    1184           1 :     properties.add(
    1185           2 :         DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
    1186           1 :     properties.add(
    1187           2 :         DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
    1188           2 :     properties.add(DiagnosticsProperty<String>(
    1189           1 :         'obscuringCharacter', obscuringCharacter,
    1190             :         defaultValue: '•'));
    1191           3 :     properties.add(DiagnosticsProperty<bool>('obscureText', obscureText,
    1192             :         defaultValue: false));
    1193           3 :     properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect,
    1194             :         defaultValue: true));
    1195           2 :     properties.add(DiagnosticsProperty<bool>(
    1196           1 :         'enableSuggestions', enableSuggestions,
    1197             :         defaultValue: true));
    1198           3 :     properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
    1199           3 :     properties.add(IntProperty('minLines', minLines, defaultValue: null));
    1200           1 :     properties.add(
    1201           2 :         DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
    1202           3 :     properties.add(IntProperty('maxLength', maxLength, defaultValue: null));
    1203           2 :     properties.add(EnumProperty<TextInputAction>(
    1204           1 :         'textInputAction', textInputAction,
    1205             :         defaultValue: null));
    1206           3 :     properties.add(EnumProperty<MongolTextAlign>('textAlign', textAlign,
    1207             :         defaultValue: MongolTextAlign.top));
    1208           2 :     properties.add(DiagnosticsProperty<TextAlignHorizontal>(
    1209           1 :         'textAlignHorizontal', textAlignHorizontal,
    1210             :         defaultValue: null));
    1211             :     properties
    1212           3 :         .add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: null));
    1213             :     properties
    1214           3 :         .add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: 2.0));
    1215           3 :     properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius,
    1216             :         defaultValue: null));
    1217             :     properties
    1218           3 :         .add(ColorProperty('cursorColor', cursorColor, defaultValue: null));
    1219           2 :     properties.add(DiagnosticsProperty<Brightness>(
    1220           1 :         'keyboardAppearance', keyboardAppearance,
    1221             :         defaultValue: null));
    1222           2 :     properties.add(DiagnosticsProperty<EdgeInsetsGeometry>(
    1223           1 :         'scrollPadding', scrollPadding,
    1224             :         defaultValue: const EdgeInsets.all(20.0)));
    1225           2 :     properties.add(FlagProperty('selectionEnabled',
    1226           1 :         value: selectionEnabled,
    1227             :         defaultValue: true,
    1228             :         ifFalse: 'selection disabled'));
    1229           2 :     properties.add(DiagnosticsProperty<TextSelectionControls>(
    1230           1 :         'selectionControls', selectionControls,
    1231             :         defaultValue: null));
    1232           2 :     properties.add(DiagnosticsProperty<ScrollController>(
    1233           1 :         'scrollController', scrollController,
    1234             :         defaultValue: null));
    1235           2 :     properties.add(DiagnosticsProperty<ScrollPhysics>(
    1236           1 :         'scrollPhysics', scrollPhysics,
    1237             :         defaultValue: null));
    1238             :   }
    1239             : }
    1240             : 
    1241             : class _TextFieldState extends State<MongolTextField>
    1242             :     with RestorationMixin
    1243             :     implements MongolTextSelectionGestureDetectorBuilderDelegate {
    1244             :   RestorableTextEditingController? _controller;
    1245           1 :   TextEditingController get _effectiveController =>
    1246           4 :       widget.controller ?? _controller!.value;
    1247             : 
    1248             :   FocusNode? _focusNode;
    1249           1 :   FocusNode get _effectiveFocusNode =>
    1250           4 :       widget.focusNode ?? (_focusNode ??= FocusNode());
    1251             : 
    1252             :   bool _isHovering = false;
    1253             : 
    1254           0 :   bool get needsCounter =>
    1255           0 :       widget.maxLength != null &&
    1256           0 :       widget.decoration != null &&
    1257           0 :       widget.decoration!.counterText == null;
    1258             : 
    1259             :   bool _showSelectionHandles = false;
    1260             : 
    1261             :   late _TextFieldSelectionGestureDetectorBuilder
    1262             :       _selectionGestureDetectorBuilder;
    1263             : 
    1264             :   // API for MongolTextSelectionGestureDetectorBuilderDelegate.
    1265             :   @override
    1266             :   late bool forcePressEnabled;
    1267             : 
    1268             :   @override
    1269             :   final GlobalKey<MongolEditableTextState> editableTextKey =
    1270             :       GlobalKey<MongolEditableTextState>();
    1271             : 
    1272           1 :   @override
    1273           2 :   bool get selectionEnabled => widget.selectionEnabled;
    1274             :   // End of API for MongolTextSelectionGestureDetectorBuilderDelegate.
    1275             : 
    1276           6 :   bool get _isEnabled => widget.enabled ?? widget.decoration?.enabled ?? true;
    1277             : 
    1278           6 :   int get _currentLength => _effectiveController.value.text.characters.length;
    1279             : 
    1280           1 :   bool get _hasIntrinsicError =>
    1281           2 :       widget.maxLength != null &&
    1282           3 :       widget.maxLength! > 0 &&
    1283           8 :       _effectiveController.value.text.characters.length > widget.maxLength!;
    1284             : 
    1285           1 :   bool get _hasError =>
    1286           4 :       widget.decoration?.errorText != null || _hasIntrinsicError;
    1287             : 
    1288           1 :   InputDecoration _getEffectiveDecoration() {
    1289           2 :     final localizations = MaterialLocalizations.of(context);
    1290           2 :     final themeData = Theme.of(context);
    1291           2 :     final effectiveDecoration = (widget.decoration ?? const InputDecoration())
    1292           2 :         .applyDefaults(themeData.inputDecorationTheme)
    1293           1 :         .copyWith(
    1294           1 :           enabled: _isEnabled,
    1295           5 :           hintMaxLines: widget.decoration?.hintMaxLines ?? widget.maxLines,
    1296             :         );
    1297             : 
    1298             :     // No need to build anything if counter or counterText were given directly.
    1299           1 :     if (effectiveDecoration.counter != null ||
    1300           1 :         effectiveDecoration.counterText != null) return effectiveDecoration;
    1301             : 
    1302             :     // If buildCounter was provided, use it to generate a counter widget.
    1303             :     Widget? counter;
    1304           1 :     final currentLength = _currentLength;
    1305           1 :     if (effectiveDecoration.counter == null &&
    1306           1 :         effectiveDecoration.counterText == null &&
    1307           2 :         widget.buildCounter != null) {
    1308           2 :       final isFocused = _effectiveFocusNode.hasFocus;
    1309           3 :       final builtCounter = widget.buildCounter!(
    1310           1 :         context,
    1311             :         currentLength: currentLength,
    1312           2 :         maxLength: widget.maxLength,
    1313             :         isFocused: isFocused,
    1314             :       );
    1315             :       // If buildCounter returns null, don't add a counter widget to the field.
    1316             :       if (builtCounter != null) {
    1317           0 :         counter = Semantics(
    1318             :           container: true,
    1319             :           liveRegion: isFocused,
    1320             :           child: builtCounter,
    1321             :         );
    1322             :       }
    1323           1 :       return effectiveDecoration.copyWith(counter: counter);
    1324             :     }
    1325             : 
    1326           2 :     if (widget.maxLength == null) {
    1327             :       return effectiveDecoration; // No counter widget
    1328             :     }
    1329             : 
    1330           1 :     var counterText = '$currentLength';
    1331             :     var semanticCounterText = '';
    1332             : 
    1333             :     // Handle a real maxLength (positive number)
    1334           3 :     if (widget.maxLength! > 0) {
    1335             :       // Show the maxLength in the counter
    1336           4 :       counterText += '/${widget.maxLength}';
    1337             :       final remaining =
    1338           6 :           (widget.maxLength! - currentLength).clamp(0, widget.maxLength!);
    1339             :       semanticCounterText =
    1340           1 :           localizations.remainingTextFieldCharacterCount(remaining);
    1341             :     }
    1342             : 
    1343           1 :     if (_hasIntrinsicError) {
    1344           0 :       return effectiveDecoration.copyWith(
    1345           0 :         errorText: effectiveDecoration.errorText ?? '',
    1346           0 :         counterStyle: effectiveDecoration.errorStyle ??
    1347           0 :             themeData.textTheme.caption!.copyWith(color: themeData.errorColor),
    1348             :         counterText: counterText,
    1349             :         semanticCounterText: semanticCounterText,
    1350             :       );
    1351             :     }
    1352             : 
    1353           1 :     return effectiveDecoration.copyWith(
    1354             :       counterText: counterText,
    1355             :       semanticCounterText: semanticCounterText,
    1356             :     );
    1357             :   }
    1358             : 
    1359           1 :   @override
    1360             :   void initState() {
    1361           1 :     super.initState();
    1362           1 :     _selectionGestureDetectorBuilder =
    1363           1 :         _TextFieldSelectionGestureDetectorBuilder(state: this);
    1364           2 :     if (widget.controller == null) {
    1365           1 :       _createLocalController();
    1366             :     }
    1367           3 :     _effectiveFocusNode.canRequestFocus = _isEnabled;
    1368             :   }
    1369             : 
    1370           1 :   bool get _canRequestFocus {
    1371           3 :     final mode = MediaQuery.maybeOf(context)?.navigationMode ??
    1372             :         NavigationMode.traditional;
    1373             :     switch (mode) {
    1374           1 :       case NavigationMode.traditional:
    1375           1 :         return _isEnabled;
    1376           1 :       case NavigationMode.directional:
    1377             :         return true;
    1378             :     }
    1379             :   }
    1380             : 
    1381           1 :   @override
    1382             :   void didChangeDependencies() {
    1383           1 :     super.didChangeDependencies();
    1384           3 :     _effectiveFocusNode.canRequestFocus = _canRequestFocus;
    1385             :   }
    1386             : 
    1387           1 :   @override
    1388             :   void didUpdateWidget(MongolTextField oldWidget) {
    1389           1 :     super.didUpdateWidget(oldWidget);
    1390           3 :     if (widget.controller == null && oldWidget.controller != null) {
    1391           3 :       _createLocalController(oldWidget.controller!.value);
    1392           3 :     } else if (widget.controller != null && oldWidget.controller == null) {
    1393           2 :       unregisterFromRestoration(_controller!);
    1394           2 :       _controller!.dispose();
    1395           1 :       _controller = null;
    1396             :     }
    1397           3 :     _effectiveFocusNode.canRequestFocus = _canRequestFocus;
    1398           2 :     if (_effectiveFocusNode.hasFocus &&
    1399           4 :         widget.readOnly != oldWidget.readOnly &&
    1400           1 :         _isEnabled) {
    1401           3 :       if (_effectiveController.selection.isCollapsed) {
    1402           3 :         _showSelectionHandles = !widget.readOnly;
    1403             :       }
    1404             :     }
    1405             :   }
    1406             : 
    1407           1 :   @override
    1408             :   void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    1409           1 :     if (_controller != null) {
    1410           1 :       _registerController();
    1411             :     }
    1412             :   }
    1413             : 
    1414           1 :   void _registerController() {
    1415           1 :     assert(_controller != null);
    1416           2 :     registerForRestoration(_controller!, 'controller');
    1417             :   }
    1418             : 
    1419           1 :   void _createLocalController([TextEditingValue? value]) {
    1420           1 :     assert(_controller == null);
    1421           1 :     _controller = value == null
    1422           1 :         ? RestorableTextEditingController()
    1423           1 :         : RestorableTextEditingController.fromValue(value);
    1424           1 :     if (!restorePending) {
    1425           1 :       _registerController();
    1426             :     }
    1427             :   }
    1428             : 
    1429           1 :   @override
    1430           2 :   String? get restorationId => widget.restorationId;
    1431             : 
    1432           1 :   @override
    1433             :   void dispose() {
    1434           2 :     _focusNode?.dispose();
    1435           2 :     _controller?.dispose();
    1436           1 :     super.dispose();
    1437             :   }
    1438             : 
    1439           3 :   MongolEditableTextState? get _editableText => editableTextKey.currentState;
    1440             : 
    1441           1 :   void _requestKeyboard() {
    1442           2 :     _editableText?.requestKeyboard();
    1443             :   }
    1444             : 
    1445           1 :   bool _shouldShowSelectionHandles(SelectionChangedCause? cause) {
    1446             :     // When the text field is activated by something that doesn't trigger the
    1447             :     // selection overlay, we shouldn't show the handles either.
    1448           2 :     if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) {
    1449             :       return false;
    1450             :     }
    1451             : 
    1452           1 :     if (cause == SelectionChangedCause.keyboard) return false;
    1453             : 
    1454           5 :     if (widget.readOnly && _effectiveController.selection.isCollapsed) {
    1455             :       return false;
    1456             :     }
    1457             : 
    1458           1 :     if (!_isEnabled) return false;
    1459             : 
    1460           1 :     if (cause == SelectionChangedCause.longPress) return true;
    1461             : 
    1462           3 :     if (_effectiveController.text.isNotEmpty) return true;
    1463             : 
    1464             :     return false;
    1465             :   }
    1466             : 
    1467           1 :   void _handleSelectionChanged(
    1468             :       TextSelection selection, SelectionChangedCause? cause) {
    1469           1 :     final willShowSelectionHandles = _shouldShowSelectionHandles(cause);
    1470           2 :     if (willShowSelectionHandles != _showSelectionHandles) {
    1471           2 :       setState(() {
    1472           1 :         _showSelectionHandles = willShowSelectionHandles;
    1473             :       });
    1474             :     }
    1475             : 
    1476           3 :     switch (Theme.of(context).platform) {
    1477           1 :       case TargetPlatform.iOS:
    1478           1 :       case TargetPlatform.macOS:
    1479           1 :         if (cause == SelectionChangedCause.longPress) {
    1480           0 :           _editableText?.bringIntoView(selection.base);
    1481             :         }
    1482             :         return;
    1483           1 :       case TargetPlatform.android:
    1484           1 :       case TargetPlatform.fuchsia:
    1485           1 :       case TargetPlatform.linux:
    1486           1 :       case TargetPlatform.windows:
    1487             :       // Do nothing.
    1488             :     }
    1489             :   }
    1490             : 
    1491             :   /// Toggle the toolbar when a selection handle is tapped.
    1492           1 :   void _handleSelectionHandleTapped() {
    1493           3 :     if (_effectiveController.selection.isCollapsed) {
    1494           2 :       _editableText!.toggleToolbar();
    1495             :     }
    1496             :   }
    1497             : 
    1498           1 :   void _handleHover(bool hovering) {
    1499           2 :     if (hovering != _isHovering) {
    1500           2 :       setState(() {
    1501           1 :         _isHovering = hovering;
    1502             :       });
    1503             :     }
    1504             :   }
    1505             : 
    1506           1 :   @override
    1507             :   Widget build(BuildContext context) {
    1508           1 :     assert(debugCheckHasMaterial(context));
    1509           1 :     assert(debugCheckHasMaterialLocalizations(context));
    1510             :     assert(
    1511           3 :       !(widget.style != null &&
    1512           4 :           widget.style!.inherit == false &&
    1513           3 :           (widget.style!.fontSize == null ||
    1514           3 :               widget.style!.textBaseline == null)),
    1515             :       'inherit false style must supply fontSize and textBaseline',
    1516             :     );
    1517             : 
    1518           1 :     final theme = Theme.of(context);
    1519           1 :     final selectionTheme = TextSelectionTheme.of(context);
    1520           5 :     final style = theme.textTheme.subtitle1!.merge(widget.style);
    1521             :     final keyboardAppearance =
    1522           3 :         widget.keyboardAppearance ?? theme.primaryColorBrightness;
    1523           1 :     final controller = _effectiveController;
    1524           1 :     final focusNode = _effectiveFocusNode;
    1525           1 :     final formatters = <TextInputFormatter>[
    1526           2 :       ...?widget.inputFormatters,
    1527             :     ];
    1528             : 
    1529           2 :     var textSelectionControls = widget.selectionControls;
    1530             :     final bool cursorOpacityAnimates;
    1531             :     Offset? cursorOffset;
    1532           2 :     var cursorColor = widget.cursorColor;
    1533             :     final Color selectionColor;
    1534           2 :     var cursorRadius = widget.cursorRadius;
    1535             : 
    1536           1 :     switch (theme.platform) {
    1537           1 :       case TargetPlatform.iOS:
    1538           1 :         final cupertinoTheme = CupertinoTheme.of(context);
    1539           1 :         forcePressEnabled = true;
    1540           1 :         textSelectionControls ??= mongolTextSelectionControls;
    1541             :         cursorOpacityAnimates = true;
    1542             :         cursorColor ??=
    1543           2 :             selectionTheme.cursorColor ?? cupertinoTheme.primaryColor;
    1544           1 :         selectionColor = selectionTheme.selectionColor ??
    1545           2 :             cupertinoTheme.primaryColor.withOpacity(0.40);
    1546             :         cursorRadius ??= const Radius.circular(2.0);
    1547           1 :         cursorOffset = Offset(
    1548           3 :             iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
    1549             :         break;
    1550             : 
    1551           1 :       case TargetPlatform.macOS:
    1552           1 :         final cupertinoTheme = CupertinoTheme.of(context);
    1553           1 :         forcePressEnabled = false;
    1554           1 :         textSelectionControls ??= mongolTextSelectionControls;
    1555             :         cursorOpacityAnimates = true;
    1556             :         cursorColor ??=
    1557           2 :             selectionTheme.cursorColor ?? cupertinoTheme.primaryColor;
    1558           1 :         selectionColor = selectionTheme.selectionColor ??
    1559           2 :             cupertinoTheme.primaryColor.withOpacity(0.40);
    1560             :         cursorRadius ??= const Radius.circular(2.0);
    1561           1 :         cursorOffset = Offset(
    1562           3 :             iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
    1563             :         break;
    1564             : 
    1565           1 :       case TargetPlatform.android:
    1566           1 :       case TargetPlatform.fuchsia:
    1567           1 :         forcePressEnabled = false;
    1568           1 :         textSelectionControls ??= mongolTextSelectionControls;
    1569             :         cursorOpacityAnimates = false;
    1570           3 :         cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary;
    1571           1 :         selectionColor = selectionTheme.selectionColor ??
    1572           3 :             theme.colorScheme.primary.withOpacity(0.40);
    1573             :         break;
    1574             : 
    1575           1 :       case TargetPlatform.linux:
    1576           1 :       case TargetPlatform.windows:
    1577           1 :         forcePressEnabled = false;
    1578           1 :         textSelectionControls ??= mongolTextSelectionControls;
    1579             :         cursorOpacityAnimates = false;
    1580           3 :         cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary;
    1581           1 :         selectionColor = selectionTheme.selectionColor ??
    1582           3 :             theme.colorScheme.primary.withOpacity(0.40);
    1583             :         break;
    1584             :     }
    1585             : 
    1586           1 :     Widget child = RepaintBoundary(
    1587           1 :       child: UnmanagedRestorationScope(
    1588           1 :         bucket: bucket,
    1589           1 :         child: MongolEditableText(
    1590           1 :           key: editableTextKey,
    1591           3 :           readOnly: widget.readOnly || !_isEnabled,
    1592           2 :           toolbarOptions: widget.toolbarOptions,
    1593           2 :           showCursor: widget.showCursor,
    1594           1 :           showSelectionHandles: _showSelectionHandles,
    1595             :           controller: controller,
    1596             :           focusNode: focusNode,
    1597           2 :           keyboardType: widget.keyboardType,
    1598           2 :           textInputAction: widget.textInputAction,
    1599             :           style: style,
    1600           2 :           textAlign: widget.textAlign,
    1601           2 :           autofocus: widget.autofocus,
    1602           2 :           obscuringCharacter: widget.obscuringCharacter,
    1603           2 :           obscureText: widget.obscureText,
    1604           2 :           autocorrect: widget.autocorrect,
    1605           2 :           enableSuggestions: widget.enableSuggestions,
    1606           2 :           maxLines: widget.maxLines,
    1607           2 :           minLines: widget.minLines,
    1608           2 :           expands: widget.expands,
    1609             :           selectionColor: selectionColor,
    1610             :           selectionControls:
    1611           2 :               widget.selectionEnabled ? textSelectionControls : null,
    1612           2 :           onChanged: widget.onChanged,
    1613           1 :           onSelectionChanged: _handleSelectionChanged,
    1614           2 :           onEditingComplete: widget.onEditingComplete,
    1615           2 :           onSubmitted: widget.onSubmitted,
    1616           2 :           onAppPrivateCommand: widget.onAppPrivateCommand,
    1617           1 :           onSelectionHandleTapped: _handleSelectionHandleTapped,
    1618             :           inputFormatters: formatters,
    1619             :           rendererIgnoresPointer: true,
    1620             :           mouseCursor:
    1621             :               MouseCursor.defer, // MongolTextField will handle the cursor
    1622           2 :           cursorWidth: widget.cursorWidth,
    1623           2 :           cursorHeight: widget.cursorHeight,
    1624             :           cursorRadius: cursorRadius,
    1625             :           cursorColor: cursorColor,
    1626             :           cursorOpacityAnimates: cursorOpacityAnimates,
    1627             :           cursorOffset: cursorOffset,
    1628           2 :           scrollPadding: widget.scrollPadding,
    1629             :           keyboardAppearance: keyboardAppearance,
    1630           2 :           enableInteractiveSelection: widget.enableInteractiveSelection,
    1631           2 :           dragStartBehavior: widget.dragStartBehavior,
    1632           2 :           scrollController: widget.scrollController,
    1633           2 :           scrollPhysics: widget.scrollPhysics,
    1634           2 :           autofillHints: widget.autofillHints,
    1635             :           restorationId: 'editable',
    1636             :         ),
    1637             :       ),
    1638             :     );
    1639             : 
    1640           2 :     if (widget.decoration != null) {
    1641           1 :       child = AnimatedBuilder(
    1642           2 :         animation: Listenable.merge(<Listenable>[focusNode, controller]),
    1643           1 :         builder: (BuildContext context, Widget? child) {
    1644           1 :           return MongolInputDecorator(
    1645           1 :             decoration: _getEffectiveDecoration(),
    1646           2 :             baseStyle: widget.style,
    1647           2 :             textAlign: widget.textAlign,
    1648           2 :             textAlignHorizontal: widget.textAlignHorizontal,
    1649           1 :             isHovering: _isHovering,
    1650           1 :             isFocused: focusNode.hasFocus,
    1651           3 :             isEmpty: controller.value.text.isEmpty,
    1652           2 :             expands: widget.expands,
    1653             :             child: child,
    1654             :           );
    1655             :         },
    1656             :         child: child,
    1657             :       );
    1658             :     }
    1659           1 :     final effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
    1660           2 :       widget.mouseCursor ?? MaterialStateMouseCursor.textable,
    1661             :       <MaterialState>{
    1662           2 :         if (!_isEnabled) MaterialState.disabled,
    1663           2 :         if (_isHovering) MaterialState.hovered,
    1664           2 :         if (focusNode.hasFocus) MaterialState.focused,
    1665           2 :         if (_hasError) MaterialState.error,
    1666             :       },
    1667             :     );
    1668             : 
    1669             :     final int? semanticsMaxValueLength;
    1670           5 :     if (widget.maxLength != null && widget.maxLength! > 0) {
    1671           2 :       semanticsMaxValueLength = widget.maxLength;
    1672             :     } else {
    1673             :       semanticsMaxValueLength = null;
    1674             :     }
    1675             : 
    1676           1 :     return MouseRegion(
    1677             :       cursor: effectiveMouseCursor,
    1678           2 :       onEnter: (PointerEnterEvent event) => _handleHover(true),
    1679           0 :       onExit: (PointerExitEvent event) => _handleHover(false),
    1680           1 :       child: IgnorePointer(
    1681           1 :         ignoring: !_isEnabled,
    1682           1 :         child: AnimatedBuilder(
    1683             :           animation: controller, // changes the _currentLength
    1684           1 :           builder: (BuildContext context, Widget? child) {
    1685           1 :             return Semantics(
    1686             :               maxValueLength: semanticsMaxValueLength,
    1687           1 :               currentValueLength: _currentLength,
    1688           2 :               onTap: widget.readOnly
    1689             :                   ? null
    1690           0 :                   : () {
    1691           0 :                       if (!_effectiveController.selection.isValid) {
    1692           0 :                         _effectiveController.selection =
    1693           0 :                             TextSelection.collapsed(
    1694           0 :                                 offset: _effectiveController.text.length);
    1695             :                       }
    1696           0 :                       _requestKeyboard();
    1697             :                     },
    1698             :               child: child,
    1699             :             );
    1700             :           },
    1701           2 :           child: _selectionGestureDetectorBuilder.buildGestureDetector(
    1702             :             behavior: HitTestBehavior.translucent,
    1703             :             child: child,
    1704             :           ),
    1705             :         ),
    1706             :       ),
    1707             :     );
    1708             :   }
    1709             : }

Generated by: LCOV version 1.15