Created
December 16, 2025 12:08
-
-
Save followthemoney1/43a685ca037b546fc7a5af64c68246e7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:flutter/services.dart'; | |
| import 'package:pod_payment/core/contants/size_constants.dart'; | |
| import 'package:pod_payment/core/extensions/context_extension.dart'; | |
| import 'package:pod_payment/shared/widgets/pod_svg.dart'; | |
| import 'package:pod_payment/core/contants/image_constants.dart'; | |
| typedef ValidationChecker = String? Function(String?); | |
| class PodTextField extends StatefulWidget { | |
| const PodTextField({ | |
| super.key, | |
| required this.controller, | |
| this.focusNode, | |
| this.inputFormatters, | |
| this.hintText, | |
| this.autoFocus = false, | |
| this.obscureText = false, | |
| this.canShowHidePassword = false, | |
| this.keyboardType = TextInputType.text, | |
| this.isDisabled = false, | |
| this.textInputAction = TextInputAction.done, | |
| this.autoFillHints, | |
| this.maxLength = 150, | |
| this.onChanged, | |
| this.onEditingComplete, | |
| this.onSubmitted, | |
| this.onFocusChanged, | |
| this.readOnly = false, | |
| this.containerPadding, | |
| this.cursorColor, | |
| this.borderRadius, | |
| this.validator, | |
| this.textStyle, | |
| this.centerInput = false, | |
| this.minLines, | |
| this.maxLines, | |
| this.autovalidateMode = AutovalidateMode.onUserInteraction, | |
| this.backgroundColor, | |
| this.prefix, | |
| this.suffix, | |
| this.mouseCursor, | |
| this.focusedBorderSide, | |
| this.unfocusedBorderSide = const BorderSide(color: Color(0xFFC6C5C4), width: 1), | |
| this.hoverColor, | |
| this.isCollapsed, | |
| this.prefixIconConstraints, | |
| this.suffixIconConstraints, | |
| this.textAlignVertical, | |
| // | |
| }); | |
| final TextEditingController controller; | |
| final FocusNode? focusNode; | |
| final String? hintText; | |
| final List<TextInputFormatter>? inputFormatters; | |
| final bool obscureText; | |
| final bool canShowHidePassword; | |
| final TextInputType keyboardType; | |
| final bool autoFocus; | |
| final bool isDisabled; | |
| final TextInputAction textInputAction; | |
| final List<String>? autoFillHints; | |
| final int? maxLength; | |
| final Function(String)? onChanged; | |
| final VoidCallback? onEditingComplete; | |
| final Function(String)? onSubmitted; | |
| final Function(bool value)? onFocusChanged; | |
| final int? maxLines; | |
| final int? minLines; | |
| final bool readOnly; | |
| final EdgeInsets? containerPadding; | |
| final Color? cursorColor; | |
| final BorderRadius? borderRadius; | |
| final ValidationChecker? validator; | |
| final TextStyle? textStyle; | |
| final bool centerInput; | |
| final AutovalidateMode autovalidateMode; | |
| final Widget? prefix; | |
| final Widget? suffix; | |
| final Color? backgroundColor; | |
| final MouseCursor? mouseCursor; | |
| final BorderSide? focusedBorderSide; | |
| final BorderSide? unfocusedBorderSide; | |
| final Color? hoverColor; | |
| final bool? isCollapsed; | |
| final BoxConstraints? prefixIconConstraints; | |
| final BoxConstraints? suffixIconConstraints; | |
| final TextAlignVertical? textAlignVertical; | |
| @override | |
| State<PodTextField> createState() => _PodTextFieldState(); | |
| } | |
| class _PodTextFieldState extends State<PodTextField> { | |
| bool _obscureText = false; | |
| bool _hasFocus = false; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _obscureText = widget.obscureText; | |
| widget.focusNode?.addListener(() { | |
| setState(() { | |
| _hasFocus = widget.focusNode?.hasFocus ?? false; | |
| }); | |
| widget.onFocusChanged?.call(_hasFocus); | |
| }); | |
| } | |
| void _requestFocus() { | |
| setState(() { | |
| FocusScope.of(context).requestFocus(widget.focusNode); | |
| }); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Focus( | |
| onFocusChange: (value) { | |
| widget.onFocusChanged?.call(value); | |
| setState(() { | |
| _hasFocus = value; | |
| }); | |
| }, | |
| child: _buildBody(), | |
| ); | |
| } | |
| Widget _buildBody() { | |
| return TextFormField( | |
| textAlign: widget.centerInput ? TextAlign.center : TextAlign.start, | |
| maxLines: widget.maxLines ?? 1, | |
| minLines: widget.minLines, | |
| maxLength: widget.maxLength, | |
| readOnly: widget.readOnly, | |
| enabled: !widget.isDisabled, | |
| inputFormatters: [ | |
| ...?widget.inputFormatters, | |
| // FilteringTextInputFormatter.deny(_confusablesPattern), | |
| ], | |
| controller: widget.controller, | |
| focusNode: widget.focusNode, | |
| enableSuggestions: widget.autoFillHints != null, | |
| autofocus: widget.autoFocus, | |
| autocorrect: widget.autoFillHints != null, | |
| autofillHints: widget.autoFillHints, | |
| keyboardType: widget.keyboardType, | |
| obscureText: _obscureText, | |
| textInputAction: widget.textInputAction, | |
| cursorColor: widget.cursorColor ?? context.colorScheme.primary, | |
| expands: false, | |
| style: widget.textStyle ?? context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurface), | |
| onTap: _requestFocus, | |
| validator: widget.validator, | |
| onChanged: widget.onChanged, | |
| textAlignVertical: widget.textAlignVertical, | |
| mouseCursor: widget.mouseCursor, | |
| autovalidateMode: widget.autovalidateMode, | |
| onEditingComplete: widget.onEditingComplete, | |
| onFieldSubmitted: widget.onSubmitted, | |
| decoration: InputDecoration( | |
| isCollapsed: widget.isCollapsed ?? false, | |
| hoverColor: widget.hoverColor, | |
| border: OutlineInputBorder( | |
| borderRadius: widget.borderRadius ?? BorderRadius.circular(BorderRadiusConstants.small), | |
| borderSide: const BorderSide(color: Colors.transparent), | |
| ), | |
| focusedBorder: OutlineInputBorder( | |
| borderRadius: widget.borderRadius ?? BorderRadius.circular(BorderRadiusConstants.small), | |
| borderSide: widget.focusedBorderSide ?? BorderSide(color: context.colorScheme.primary), | |
| ), | |
| enabledBorder: OutlineInputBorder( | |
| borderRadius: widget.borderRadius ?? BorderRadius.circular(BorderRadiusConstants.small), | |
| borderSide: widget.unfocusedBorderSide ?? BorderSide(color: context.colorScheme.surface.withAlpha(20)), | |
| ), | |
| errorBorder: OutlineInputBorder( | |
| borderRadius: widget.borderRadius ?? BorderRadius.circular(BorderRadiusConstants.small), | |
| borderSide: BorderSide(color: context.colorScheme.error), | |
| ), | |
| focusedErrorBorder: OutlineInputBorder( | |
| borderRadius: widget.borderRadius ?? BorderRadius.circular(BorderRadiusConstants.small), | |
| borderSide: BorderSide(color: context.colorScheme.error), | |
| ), | |
| disabledBorder: OutlineInputBorder( | |
| borderRadius: widget.borderRadius ?? BorderRadius.circular(BorderRadiusConstants.small), | |
| borderSide: const BorderSide(color: Colors.transparent), | |
| ), | |
| prefixIcon: _buildPrefix(), | |
| prefixIconConstraints: widget.prefixIconConstraints, | |
| suffixIcon: _buildSuffix(), | |
| suffixIconConstraints: widget.suffixIconConstraints, | |
| filled: true, | |
| fillColor: widget.backgroundColor ?? const Color(0xFF000000), | |
| errorStyle: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.error, fontSize: 12), | |
| hintStyle: | |
| widget.textStyle?.copyWith(color: context.colorScheme.onSurfaceVariant) ?? | |
| context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceVariant), | |
| hintText: widget.hintText, | |
| alignLabelWithHint: widget.maxLines != null, | |
| counterText: '', | |
| contentPadding: | |
| widget.containerPadding ?? | |
| const EdgeInsets.symmetric(horizontal: PaddingConstants.large, vertical: PaddingConstants.large), | |
| ), | |
| ); | |
| } | |
| Widget? _buildPrefix() { | |
| if (widget.prefix != null) { | |
| return Padding(padding: const EdgeInsets.only(left: 4), child: widget.prefix); | |
| } | |
| return null; | |
| } | |
| Widget? _buildSuffix() { | |
| if (widget.canShowHidePassword && widget.suffix != null) { | |
| return Row(mainAxisSize: MainAxisSize.min, children: [widget.suffix!, _buildShowHideButton()]); | |
| } else if (widget.canShowHidePassword) { | |
| return _buildShowHideButton(); | |
| } else if (widget.suffix != null) { | |
| return widget.suffix; | |
| } | |
| return null; | |
| } | |
| Widget _buildShowHideButton() { | |
| return Padding( | |
| padding: const EdgeInsets.all(PaddingConstants.medium), | |
| child: InkWell( | |
| onTap: () { | |
| setState(() { | |
| _obscureText = !_obscureText; | |
| }); | |
| }, | |
| child: Icon( | |
| _obscureText ? Icons.visibility : Icons.visibility_off, | |
| color: _hasFocus ? context.colorScheme.primary : context.colorScheme.onSurfaceVariant, | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| class SearchBarField extends StatelessWidget { | |
| const SearchBarField({ | |
| super.key, | |
| this.controller, | |
| this.onChanged, | |
| this.onSubmitted, | |
| this.hintText = 'Search', | |
| this.autofocus = false, | |
| this.textInputAction = TextInputAction.search, | |
| this.focusNode, | |
| this.backgroundColor = const Color(0xFFF5F3F2), | |
| this.iconColor, | |
| this.textColor, | |
| this.hintColor, | |
| }); | |
| final TextEditingController? controller; | |
| final ValueChanged<String>? onChanged; | |
| final ValueChanged<String>? onSubmitted; | |
| final String hintText; | |
| final bool autofocus; | |
| final TextInputAction textInputAction; | |
| final FocusNode? focusNode; | |
| final Color backgroundColor; | |
| final Color? iconColor; | |
| final Color? textColor; | |
| final Color? hintColor; | |
| @override | |
| Widget build(BuildContext context) { | |
| // Use provided colors or fall back to default colors | |
| final effectiveIconColor = iconColor ?? Colors.grey.shade600; | |
| final effectiveTextColor = textColor ?? Colors.grey.shade700; | |
| final effectiveHintColor = hintColor ?? Colors.grey.shade500; | |
| return Semantics( | |
| label: hintText, | |
| textField: true, | |
| child: Container( | |
| decoration: BoxDecoration( | |
| color: backgroundColor, | |
| borderRadius: BorderRadius.circular(BorderRadiusConstants.small), | |
| ), | |
| // Height + horizontal padding mimic the roomy feel from the image | |
| padding: const EdgeInsets.symmetric(horizontal: PaddingConstants.large), | |
| height: SizeConstants.searchFieldHeight, | |
| alignment: Alignment.centerLeft, | |
| child: Row( | |
| children: [ | |
| PodSVGIcon(size: SizeConstants.size20, pathNonBinary: ImageConstants.icSearch, color: effectiveIconColor), | |
| const SizedBox(width: PaddingConstants.medium), | |
| // Expanded TextField without its own fill/borders | |
| Expanded( | |
| child: TextField( | |
| controller: controller, | |
| focusNode: focusNode, | |
| autofocus: autofocus, | |
| textInputAction: textInputAction, | |
| onChanged: onChanged, | |
| onSubmitted: onSubmitted, | |
| cursorColor: effectiveTextColor, | |
| style: context.theme.textTheme.titleMedium?.copyWith( | |
| color: effectiveTextColor, | |
| fontWeight: FontWeight.w500, | |
| ), | |
| decoration: InputDecoration( | |
| isDense: true, | |
| hintText: hintText, | |
| hintStyle: context.theme.textTheme.titleMedium?.copyWith( | |
| color: effectiveHintColor, | |
| fontWeight: FontWeight.w500, | |
| ), | |
| // No borders/fill so the container is the only background. | |
| border: InputBorder.none, | |
| enabledBorder: InputBorder.none, | |
| focusedBorder: InputBorder.none, | |
| disabledBorder: InputBorder.none, | |
| contentPadding: EdgeInsets.zero, | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment