270 lines
8.3 KiB
Dart
270 lines
8.3 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/services.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;
|
|
|
|
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,
|
|
}) : 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)
|
|
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 ? 0 : 12,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: _fillColor(),
|
|
borderRadius: DesignConfig.lowBorderRadius,
|
|
border: widget.disableBorders
|
|
? null
|
|
: Border.all(color: _borderColor()),
|
|
),
|
|
child: ValueListenableBuilder(
|
|
valueListenable: _controller,
|
|
builder: (context, val, _) {
|
|
return Directionality(
|
|
textDirection: val.text.startsWithEnglish()
|
|
? TextDirection.ltr
|
|
: TextDirection.rtl,
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(8,8,0,8),
|
|
child: TextFormField(
|
|
inputFormatters: <TextInputFormatter>[
|
|
if (!widget.acceptSpace)
|
|
FilteringTextInputFormatter.allow(
|
|
RegExp("[0-9a-zA-Z\u0600-\u06FF]")),
|
|
],
|
|
autofocus: widget.autoFocus,
|
|
obscureText: _hideContent,
|
|
textAlign: widget.textAlign ?? TextAlign.start,
|
|
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(
|
|
suffixIcon: _suffixBuilder(),
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
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.focusedBorder;
|
|
} else if (_error != null) {
|
|
return Theme.of(context).colorScheme.error;
|
|
}
|
|
return Theme.of(context).colorScheme.border;
|
|
}
|
|
|
|
Color? _titleColor() {
|
|
if (!widget.enabled) {
|
|
return Theme.of(context).colorScheme.hint;
|
|
}
|
|
if (_error != null) {
|
|
return Theme.of(context).colorScheme.error;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Color _fillColor() {
|
|
if (!widget.enabled) {
|
|
return DesignConfig.isDark
|
|
? Theme.of(context).colorScheme.disabledBackground
|
|
: Theme.of(context).colorScheme.secondCTA;
|
|
}
|
|
if (_focusNode.hasFocus) {
|
|
return Theme.of(context).colorScheme.focused;
|
|
}
|
|
if (_error != null) {
|
|
return Theme.of(context).colorScheme.errorBack;
|
|
}
|
|
return Theme.of(context).colorScheme.surface;
|
|
}
|
|
|
|
Widget? _suffixBuilder() {
|
|
if (widget.obsecureText) {
|
|
return FittedBox(
|
|
fit: BoxFit.scaleDown,
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
setState(() {
|
|
_hideContent = !_hideContent;
|
|
});
|
|
},
|
|
child: Icon(
|
|
_hideContent ? DidvanIcons.eye_solid : DidvanIcons.eye_close_solid,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
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();
|
|
}
|
|
} |