| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- import 'dart:async';
- import 'dart:math' as math;
- import 'package:flowy_style/size.dart';
- import 'package:flowy_style/text_style.dart';
- import 'package:flowy_style/theme.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:provider/provider.dart';
- // ignore: import_of_legacy_library_into_null_safe
- import 'package:textstyle_extensions/textstyle_extensions.dart';
- class StyledFormTextInput extends StatelessWidget {
- static EdgeInsets kDefaultTextInputPadding =
- EdgeInsets.only(bottom: Insets.sm, top: 4);
- final String? label;
- final bool? autoFocus;
- final String? initialValue;
- final String? hintText;
- final EdgeInsets? contentPadding;
- final TextStyle? textStyle;
- final int? maxLines;
- final TextEditingController? controller;
- final TextCapitalization? capitalization;
- final Function(String)? onChanged;
- final Function()? onEditingComplete;
- final Function(bool)? onFocusChanged;
- final Function(FocusNode)? onFocusCreated;
- const StyledFormTextInput(
- {Key? key,
- this.label,
- this.autoFocus,
- this.initialValue,
- this.onChanged,
- this.onEditingComplete,
- this.hintText,
- this.onFocusChanged,
- this.onFocusCreated,
- this.controller,
- this.contentPadding,
- this.capitalization,
- this.textStyle,
- this.maxLines})
- : super(key: key);
- @override
- Widget build(BuildContext context) {
- return StyledSearchTextInput(
- capitalization: capitalization,
- label: label,
- autoFocus: autoFocus,
- initialValue: initialValue,
- onChanged: onChanged,
- onFocusCreated: onFocusCreated,
- style: textStyle ?? TextStyles.Body1,
- onEditingComplete: onEditingComplete,
- onFocusChanged: onFocusChanged,
- controller: controller,
- maxLines: maxLines,
- inputDecoration: InputDecoration(
- isDense: true,
- contentPadding: contentPadding ?? kDefaultTextInputPadding,
- border: const ThinUnderlineBorder(
- borderSide: BorderSide(width: 5, color: Colors.red)),
- //focusedBorder: UnderlineInputBorder(borderSide: BorderSide(width: .5, color: Colors.red)),
- hintText: hintText,
- ),
- );
- }
- }
- class StyledSearchTextInput extends StatefulWidget {
- final String? label;
- final TextStyle? style;
- final EdgeInsets? contentPadding;
- final bool? autoFocus;
- final bool? obscureText;
- final IconData? icon;
- final String? initialValue;
- final int? maxLines;
- final TextEditingController? controller;
- final TextCapitalization? capitalization;
- final TextInputType? type;
- final bool? enabled;
- final bool? autoValidate;
- final bool? enableSuggestions;
- final bool? autoCorrect;
- final String? errorText;
- final String? hintText;
- final Widget? prefixIcon;
- final Widget? suffixIcon;
- final InputDecoration? inputDecoration;
- final Function(String)? onChanged;
- final Function()? onEditingComplete;
- final Function()? onEditingCancel;
- final Function(bool)? onFocusChanged;
- final Function(FocusNode)? onFocusCreated;
- final Function(String)? onFieldSubmitted;
- final Function(String?)? onSaved;
- final VoidCallback? onTap;
- const StyledSearchTextInput({
- Key? key,
- this.label,
- this.autoFocus = false,
- this.obscureText = false,
- this.type = TextInputType.text,
- this.icon,
- this.initialValue = '',
- this.controller,
- this.enabled,
- this.autoValidate = false,
- this.enableSuggestions = true,
- this.autoCorrect = true,
- this.errorText,
- this.style,
- this.contentPadding,
- this.prefixIcon,
- this.suffixIcon,
- this.inputDecoration,
- this.onChanged,
- this.onEditingComplete,
- this.onEditingCancel,
- this.onFocusChanged,
- this.onFocusCreated,
- this.onFieldSubmitted,
- this.onSaved,
- this.onTap,
- this.hintText,
- this.capitalization,
- this.maxLines,
- }) : super(key: key);
- @override
- StyledSearchTextInputState createState() => StyledSearchTextInputState();
- }
- class StyledSearchTextInputState extends State<StyledSearchTextInput> {
- late TextEditingController _controller;
- late FocusNode _focusNode;
- @override
- void initState() {
- _controller =
- widget.controller ?? TextEditingController(text: widget.initialValue);
- _focusNode = FocusNode(
- debugLabel: widget.label ?? '',
- onKey: (FocusNode node, RawKeyEvent evt) {
- if (evt is RawKeyDownEvent) {
- if (evt.logicalKey == LogicalKeyboardKey.escape) {
- widget.onEditingCancel?.call();
- return KeyEventResult.handled;
- }
- }
- return KeyEventResult.ignored;
- },
- canRequestFocus: true,
- );
- // Listen for focus out events
- _focusNode
- .addListener(() => widget.onFocusChanged?.call(_focusNode.hasFocus));
- widget.onFocusCreated?.call(_focusNode);
- if (widget.autoFocus ?? false) {
- scheduleMicrotask(() => _focusNode.requestFocus());
- }
- super.initState();
- }
- @override
- void dispose() {
- _controller.dispose();
- _focusNode.dispose();
- super.dispose();
- }
- void clear() => _controller.clear();
- String get text => _controller.text;
- set text(String value) => _controller.text = value;
- @override
- Widget build(BuildContext context) {
- final theme = context.watch<AppTheme>();
- return Container(
- padding: EdgeInsets.symmetric(vertical: Insets.sm),
- child: TextFormField(
- onChanged: widget.onChanged,
- onEditingComplete: widget.onEditingComplete,
- onFieldSubmitted: widget.onFieldSubmitted,
- onSaved: widget.onSaved,
- onTap: widget.onTap,
- autofocus: widget.autoFocus ?? false,
- focusNode: _focusNode,
- keyboardType: widget.type,
- obscureText: widget.obscureText ?? false,
- autocorrect: widget.autoCorrect ?? false,
- enableSuggestions: widget.enableSuggestions ?? false,
- style: widget.style ?? TextStyles.Body1,
- cursorColor: theme.accent1,
- controller: _controller,
- showCursor: true,
- enabled: widget.enabled,
- maxLines: widget.maxLines,
- textCapitalization: widget.capitalization ?? TextCapitalization.none,
- decoration: widget.inputDecoration ??
- InputDecoration(
- prefixIcon: widget.prefixIcon,
- suffixIcon: widget.suffixIcon,
- contentPadding:
- widget.contentPadding ?? EdgeInsets.all(Insets.m),
- border: const OutlineInputBorder(borderSide: BorderSide.none),
- isDense: true,
- icon: widget.icon == null ? null : Icon(widget.icon),
- errorText: widget.errorText,
- errorMaxLines: 2,
- hintText: widget.hintText,
- hintStyle: TextStyles.Body1.textColor(theme.grey),
- labelText: widget.label),
- ),
- );
- }
- }
- class ThinUnderlineBorder extends InputBorder {
- /// Creates an underline border for an [InputDecorator].
- ///
- /// The [borderSide] parameter defaults to [BorderSide.none] (it must not be
- /// null). Applications typically do not specify a [borderSide] parameter
- /// because the input decorator substitutes its own, using [copyWith], based
- /// on the current theme and [InputDecorator.isFocused].
- ///
- /// The [borderRadius] parameter defaults to a value where the top left
- /// and right corners have a circular radius of 4.0. The [borderRadius]
- /// parameter must not be null.
- const ThinUnderlineBorder({
- BorderSide borderSide = const BorderSide(),
- this.borderRadius = const BorderRadius.only(
- topLeft: Radius.circular(4.0),
- topRight: Radius.circular(4.0),
- ),
- }) : super(borderSide: borderSide);
- /// The radii of the border's rounded rectangle corners.
- ///
- /// When this border is used with a filled input decorator, see
- /// [InputDecoration.filled], the border radius defines the shape
- /// of the background fill as well as the bottom left and right
- /// edges of the underline itself.
- ///
- /// By default the top right and top left corners have a circular radius
- /// of 4.0.
- final BorderRadius borderRadius;
- @override
- bool get isOutline => false;
- @override
- UnderlineInputBorder copyWith(
- {BorderSide? borderSide, BorderRadius? borderRadius}) {
- return UnderlineInputBorder(
- borderSide: borderSide ?? this.borderSide,
- borderRadius: borderRadius ?? this.borderRadius,
- );
- }
- @override
- EdgeInsetsGeometry get dimensions {
- return EdgeInsets.only(bottom: borderSide.width);
- }
- @override
- UnderlineInputBorder scale(double t) {
- return UnderlineInputBorder(borderSide: borderSide.scale(t));
- }
- @override
- Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
- return Path()
- ..addRect(Rect.fromLTWH(rect.left, rect.top, rect.width,
- math.max(0.0, rect.height - borderSide.width)));
- }
- @override
- Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
- return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
- }
- @override
- ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
- if (a is UnderlineInputBorder) {
- final newBorderRadius =
- BorderRadius.lerp(a.borderRadius, borderRadius, t);
- if (newBorderRadius != null) {
- return UnderlineInputBorder(
- borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
- borderRadius: newBorderRadius,
- );
- }
- }
- return super.lerpFrom(a, t);
- }
- @override
- ShapeBorder? lerpTo(ShapeBorder? b, double t) {
- if (b is UnderlineInputBorder) {
- final newBorderRadius =
- BorderRadius.lerp(b.borderRadius, borderRadius, t);
- if (newBorderRadius != null) {
- return UnderlineInputBorder(
- borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
- borderRadius: newBorderRadius,
- );
- }
- }
- return super.lerpTo(b, t);
- }
- /// Draw a horizontal line at the bottom of [rect].
- ///
- /// The [borderSide] defines the line's color and weight. The `textDirection`
- /// `gap` and `textDirection` parameters are ignored.
- /// @override
- @override
- void paint(
- Canvas canvas,
- Rect rect, {
- double? gapStart,
- double gapExtent = 0.0,
- double gapPercentage = 0.0,
- TextDirection? textDirection,
- }) {
- if (borderRadius.bottomLeft != Radius.zero ||
- borderRadius.bottomRight != Radius.zero) {
- canvas.clipPath(getOuterPath(rect, textDirection: textDirection));
- }
- canvas.drawLine(rect.bottomLeft, rect.bottomRight, borderSide.toPaint());
- }
- @override
- bool operator ==(Object other) {
- if (identical(this, other)) return true;
- if (other.runtimeType != runtimeType) return false;
- return other is InputBorder && other.borderSide == borderSide;
- }
- @override
- int get hashCode => borderSide.hashCode;
- }
|