diff --git a/optimus/lib/src/form/input_field.dart b/optimus/lib/src/form/input_field.dart index 01cfc062..5aff589f 100644 --- a/optimus/lib/src/form/input_field.dart +++ b/optimus/lib/src/form/input_field.dart @@ -52,6 +52,7 @@ class OptimusInputField extends StatefulWidget { this.enableIMEPersonalizedLearning = true, this.enableSuggestions = true, this.inline = false, + this.autoCollapse = false, this.statusBarState, }); @@ -82,6 +83,10 @@ class OptimusInputField extends StatefulWidget { final int? maxCharacters; /// {@macro flutter.widgets.editableText.minLines} + /// + /// If the input is multi-lined we recommend you to set the [keyboardType] to + /// [TextInputType.multiline] otherwise the move to the next line won't work + /// properly. final int? minLines; final TextEditingController? controller; final String? error; @@ -161,6 +166,10 @@ class OptimusInputField extends StatefulWidget { /// the vertical direction. final bool inline; + /// Controls whether the input should collapse to one line height if not + /// focused. + final bool autoCollapse; + final OptimusStatusBarState? statusBarState; bool get hasError { @@ -178,6 +187,8 @@ class _OptimusInputFieldState extends State FocusNode? _focusNode; bool _isShowPasswordEnabled = false; TextEditingController? _controller; + late int? _minLines = widget.minLines; + late int _maxLines = widget.maxLines; TextEditingController get _effectiveController => widget.controller ?? (_controller ??= TextEditingController()); @@ -188,19 +199,29 @@ class _OptimusInputFieldState extends State @override void initState() { super.initState(); - _effectiveFocusNode.addListener(_handleStateUpdate); + _effectiveFocusNode.addListener(_handleFocusUpdate); _effectiveController.addListener(_handleStateUpdate); } @override void dispose() { - _effectiveFocusNode.removeListener(_handleStateUpdate); + _effectiveFocusNode.removeListener(_handleFocusUpdate); _effectiveController.removeListener(_handleStateUpdate); _focusNode?.dispose(); _controller?.dispose(); super.dispose(); } + @override + void didUpdateWidget(OptimusInputField oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.autoCollapse != oldWidget.autoCollapse || + widget.minLines != oldWidget.minLines || + widget.maxLines != oldWidget.maxLines) { + _updateLines(); + } + } + bool get _shouldShowInlineError => (widget.errorVariant == OptimusInputErrorVariant.inlineTooltip || widget.inline) && @@ -228,6 +249,19 @@ class _OptimusInputFieldState extends State bool get _isPasswordToggleVisible => widget.isPasswordField && !widget.showLoader; + bool get _shouldCollapse => + widget.autoCollapse && !_effectiveFocusNode.hasFocus; + + void _handleFocusUpdate() => setState(() { + if (!widget.autoCollapse) return; + _updateLines(); + }); + + void _updateLines() { + _maxLines = _shouldCollapse ? 1 : widget.maxLines; + _minLines = _shouldCollapse ? 1 : widget.minLines; + } + void _handleStateUpdate() => setState(() {}); void _handlePasswordTap() => setState( @@ -303,8 +337,8 @@ class _OptimusInputFieldState extends State autofocus: widget.autofocus, enableInteractiveSelection: widget.enableInteractiveSelection, controller: _effectiveController, - maxLines: widget.maxLines, - minLines: widget.minLines, + maxLines: _maxLines, + minLines: _minLines, onSubmitted: widget.onSubmitted, textInputAction: widget.textInputAction, placeholder: widget.placeholder, diff --git a/storybook/lib/stories/input.dart b/storybook/lib/stories/input.dart index c0de7ad0..5cb925e3 100644 --- a/storybook/lib/stories/input.dart +++ b/storybook/lib/stories/input.dart @@ -1,4 +1,4 @@ -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:optimus/optimus.dart'; import 'package:storybook/utils.dart'; import 'package:storybook_flutter/storybook_flutter.dart'; @@ -45,7 +45,13 @@ final Story inputStory = Story( } final minLines = k.sliderInt(label: 'Min lines', initial: 1, min: 1, max: 10); + final keyboardType = k.options( + label: 'Keyboard Type:', + initial: null, + options: KeyboardType.values.toOptions().withEmpty(), + ); final inline = k.boolean(label: 'Inline', initial: false); + final autoCollapse = k.boolean(label: 'Auto Collapse', initial: true); final statusBar = k.options( label: 'Status Bar', @@ -64,6 +70,7 @@ final Story inputStory = Story( maxCharacters: maxChars, minLines: minLines, maxLines: minLines, + keyboardType: keyboardType?.inputType, prefix: prefix.isNotEmpty ? Text(prefix) : null, suffix: suffix.isNotEmpty ? Text(suffix) : null, leading: leadingIcon == null ? null : Icon(leadingIcon), @@ -78,6 +85,7 @@ final Story inputStory = Story( options: sizeOptions, ), inline: inline, + autoCollapse: autoCollapse, label: k.text(label: 'Label', initial: 'Optimus input field'), placeholder: k.text(label: 'Placeholder', initial: 'Put some hint here...'), @@ -90,3 +98,16 @@ final Story inputStory = Story( ); }, ); + +enum KeyboardType { + text(TextInputType.text), + multiline(TextInputType.multiline), + number(TextInputType.number), + phone(TextInputType.phone), + datetime(TextInputType.datetime), + emailAddress(TextInputType.emailAddress), + url(TextInputType.url); + + const KeyboardType(this.inputType); + final TextInputType inputType; +}