/// copyright 2020, roipeker //import 'package:descontar_app/const/const.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; enum ValidationPlace { /// calls [validate()] when you focus out the TextField. focus, /// calls [validate()] as you type the text. change, /// in [manual] you take care of calling of the errors on each TextField. /// you can use [TextConfig.validateAll([])] to run the [validate()] on each /// TextField, or [TextConfig.getErrors([])] to get run [onValidate()] and /// get a List<String> of errors (and no TextField UI invalidation). manual, } enum ErrorMode { none, fixed, float } class GetInputTextConfig extends GetxController { final InputBorder border; final InputBorder focusedBorder; final InputBorder enabledBorder; final InputBorder errorBorder; final InputBorder disabledBorder; final InputBorder focusedErrorBorder; final ValidationPlace validationPlace; final TextInputType keyboardType; final TextInputAction textInputAction; final TextCapitalization textCapitalization; TextStyle style; TextStyle disabledStyle; final bool showCounter; final String label; final IconData icon; final bool isPassword; final ErrorMode errorMode; final bool autocorrect; // another instance will control this one. GetInputTextConfig _obscureController; GetInputTextConfig get obscureController => _obscureController; set obscureController(GetInputTextConfig value) { _obscureController = value; _obscureController?._childObscureToControl = this; } // this is the child instance to control by the obscureController. GetInputTextConfig _childObscureToControl; final Iterable<String> autofillHints; final List<TextInputFormatter> inputFormatters; //FilteringTextInputFormatter.digitsOnly, // decoration stuffs. final FloatingLabelBehavior floatingLabelBehavior; /// should be in an InputDecoration object final bool isCollapsed; int maxLength; bool _obscureText = false; FocusNode _focus; TextEditingController _controller; FormFieldValidator<String> onValidate; bool clearErrorOnFocus = true; bool clearErrorOnTyping = true; String lastError; bool _enabled; bool get enabled => _enabled; TextStyle get _actualStyle { if (_enabled ?? true) return style; return disabledStyle; } set enabled(bool val) { if (_enabled == val) return; _enabled = val; update(); } static List<String> getErrors(List<GetInputTextConfig> inputs) { final output = <String>[]; for (var i in inputs) { final error = i.onValidate(i.value); if (!error.isNullOrBlank) output.add(error); } return output; } /// Runs [validate()] on each element in [inputs]. /// [stopOnError] will return false on the first element with an error. /// otherwise it will validate() the entire [inputs] List. static bool validateAll(List<GetInputTextConfig> inputs, {bool stopOnError = true}) { bool hasError = false; for (var i in inputs) { if (!i.validate()) { hasError = true; if (stopOnError) break; } } return !hasError; } GetInputTextConfig({ this.onValidate, this.validationPlace = ValidationPlace.manual, this.errorMode = ErrorMode.fixed, bool enabled, this.showCounter = false, this.isCollapsed = false, this.floatingLabelBehavior = FloatingLabelBehavior.auto, this.keyboardType, this.textInputAction, this.textCapitalization, this.maxLength, GetInputTextConfig obscureController, this.autocorrect = true, this.inputFormatters, this.autofillHints, this.style, // this.disabledStyle = const TextStyle(color: Styles.darkGrey), this.label, this.icon, this.border = const UnderlineInputBorder(), this.focusedBorder, this.enabledBorder, this.errorBorder, this.disabledBorder, this.focusedErrorBorder, this.isPassword = false, }) { _obscureText = isPassword; obscureController?._childObscureToControl = this; this.enabled = enabled; } FocusNode get focus => _focus ??= FocusNode(); TextEditingController get controller => _controller ??= TextEditingController(); String get value => controller.text; set value(String val) { val ??= ''; if (val == controller.text) return; controller.value = controller.value.copyWith(text: val); } bool get obscureText => _obscureText; set obscureText(bool flag) { if (_obscureText == flag) return; _obscureText = flag; update(); } @override void onInit() { focus.addListener(_handleFocus); controller.addListener(_handleTextChange); } bool get hasFocus => focus.hasFocus; String _value; void _handleTextChange() { var val = controller.text; if (val == _value) return; _value = val; if (onChanged != null) onChanged(_value); if (validationPlace == ValidationPlace.change) { validate(); } else { if (clearErrorOnTyping) error = ''; } } void _handleFocus() { if (onFocus != null) onFocus(hasFocus); if (!hasFocus) { if (validationPlace == ValidationPlace.focus) { validate(); } } else { if (hasError && clearErrorOnFocus) { error = ''; } } } bool validate() { if (onValidate != null) { error = onValidate(value); lastError = error; } return !hasError; } bool get hasError { // return !_error.isNullOrBlank; return !lastError.isNullOrBlank; // return _actualErrorText != null; } /// todo: give ability to access last error.../ /// make a private var, and do error public and holding the last /// message. String get error => _error; set error(String val) { if (_error == val) return; _error = val; if (onErrorChange != null) onErrorChange(_error.isNullOrBlank ? null : _error); update(); } Widget get _counterWidget { if (errorMode == ErrorMode.fixed) return null; return (showCounter ?? false) ? null : Container(); } String get _counterText { if (errorMode == ErrorMode.fixed) return ' '; return null; // ErrorMode.float. } // used by widget String get _actualErrorText { if (errorMode == ErrorMode.none) return null; return _error.isNullOrBlank ? null : _error; } String _error; Function(String) onChanged; Function(bool) onFocus; Function(String) onErrorChange; @override void onClose() { controller?.removeListener(_handleTextChange); controller?.dispose(); focus?.removeListener(_handleFocus); focus?.dispose(); } Widget getSuffix() { if (!isPassword) return null; if (obscureController != null) return null; return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { obscureText = !obscureText; _childObscureToControl?.obscureText = obscureText; }, child: Icon( obscureText ? Icons.visibility : Icons.visibility_off, // color: Styles.lightGrey, size: 18, ).paddingSymmetric(horizontal: 10, vertical: 6), ); } } class GetInputText extends StatelessWidget { final GetInputTextConfig config; const GetInputText({Key key, this.config}) : super(key: key); @override Widget build(BuildContext context) { return GetBuilder( init: config, global: false, assignId: true, builder: (_) { return TextField( controller: config.controller, focusNode: config.focus, style: config._actualStyle, obscureText: config.obscureText, keyboardType: config.keyboardType, maxLength: config.maxLength, maxLengthEnforced: !config.maxLength.isNull, autocorrect: config.autocorrect ?? true, autofillHints: (config.enabled ?? true) ? config.autofillHints : null, textInputAction: config.textInputAction, inputFormatters: config.inputFormatters, enabled: config.enabled, textCapitalization: config.textCapitalization ?? TextCapitalization.none, //const InputDecoration() decoration: InputDecoration( labelText: config.label, border: config.border, focusedBorder: config.focusedBorder, enabledBorder: config.enabledBorder, errorBorder: config.errorBorder, disabledBorder: config.disabledBorder, focusedErrorBorder: config.focusedErrorBorder, // errorBorder: UnderlineInputBorder( // borderSide: BorderSide(color: Styles.lightGrey), // ), contentPadding: EdgeInsets.symmetric(vertical: 6), alignLabelWithHint: true, floatingLabelBehavior: config.floatingLabelBehavior ?? FloatingLabelBehavior.auto, errorText: config._actualErrorText, errorMaxLines: 1, counterText: config._counterText, counter: config._counterWidget, icon: config.icon != null ? Icon(config.icon, size: 20).paddingOnly(top: 10) : null, isCollapsed: config.isCollapsed ?? false, suffixIconConstraints: BoxConstraints(maxWidth: 24, maxHeight: 24), // suffixIcon: config.isPassword ? config.getSuffix() : null, suffix: config.isPassword ? config.getSuffix() : null, ), ); }, ); } }