didvan-app/lib/views/widgets/didvan/text_field.dart

328 lines
10 KiB
Dart

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<DidvanTextField> createState() => _DidvanTextFieldState();
}
class _DidvanTextFieldState extends State<DidvanTextField> {
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();
}
}