125 lines
3.8 KiB
Dart
125 lines
3.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
/// Reusable text field used across the app.
|
|
/// - Use [controller] when you need to keep text state externally (recommended).
|
|
/// - Use [initialValue] for quick one-off population (internal controller created).
|
|
/// - [readOnly], [maxLines], [keyboardType], [validator], [onChanged] supported.
|
|
/// - [label] used as InputDecoration.labelText, [hint] as hintText.
|
|
class MyTextField extends StatefulWidget {
|
|
final String? label;
|
|
final String? hint;
|
|
final TextEditingController? controller;
|
|
final String? initialValue;
|
|
final bool readOnly;
|
|
final bool enabled;
|
|
final int maxLines;
|
|
final TextInputType keyboardType;
|
|
final Widget? prefix;
|
|
final Widget? suffix;
|
|
final String? Function(String?)? validator;
|
|
final void Function(String)? onChanged;
|
|
final void Function()? onTap;
|
|
final String? errorText;
|
|
final EdgeInsetsGeometry? contentPadding;
|
|
|
|
const MyTextField({
|
|
Key? key,
|
|
this.label,
|
|
this.hint,
|
|
this.controller,
|
|
this.initialValue,
|
|
this.readOnly = false,
|
|
this.enabled = true,
|
|
this.maxLines = 1,
|
|
this.keyboardType = TextInputType.text,
|
|
this.prefix,
|
|
this.suffix,
|
|
this.validator,
|
|
this.onChanged,
|
|
this.onTap,
|
|
this.errorText,
|
|
this.contentPadding,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<MyTextField> createState() => _MyTextFieldState();
|
|
}
|
|
|
|
class _MyTextFieldState extends State<MyTextField> {
|
|
late final TextEditingController _internalController;
|
|
bool _usingInternal = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
if (widget.controller == null) {
|
|
_usingInternal = true;
|
|
_internalController =
|
|
TextEditingController(text: widget.initialValue ?? '');
|
|
} else {
|
|
_internalController = widget.controller!;
|
|
// if initialValue provided but using external controller, set it once:
|
|
if (widget.initialValue != null && widget.initialValue!.isNotEmpty) {
|
|
_internalController.text = widget.initialValue!;
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant MyTextField oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
// If external controller changed, update our reference
|
|
if (oldWidget.controller != widget.controller && widget.controller != null) {
|
|
_internalController.text = widget.controller!.text;
|
|
}
|
|
// If initialValue changed and we use internal controller, update.
|
|
if (_usingInternal &&
|
|
widget.initialValue != null &&
|
|
widget.initialValue != oldWidget.initialValue) {
|
|
_internalController.text = widget.initialValue!;
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
if (_usingInternal) {
|
|
_internalController.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
InputBorder _defaultBorder() => OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(6),
|
|
borderSide: const BorderSide(width: 1),
|
|
);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return TextFormField(
|
|
controller: _internalController,
|
|
readOnly: widget.readOnly,
|
|
enabled: widget.enabled,
|
|
maxLines: widget.maxLines,
|
|
keyboardType: widget.keyboardType,
|
|
validator: widget.validator,
|
|
onChanged: widget.onChanged,
|
|
onTap: widget.onTap,
|
|
decoration: InputDecoration(
|
|
isDense: true,
|
|
contentPadding:
|
|
widget.contentPadding ?? const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
|
labelText: widget.label,
|
|
hintText: widget.hint,
|
|
prefixIcon: widget.prefix,
|
|
suffixIcon: widget.suffix,
|
|
errorText: widget.errorText,
|
|
border: _defaultBorder(),
|
|
enabledBorder: _defaultBorder(),
|
|
focusedBorder: _defaultBorder().copyWith(
|
|
borderSide: BorderSide(color: Theme.of(context).colorScheme.primary, width: 1.2)),
|
|
disabledBorder: _defaultBorder(),
|
|
),
|
|
);
|
|
}
|
|
}
|