styled_text_input.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. import 'dart:async';
  2. import 'dart:math' as math;
  3. import 'package:flowy_style/size.dart';
  4. import 'package:flowy_style/text_style.dart';
  5. import 'package:flowy_style/theme.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter/services.dart';
  8. import 'package:provider/provider.dart';
  9. // ignore: import_of_legacy_library_into_null_safe
  10. import 'package:textstyle_extensions/textstyle_extensions.dart';
  11. class StyledFormTextInput extends StatelessWidget {
  12. static EdgeInsets kDefaultTextInputPadding =
  13. EdgeInsets.only(bottom: Insets.sm, top: 4);
  14. final String? label;
  15. final bool? autoFocus;
  16. final String? initialValue;
  17. final String? hintText;
  18. final EdgeInsets? contentPadding;
  19. final TextStyle? textStyle;
  20. final int? maxLines;
  21. final TextEditingController? controller;
  22. final TextCapitalization? capitalization;
  23. final Function(String)? onChanged;
  24. final Function()? onEditingComplete;
  25. final Function(bool)? onFocusChanged;
  26. final Function(FocusNode)? onFocusCreated;
  27. const StyledFormTextInput(
  28. {Key? key,
  29. this.label,
  30. this.autoFocus,
  31. this.initialValue,
  32. this.onChanged,
  33. this.onEditingComplete,
  34. this.hintText,
  35. this.onFocusChanged,
  36. this.onFocusCreated,
  37. this.controller,
  38. this.contentPadding,
  39. this.capitalization,
  40. this.textStyle,
  41. this.maxLines})
  42. : super(key: key);
  43. @override
  44. Widget build(BuildContext context) {
  45. return StyledSearchTextInput(
  46. capitalization: capitalization,
  47. label: label,
  48. autoFocus: autoFocus,
  49. initialValue: initialValue,
  50. onChanged: onChanged,
  51. onFocusCreated: onFocusCreated,
  52. style: textStyle ?? TextStyles.Body1,
  53. onEditingComplete: onEditingComplete,
  54. onFocusChanged: onFocusChanged,
  55. controller: controller,
  56. maxLines: maxLines,
  57. inputDecoration: InputDecoration(
  58. isDense: true,
  59. contentPadding: contentPadding ?? kDefaultTextInputPadding,
  60. border: const ThinUnderlineBorder(
  61. borderSide: BorderSide(width: 5, color: Colors.red)),
  62. //focusedBorder: UnderlineInputBorder(borderSide: BorderSide(width: .5, color: Colors.red)),
  63. hintText: hintText,
  64. ),
  65. );
  66. }
  67. }
  68. class StyledSearchTextInput extends StatefulWidget {
  69. final String? label;
  70. final TextStyle? style;
  71. final EdgeInsets? contentPadding;
  72. final bool? autoFocus;
  73. final bool? obscureText;
  74. final IconData? icon;
  75. final String? initialValue;
  76. final int? maxLines;
  77. final TextEditingController? controller;
  78. final TextCapitalization? capitalization;
  79. final TextInputType? type;
  80. final bool? enabled;
  81. final bool? autoValidate;
  82. final bool? enableSuggestions;
  83. final bool? autoCorrect;
  84. final String? errorText;
  85. final String? hintText;
  86. final Widget? prefixIcon;
  87. final Widget? suffixIcon;
  88. final InputDecoration? inputDecoration;
  89. final Function(String)? onChanged;
  90. final Function()? onEditingComplete;
  91. final Function()? onEditingCancel;
  92. final Function(bool)? onFocusChanged;
  93. final Function(FocusNode)? onFocusCreated;
  94. final Function(String)? onFieldSubmitted;
  95. final Function(String?)? onSaved;
  96. final VoidCallback? onTap;
  97. const StyledSearchTextInput({
  98. Key? key,
  99. this.label,
  100. this.autoFocus = false,
  101. this.obscureText = false,
  102. this.type = TextInputType.text,
  103. this.icon,
  104. this.initialValue = '',
  105. this.controller,
  106. this.enabled,
  107. this.autoValidate = false,
  108. this.enableSuggestions = true,
  109. this.autoCorrect = true,
  110. this.errorText,
  111. this.style,
  112. this.contentPadding,
  113. this.prefixIcon,
  114. this.suffixIcon,
  115. this.inputDecoration,
  116. this.onChanged,
  117. this.onEditingComplete,
  118. this.onEditingCancel,
  119. this.onFocusChanged,
  120. this.onFocusCreated,
  121. this.onFieldSubmitted,
  122. this.onSaved,
  123. this.onTap,
  124. this.hintText,
  125. this.capitalization,
  126. this.maxLines,
  127. }) : super(key: key);
  128. @override
  129. StyledSearchTextInputState createState() => StyledSearchTextInputState();
  130. }
  131. class StyledSearchTextInputState extends State<StyledSearchTextInput> {
  132. late TextEditingController _controller;
  133. late FocusNode _focusNode;
  134. @override
  135. void initState() {
  136. _controller =
  137. widget.controller ?? TextEditingController(text: widget.initialValue);
  138. _focusNode = FocusNode(
  139. debugLabel: widget.label ?? '',
  140. onKey: (FocusNode node, RawKeyEvent evt) {
  141. if (evt is RawKeyDownEvent) {
  142. if (evt.logicalKey == LogicalKeyboardKey.escape) {
  143. widget.onEditingCancel?.call();
  144. return KeyEventResult.handled;
  145. }
  146. }
  147. return KeyEventResult.ignored;
  148. },
  149. canRequestFocus: true,
  150. );
  151. // Listen for focus out events
  152. _focusNode
  153. .addListener(() => widget.onFocusChanged?.call(_focusNode.hasFocus));
  154. widget.onFocusCreated?.call(_focusNode);
  155. if (widget.autoFocus ?? false) {
  156. scheduleMicrotask(() => _focusNode.requestFocus());
  157. }
  158. super.initState();
  159. }
  160. @override
  161. void dispose() {
  162. _controller.dispose();
  163. _focusNode.dispose();
  164. super.dispose();
  165. }
  166. void clear() => _controller.clear();
  167. String get text => _controller.text;
  168. set text(String value) => _controller.text = value;
  169. @override
  170. Widget build(BuildContext context) {
  171. final theme = context.watch<AppTheme>();
  172. return Container(
  173. padding: EdgeInsets.symmetric(vertical: Insets.sm),
  174. child: TextFormField(
  175. onChanged: widget.onChanged,
  176. onEditingComplete: widget.onEditingComplete,
  177. onFieldSubmitted: widget.onFieldSubmitted,
  178. onSaved: widget.onSaved,
  179. onTap: widget.onTap,
  180. autofocus: widget.autoFocus ?? false,
  181. focusNode: _focusNode,
  182. keyboardType: widget.type,
  183. obscureText: widget.obscureText ?? false,
  184. autocorrect: widget.autoCorrect ?? false,
  185. enableSuggestions: widget.enableSuggestions ?? false,
  186. style: widget.style ?? TextStyles.Body1,
  187. cursorColor: theme.accent1,
  188. controller: _controller,
  189. showCursor: true,
  190. enabled: widget.enabled,
  191. maxLines: widget.maxLines,
  192. textCapitalization: widget.capitalization ?? TextCapitalization.none,
  193. decoration: widget.inputDecoration ??
  194. InputDecoration(
  195. prefixIcon: widget.prefixIcon,
  196. suffixIcon: widget.suffixIcon,
  197. contentPadding:
  198. widget.contentPadding ?? EdgeInsets.all(Insets.m),
  199. border: const OutlineInputBorder(borderSide: BorderSide.none),
  200. isDense: true,
  201. icon: widget.icon == null ? null : Icon(widget.icon),
  202. errorText: widget.errorText,
  203. errorMaxLines: 2,
  204. hintText: widget.hintText,
  205. hintStyle: TextStyles.Body1.textColor(theme.grey),
  206. labelText: widget.label),
  207. ),
  208. );
  209. }
  210. }
  211. class ThinUnderlineBorder extends InputBorder {
  212. /// Creates an underline border for an [InputDecorator].
  213. ///
  214. /// The [borderSide] parameter defaults to [BorderSide.none] (it must not be
  215. /// null). Applications typically do not specify a [borderSide] parameter
  216. /// because the input decorator substitutes its own, using [copyWith], based
  217. /// on the current theme and [InputDecorator.isFocused].
  218. ///
  219. /// The [borderRadius] parameter defaults to a value where the top left
  220. /// and right corners have a circular radius of 4.0. The [borderRadius]
  221. /// parameter must not be null.
  222. const ThinUnderlineBorder({
  223. BorderSide borderSide = const BorderSide(),
  224. this.borderRadius = const BorderRadius.only(
  225. topLeft: Radius.circular(4.0),
  226. topRight: Radius.circular(4.0),
  227. ),
  228. }) : super(borderSide: borderSide);
  229. /// The radii of the border's rounded rectangle corners.
  230. ///
  231. /// When this border is used with a filled input decorator, see
  232. /// [InputDecoration.filled], the border radius defines the shape
  233. /// of the background fill as well as the bottom left and right
  234. /// edges of the underline itself.
  235. ///
  236. /// By default the top right and top left corners have a circular radius
  237. /// of 4.0.
  238. final BorderRadius borderRadius;
  239. @override
  240. bool get isOutline => false;
  241. @override
  242. UnderlineInputBorder copyWith(
  243. {BorderSide? borderSide, BorderRadius? borderRadius}) {
  244. return UnderlineInputBorder(
  245. borderSide: borderSide ?? this.borderSide,
  246. borderRadius: borderRadius ?? this.borderRadius,
  247. );
  248. }
  249. @override
  250. EdgeInsetsGeometry get dimensions {
  251. return EdgeInsets.only(bottom: borderSide.width);
  252. }
  253. @override
  254. UnderlineInputBorder scale(double t) {
  255. return UnderlineInputBorder(borderSide: borderSide.scale(t));
  256. }
  257. @override
  258. Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
  259. return Path()
  260. ..addRect(Rect.fromLTWH(rect.left, rect.top, rect.width,
  261. math.max(0.0, rect.height - borderSide.width)));
  262. }
  263. @override
  264. Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
  265. return Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
  266. }
  267. @override
  268. ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
  269. if (a is UnderlineInputBorder) {
  270. final newBorderRadius =
  271. BorderRadius.lerp(a.borderRadius, borderRadius, t);
  272. if (newBorderRadius != null) {
  273. return UnderlineInputBorder(
  274. borderSide: BorderSide.lerp(a.borderSide, borderSide, t),
  275. borderRadius: newBorderRadius,
  276. );
  277. }
  278. }
  279. return super.lerpFrom(a, t);
  280. }
  281. @override
  282. ShapeBorder? lerpTo(ShapeBorder? b, double t) {
  283. if (b is UnderlineInputBorder) {
  284. final newBorderRadius =
  285. BorderRadius.lerp(b.borderRadius, borderRadius, t);
  286. if (newBorderRadius != null) {
  287. return UnderlineInputBorder(
  288. borderSide: BorderSide.lerp(borderSide, b.borderSide, t),
  289. borderRadius: newBorderRadius,
  290. );
  291. }
  292. }
  293. return super.lerpTo(b, t);
  294. }
  295. /// Draw a horizontal line at the bottom of [rect].
  296. ///
  297. /// The [borderSide] defines the line's color and weight. The `textDirection`
  298. /// `gap` and `textDirection` parameters are ignored.
  299. /// @override
  300. @override
  301. void paint(
  302. Canvas canvas,
  303. Rect rect, {
  304. double? gapStart,
  305. double gapExtent = 0.0,
  306. double gapPercentage = 0.0,
  307. TextDirection? textDirection,
  308. }) {
  309. if (borderRadius.bottomLeft != Radius.zero ||
  310. borderRadius.bottomRight != Radius.zero) {
  311. canvas.clipPath(getOuterPath(rect, textDirection: textDirection));
  312. }
  313. canvas.drawLine(rect.bottomLeft, rect.bottomRight, borderSide.toPaint());
  314. }
  315. @override
  316. bool operator ==(Object other) {
  317. if (identical(this, other)) return true;
  318. if (other.runtimeType != runtimeType) return false;
  319. return other is InputBorder && other.borderSide == borderSide;
  320. }
  321. @override
  322. int get hashCode => borderSide.hashCode;
  323. }