import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/utils/extension.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; class DidvanTextField extends StatefulWidget { final void Function(String value)? onChanged; final void Function(String value)? onSubmitted; final bool enabled; final bool autoFocus; final TextAlign? textAlign; final String? title; final String? hintText; final dynamic initialValue; final bool obsecureText; final bool acceptSpace; final String? Function(String value)? validator; final TextInputType? textInputType; final TextInputAction? textInputAction; final bool disableBorders; final bool isSmall; final int? maxLine; final int? minLine; final int? maxLength; final bool hasHeight; final bool showLen; final String? prefixSvgPath; final String? suffixSvgPath; const DidvanTextField({ Key? key, this.onChanged, this.enabled = true, this.title, this.hintText, this.initialValue, this.validator, this.textInputType, this.textInputAction, this.textAlign, this.obsecureText = false, this.autoFocus = false, this.onSubmitted, this.acceptSpace = true, this.disableBorders = false, this.isSmall = false, this.maxLine, this.minLine, this.maxLength, this.hasHeight = true, this.showLen = false, this.prefixSvgPath, this.suffixSvgPath, }) : super(key: key); @override State createState() => _DidvanTextFieldState(); } class _DidvanTextFieldState extends State { final FocusNode _focusNode = FocusNode(); final TextEditingController _controller = TextEditingController(); bool _hideContent = false; String? _error; @override void initState() { if (widget.initialValue != null) { _controller.text = widget.initialValue; } _hideContent = widget.obsecureText; _focusNode.addListener(() { // setState(() {}); }); super.initState(); } @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (widget.title != null) Padding( padding: const EdgeInsets.only(right: 12), child: DidvanText( widget.title!, color: _titleColor(), ), ), if (widget.title != null) const SizedBox(height: 8), Container( height: widget.hasHeight ? 48 : null, padding: const EdgeInsets.symmetric(horizontal: 12).copyWith( left: (widget.obsecureText || widget.suffixSvgPath != null) ? 0 : 12, right: (widget.prefixSvgPath != null) ? 0 : 12, ), decoration: BoxDecoration( color: _fillColor(), borderRadius: BorderRadius.circular(16), border: widget.disableBorders ? null : Border.all(color: _borderColor()), ), child: ValueListenableBuilder( valueListenable: _controller, builder: (context, val, _) { final isEnglish = val.text.startsWithEnglish(); return Directionality( textDirection: TextDirection.rtl, child: Center( child: TextFormField( autofocus: widget.autoFocus, obscureText: _hideContent, textAlign: widget.textAlign ?? (isEnglish ? TextAlign.left : TextAlign.right), keyboardType: widget.textInputType, textInputAction: widget.textInputAction, focusNode: _focusNode, controller: _controller, onFieldSubmitted: widget.onSubmitted, onChanged: _onChanged, validator: _validator, maxLines: _hideContent ? 1 : widget.maxLine, minLines: widget.minLine, maxLength: widget.maxLength, obscuringCharacter: '*', buildCounter: widget.showLen ? null : (context, {required currentLength, required isFocused, required maxLength}) => const SizedBox(), style: (widget.isSmall ? Theme.of(context).textTheme.bodySmall! : Theme.of(context).textTheme.bodyMedium!) .copyWith( fontFamily: DesignConfig.fontFamily.padRight(3)), decoration: InputDecoration( prefixIcon: _prefixBuilder(), suffixIcon: _suffixBuilder(), prefixIconConstraints: const BoxConstraints( minWidth: 32, minHeight: 32, maxHeight: 48), suffixIconConstraints: const BoxConstraints( minWidth: 32, minHeight: 32, maxHeight: 48, maxWidth: 48), enabled: widget.enabled, border: InputBorder.none, hintText: widget.hintText, errorStyle: const TextStyle(height: 0.01), hintStyle: (widget.isSmall ? Theme.of(context).textTheme.bodySmall! : Theme.of(context).textTheme.bodyMedium!) .copyWith( color: Theme.of(context).colorScheme.hint, fontSize: 12), ), ), )); }), ), AnimatedVisibility( isVisible: _error != null, duration: DesignConfig.lowAnimationDuration, child: Column( children: [ const SizedBox(height: 8), Row( children: [ Icon( DidvanIcons.lightbulb_exclamation_regular, color: Theme.of(context).colorScheme.error, size: 14, ), DidvanText( _error ?? '', style: Theme.of(context).textTheme.bodySmall, color: Theme.of(context).colorScheme.error, ), ], ), ], ), ) ], ); } Color _borderColor() { if (_focusNode.hasFocus) { return Theme.of(context).colorScheme.caption; } else if (_error != null) { return Theme.of(context).colorScheme.error; } return const Color.fromARGB(255, 223, 223, 223); } Color? _titleColor() { if (!widget.enabled) { return Theme.of(context).colorScheme.hint; } if (_error != null) { return Theme.of(context).colorScheme.error; } return Theme.of(context).colorScheme.caption; } Color _fillColor() { if (!widget.enabled) { return DesignConfig.isDark ? Theme.of(context).colorScheme.onSurface.withOpacity(0.1) : const Color.fromARGB(255, 245, 245, 245); } if (_focusNode.hasFocus) { return Theme.of(context).colorScheme.surface; } if (_error != null) { return Theme.of(context).colorScheme.errorContainer.withOpacity(0.1); } return Theme.of(context).colorScheme.surface; } Widget? _suffixBuilder() { if (widget.obsecureText) { return FittedBox( fit: BoxFit.scaleDown, child: GestureDetector( onTap: () { setState(() { _hideContent = !_hideContent; }); }, child: Padding( padding: const EdgeInsets.all(8.0), child: SvgPicture.asset( _hideContent ? 'lib/assets/icons/eye.svg' : 'lib/assets/icons/eye-slash.svg', color: DesignConfig.isDark ? Theme.of(context).colorScheme.caption : null, height: 20, ), ), ), ); } if (widget.suffixSvgPath != null) { return Padding( padding: const EdgeInsets.all(5.0), child: SvgPicture.asset( widget.suffixSvgPath!, fit: BoxFit.contain, ), ); } return null; } Widget? _prefixBuilder() { if (widget.prefixSvgPath != null) { return Row( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: SvgPicture.asset( widget.prefixSvgPath!, fit: BoxFit.contain, width: 24, height: 24, ), ), Container( width: 1, height: 24, color: const Color.fromARGB(255, 184, 184, 184)), const SizedBox(width: 8), ], ); } return null; } void _onChanged(String value) { setState(() { _error = null; }); widget.onChanged?.call(value); } String? _validator(String? value) { if (widget.validator != null) { final String? error = widget.validator!(value!); if (error != null) { setState(() { _error = error; }); return ''; } else { setState(() { _error = null; }); } } return null; } @override void dispose() { _controller.dispose(); _focusNode.dispose(); super.dispose(); } }