|
| 1 | +import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle; |
| 2 | + |
| 3 | +import 'package:flutter/cupertino.dart'; |
| 4 | +import 'package:flutter/gestures.dart'; |
| 5 | +import 'package:flutter/services.dart'; |
| 6 | +import 'package:flutter_form_builder/flutter_form_builder.dart'; |
| 7 | + |
| 8 | +class FormBuilderCupertinoTextField extends FormBuilderField<String> { |
| 9 | + /// Controls the text being edited. |
| 10 | + /// |
| 11 | + /// If null, this widget will create its own [TextEditingController]. |
| 12 | + final TextEditingController? controller; |
| 13 | + |
| 14 | + /// {@macro flutter.widgets.editableText.keyboardType} |
| 15 | + final TextInputType? keyboardType; |
| 16 | + |
| 17 | + /// The type of action button to use for the keyboard. |
| 18 | + /// |
| 19 | + /// Defaults to [TextInputAction.newline] if [keyboardType] is |
| 20 | + /// [TextInputType.multiline] and [TextInputAction.done] otherwise. |
| 21 | + final TextInputAction? textInputAction; |
| 22 | + |
| 23 | + /// {@macro flutter.widgets.editableText.textCapitalization} |
| 24 | + final TextCapitalization textCapitalization; |
| 25 | + |
| 26 | + /// The style to use for the text being edited. |
| 27 | + /// |
| 28 | + /// Also serves as a base for the [placeholder] text's style. |
| 29 | + /// |
| 30 | + /// Defaults to the standard iOS font style from [CupertinoTheme] if null. |
| 31 | + final TextStyle? style; |
| 32 | + |
| 33 | + /// {@macro flutter.widgets.editableText.strutStyle} |
| 34 | + final StrutStyle? strutStyle; |
| 35 | + |
| 36 | + /// {@macro flutter.widgets.editableText.textAlign} |
| 37 | + final TextAlign textAlign; |
| 38 | + |
| 39 | + /// {@macro flutter.widgets.inputDecorator.textAlignVertical} |
| 40 | + final TextAlignVertical? textAlignVertical; |
| 41 | + |
| 42 | + /// {@macro flutter.widgets.editableText.textDirection} |
| 43 | + final TextDirection? textDirection; |
| 44 | + |
| 45 | + /// {@macro flutter.widgets.editableText.autofocus} |
| 46 | + final bool autofocus; |
| 47 | + |
| 48 | + /// {@macro flutter.widgets.editableText.obscuringCharacter} |
| 49 | + final String obscuringCharacter; |
| 50 | + |
| 51 | + /// {@macro flutter.widgets.editableText.obscureText} |
| 52 | + final bool obscureText; |
| 53 | + |
| 54 | + /// {@macro flutter.widgets.editableText.autocorrect} |
| 55 | + final bool autocorrect; |
| 56 | + |
| 57 | + /// {@macro flutter.services.textInput.smartDashesType} |
| 58 | + final SmartDashesType? smartDashesType; |
| 59 | + |
| 60 | + /// {@macro flutter.services.textInput.smartQuotesType} |
| 61 | + final SmartQuotesType? smartQuotesType; |
| 62 | + |
| 63 | + /// {@macro flutter.services.textInput.enableSuggestions} |
| 64 | + final bool enableSuggestions; |
| 65 | + |
| 66 | + /// {@macro flutter.widgets.editableText.maxLines} |
| 67 | + final int? maxLines; |
| 68 | + |
| 69 | + /// {@macro flutter.widgets.editableText.minLines} |
| 70 | + final int? minLines; |
| 71 | + |
| 72 | + /// {@macro flutter.widgets.editableText.expands} |
| 73 | + final bool expands; |
| 74 | + |
| 75 | + /// {@macro flutter.widgets.EditableText.contextMenuBuilder} |
| 76 | + /// |
| 77 | + /// If not provided, will build a default menu based on the platform. |
| 78 | + /// |
| 79 | + /// See also: |
| 80 | + /// |
| 81 | + /// * [CupertinoAdaptiveTextSelectionToolbar], which is built by default. |
| 82 | + final EditableTextContextMenuBuilder? contextMenuBuilder; |
| 83 | + |
| 84 | + /// {@macro flutter.widgets.editableText.showCursor} |
| 85 | + final bool? showCursor; |
| 86 | + |
| 87 | + /// The maximum number of characters (Unicode grapheme clusters) to allow in |
| 88 | + /// the text field. |
| 89 | + /// |
| 90 | + /// After [maxLength] characters have been input, additional input |
| 91 | + /// is ignored, unless [maxLengthEnforcement] is set to |
| 92 | + /// [MaxLengthEnforcement.none]. |
| 93 | + /// |
| 94 | + /// The TextField enforces the length with a |
| 95 | + /// [LengthLimitingTextInputFormatter], which is evaluated after the supplied |
| 96 | + /// [inputFormatters], if any. |
| 97 | + /// |
| 98 | + /// This value must be either null or greater than zero. If set to null |
| 99 | + /// (the default), there is no limit to the number of characters allowed. |
| 100 | + /// |
| 101 | + /// Whitespace characters (e.g. newline, space, tab) are included in the |
| 102 | + /// character count. |
| 103 | + /// |
| 104 | + /// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength} |
| 105 | + final int? maxLength; |
| 106 | + |
| 107 | + /// Determines how the [maxLength] limit should be enforced. |
| 108 | + /// |
| 109 | + /// If [MaxLengthEnforcement.none] is set, additional input beyond [maxLength] |
| 110 | + /// will not be enforced by the limit. |
| 111 | + /// |
| 112 | + /// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement} |
| 113 | + /// |
| 114 | + /// {@macro flutter.services.textFormatter.maxLengthEnforcement} |
| 115 | + final MaxLengthEnforcement? maxLengthEnforcement; |
| 116 | + |
| 117 | + /// {@macro flutter.widgets.editableText.onEditingComplete} |
| 118 | + final VoidCallback? onEditingComplete; |
| 119 | + |
| 120 | + /// {@macro flutter.widgets.editableText.onSubmitted} |
| 121 | + /// |
| 122 | + /// See also: |
| 123 | + /// |
| 124 | + /// * [EditableText.onSubmitted] for an example of how to handle moving to |
| 125 | + /// the next/previous field when using [TextInputAction.next] and |
| 126 | + /// [TextInputAction.previous] for [textInputAction]. |
| 127 | + final ValueChanged<String?>? onSubmitted; |
| 128 | + |
| 129 | + /// {@macro flutter.widgets.editableText.inputFormatters} |
| 130 | + final List<TextInputFormatter>? inputFormatters; |
| 131 | + |
| 132 | + /// {@macro flutter.widgets.editableText.cursorWidth} |
| 133 | + final double cursorWidth; |
| 134 | + |
| 135 | + /// {@macro flutter.widgets.editableText.cursorHeight} |
| 136 | + final double? cursorHeight; |
| 137 | + |
| 138 | + /// {@macro flutter.widgets.editableText.cursorRadius} |
| 139 | + final Radius cursorRadius; |
| 140 | + |
| 141 | + /// The color to use when painting the cursor. |
| 142 | + /// |
| 143 | + /// Defaults to [TextSelectionThemeData.cursorColor] or [CupertinoTheme.primaryColor] |
| 144 | + /// depending on [ThemeData.platform]. |
| 145 | + final Color? cursorColor; |
| 146 | + |
| 147 | + /// Controls how tall the selection highlight boxes are computed to be. |
| 148 | + /// |
| 149 | + /// See [ui.BoxHeightStyle] for details on available styles. |
| 150 | + final ui.BoxHeightStyle selectionHeightStyle; |
| 151 | + |
| 152 | + /// Controls how wide the selection highlight boxes are computed to be. |
| 153 | + /// |
| 154 | + /// See [ui.BoxWidthStyle] for details on available styles. |
| 155 | + final ui.BoxWidthStyle selectionWidthStyle; |
| 156 | + |
| 157 | + /// The appearance of the keyboard. |
| 158 | + /// |
| 159 | + /// This setting is only honored on iOS devices. |
| 160 | + /// |
| 161 | + /// If unset, defaults to the theme brightness. |
| 162 | + final Brightness? keyboardAppearance; |
| 163 | + |
| 164 | + /// {@macro flutter.widgets.editableText.scrollPadding} |
| 165 | + final EdgeInsets scrollPadding; |
| 166 | + |
| 167 | + /// {@macro flutter.widgets.editableText.enableInteractiveSelection} |
| 168 | + final bool enableInteractiveSelection; |
| 169 | + |
| 170 | + /// {@macro flutter.widgets.scrollable.dragStartBehavior} |
| 171 | + final DragStartBehavior dragStartBehavior; |
| 172 | + |
| 173 | + /// {@macro flutter.rendering.editable.selectionEnabled} |
| 174 | + bool get selectionEnabled => enableInteractiveSelection; |
| 175 | + |
| 176 | + /// {@template flutter.material.textfield.onTap} |
| 177 | + /// Called for each distinct tap except for every second tap of a double tap. |
| 178 | + /// |
| 179 | + /// The text field builds a [GestureDetector] to handle input events like tap, |
| 180 | + /// to trigger focus requests, to move the caret, adjust the selection, etc. |
| 181 | + /// Handling some of those events by wrapping the text field with a competing |
| 182 | + /// GestureDetector is problematic. |
| 183 | + /// |
| 184 | + /// To unconditionally handle taps, without interfering with the text field's |
| 185 | + /// internal gesture detector, provide this callback. |
| 186 | + /// |
| 187 | + /// If the text field is created with [enabled] false, taps will not be |
| 188 | + /// recognized. |
| 189 | + /// |
| 190 | + /// To be notified when the text field gains or loses the focus, provide a |
| 191 | + /// [focusNode] and add a listener to that. |
| 192 | + /// |
| 193 | + /// To listen to arbitrary pointer events without competing with the |
| 194 | + /// text field's internal gesture detector, use a [Listener]. |
| 195 | + /// {@endtemplate} |
| 196 | + final GestureTapCallback? onTap; |
| 197 | + |
| 198 | + /// {@macro flutter.widgets.editableText.scrollPhysics} |
| 199 | + final ScrollPhysics? scrollPhysics; |
| 200 | + |
| 201 | + /// {@macro flutter.widgets.editableText.scrollController} |
| 202 | + final ScrollController? scrollController; |
| 203 | + |
| 204 | + /// {@macro flutter.widgets.editableText.autofillHints} |
| 205 | + /// {@macro flutter.services.autofill.autofillHints} |
| 206 | + final Iterable<String>? autofillHints; |
| 207 | + |
| 208 | + ///{@macro flutter.widgets.text_selection.TextMagnifierConfiguration.intro} |
| 209 | + /// |
| 210 | + ///{@macro flutter.widgets.magnifier.intro} |
| 211 | + /// |
| 212 | + ///{@macro flutter.widgets.text_selection.TextMagnifierConfiguration.details} |
| 213 | + final TextMagnifierConfiguration? magnifierConfiguration; |
| 214 | + |
| 215 | + /// By default `false` |
| 216 | + final bool readOnly; |
| 217 | + |
| 218 | + FormBuilderCupertinoTextField({ |
| 219 | + super.key, |
| 220 | + required super.name, |
| 221 | + super.validator, |
| 222 | + String? initialValue, |
| 223 | + super.decoration, |
| 224 | + super.onChanged, |
| 225 | + super.valueTransformer, |
| 226 | + super.enabled, |
| 227 | + super.onSaved, |
| 228 | + super.autovalidateMode, |
| 229 | + super.onReset, |
| 230 | + super.focusNode, |
| 231 | + super.restorationId, |
| 232 | + this.readOnly = false, |
| 233 | + this.maxLines = 1, |
| 234 | + this.obscureText = false, |
| 235 | + this.textCapitalization = TextCapitalization.none, |
| 236 | + this.scrollPadding = const EdgeInsets.all(20.0), |
| 237 | + this.enableInteractiveSelection = true, |
| 238 | + this.maxLengthEnforcement, |
| 239 | + this.textAlign = TextAlign.start, |
| 240 | + this.autofocus = false, |
| 241 | + this.autocorrect = true, |
| 242 | + this.cursorWidth = 2.0, |
| 243 | + this.cursorHeight, |
| 244 | + this.keyboardType, |
| 245 | + this.style, |
| 246 | + this.controller, |
| 247 | + this.textInputAction, |
| 248 | + this.strutStyle, |
| 249 | + this.textDirection, |
| 250 | + this.maxLength, |
| 251 | + this.onEditingComplete, |
| 252 | + this.onSubmitted, |
| 253 | + this.inputFormatters, |
| 254 | + this.cursorRadius = const Radius.circular(2.0), |
| 255 | + this.cursorColor, |
| 256 | + this.keyboardAppearance, |
| 257 | + this.expands = false, |
| 258 | + this.minLines, |
| 259 | + this.showCursor, |
| 260 | + this.onTap, |
| 261 | + this.enableSuggestions = false, |
| 262 | + this.textAlignVertical, |
| 263 | + this.dragStartBehavior = DragStartBehavior.start, |
| 264 | + this.scrollController, |
| 265 | + this.scrollPhysics, |
| 266 | + this.selectionWidthStyle = ui.BoxWidthStyle.tight, |
| 267 | + this.smartDashesType, |
| 268 | + this.smartQuotesType, |
| 269 | + this.selectionHeightStyle = ui.BoxHeightStyle.tight, |
| 270 | + this.autofillHints, |
| 271 | + this.obscuringCharacter = '•', |
| 272 | + this.contextMenuBuilder = _defaultContextMenuBuilder, |
| 273 | + this.magnifierConfiguration, |
| 274 | + }) : super( |
| 275 | + builder: (FormFieldState<String?> field) { |
| 276 | + final state = field as _FormBuilderCupertinoTextFieldState; |
| 277 | + |
| 278 | + return CupertinoTextField( |
| 279 | + restorationId: restorationId, |
| 280 | + controller: state._effectiveController, |
| 281 | + focusNode: state.effectiveFocusNode, |
| 282 | + decoration: BoxDecoration(), |
| 283 | + keyboardType: keyboardType, |
| 284 | + textInputAction: textInputAction, |
| 285 | + style: style, |
| 286 | + strutStyle: strutStyle, |
| 287 | + textAlign: textAlign, |
| 288 | + textAlignVertical: textAlignVertical, |
| 289 | + textDirection: textDirection, |
| 290 | + textCapitalization: textCapitalization, |
| 291 | + autofocus: autofocus, |
| 292 | + readOnly: readOnly, |
| 293 | + showCursor: showCursor, |
| 294 | + obscureText: obscureText, |
| 295 | + autocorrect: autocorrect, |
| 296 | + enableSuggestions: enableSuggestions, |
| 297 | + maxLengthEnforcement: maxLengthEnforcement, |
| 298 | + maxLines: maxLines, |
| 299 | + minLines: minLines, |
| 300 | + expands: expands, |
| 301 | + maxLength: maxLength, |
| 302 | + onTap: onTap, |
| 303 | + onEditingComplete: onEditingComplete, |
| 304 | + onSubmitted: onSubmitted, |
| 305 | + inputFormatters: inputFormatters, |
| 306 | + enabled: state.enabled, |
| 307 | + cursorWidth: cursorWidth, |
| 308 | + cursorHeight: cursorHeight, |
| 309 | + cursorRadius: cursorRadius, |
| 310 | + cursorColor: cursorColor, |
| 311 | + scrollPadding: scrollPadding, |
| 312 | + keyboardAppearance: keyboardAppearance, |
| 313 | + enableInteractiveSelection: enableInteractiveSelection, |
| 314 | + dragStartBehavior: dragStartBehavior, |
| 315 | + scrollController: scrollController, |
| 316 | + scrollPhysics: scrollPhysics, |
| 317 | + selectionHeightStyle: selectionHeightStyle, |
| 318 | + selectionWidthStyle: selectionWidthStyle, |
| 319 | + smartDashesType: smartDashesType, |
| 320 | + smartQuotesType: smartQuotesType, |
| 321 | + contextMenuBuilder: contextMenuBuilder, |
| 322 | + obscuringCharacter: obscuringCharacter, |
| 323 | + autofillHints: autofillHints, |
| 324 | + magnifierConfiguration: magnifierConfiguration, |
| 325 | + ); |
| 326 | + }, |
| 327 | + ); |
| 328 | + |
| 329 | + static Widget _defaultContextMenuBuilder( |
| 330 | + BuildContext context, |
| 331 | + EditableTextState editableTextState, |
| 332 | + ) { |
| 333 | + return CupertinoAdaptiveTextSelectionToolbar.editableText( |
| 334 | + editableTextState: editableTextState, |
| 335 | + ); |
| 336 | + } |
| 337 | + |
| 338 | + @override |
| 339 | + FormBuilderFieldState<FormBuilderCupertinoTextField, String> createState() => |
| 340 | + _FormBuilderCupertinoTextFieldState(); |
| 341 | +} |
| 342 | + |
| 343 | +class _FormBuilderCupertinoTextFieldState |
| 344 | + extends FormBuilderFieldState<FormBuilderCupertinoTextField, String> { |
| 345 | + TextEditingController? get _effectiveController => |
| 346 | + widget.controller ?? _controller; |
| 347 | + |
| 348 | + TextEditingController? _controller; |
| 349 | + |
| 350 | + @override |
| 351 | + void initState() { |
| 352 | + super.initState(); |
| 353 | + //setting this to value instead of initialValue here is OK since we handle initial value in the parent class |
| 354 | + _controller = widget.controller ?? TextEditingController(text: value); |
| 355 | + _controller!.addListener(_handleControllerChanged); |
| 356 | + } |
| 357 | + |
| 358 | + @override |
| 359 | + void dispose() { |
| 360 | + // Dispose the _controller when initState created it |
| 361 | + _controller!.removeListener(_handleControllerChanged); |
| 362 | + if (null == widget.controller) { |
| 363 | + _controller!.dispose(); |
| 364 | + } |
| 365 | + super.dispose(); |
| 366 | + } |
| 367 | + |
| 368 | + @override |
| 369 | + void reset() { |
| 370 | + super.reset(); |
| 371 | + setState(() { |
| 372 | + _effectiveController!.text = initialValue ?? ''; |
| 373 | + }); |
| 374 | + } |
| 375 | + |
| 376 | + @override |
| 377 | + void didChange(String? value) { |
| 378 | + super.didChange(value); |
| 379 | + |
| 380 | + if (_effectiveController!.text != value) { |
| 381 | + _effectiveController!.text = value ?? ''; |
| 382 | + } |
| 383 | + } |
| 384 | + |
| 385 | + void _handleControllerChanged() { |
| 386 | + // Suppress changes that originated from within this class. |
| 387 | + // |
| 388 | + // In the case where a controller has been passed in to this widget, we |
| 389 | + // register this change listener. In these cases, we'll also receive change |
| 390 | + // notifications for changes originating from within this class -- for |
| 391 | + // example, the reset() method. In such cases, the FormField value will |
| 392 | + // already have been set. |
| 393 | + if (_effectiveController!.text != (value ?? '')) { |
| 394 | + didChange(_effectiveController!.text); |
| 395 | + } |
| 396 | + } |
| 397 | +} |
0 commit comments