Line data Source code
1 : import 'dart:async';
2 :
3 : import 'package:dotted_border/dotted_border.dart';
4 : import 'package:flutter/material.dart';
5 : import 'package:flutter/services.dart';
6 : import 'package:flutter/widgets.dart';
7 : import 'package:pal/src/router.dart';
8 : import 'package:pal/src/ui/editor/pages/helper_editor/font_editor/font_editor.dart';
9 : import 'package:pal/src/ui/editor/pages/helper_editor/font_editor/font_editor_viewmodel.dart';
10 : import 'package:pal/src/ui/editor/pages/helper_editor/font_editor/pickers/font_weight_picker/font_weight_picker_loader.dart';
11 : import 'package:pal/src/ui/editor/pages/helper_editor/helper_editor_notifiers.dart';
12 : import 'package:pal/src/ui/editor/pages/helper_editor/widgets/color_picker.dart';
13 : import 'package:pal/src/ui/editor/widgets/edit_helper_toolbar.dart';
14 : import 'package:pal/src/ui/shared/widgets/overlayed.dart';
15 :
16 78 : enum ToolbarType { text, border }
17 :
18 : typedef OnFieldChanged(String id, String value);
19 :
20 : typedef OnTextStyleChanged(String id, TextStyle style, FontKeys fontkeys);
21 :
22 : class EditableTextField extends StatefulWidget {
23 : // Keys
24 : // static final GlobalKey<_EditableTextFieldState> globalKey = GlobalKey();
25 : final String id;
26 : final Key textFormFieldKey;
27 : final Key backgroundContainerKey;
28 : final Key helperToolbarKey;
29 :
30 : // Textfield stuff
31 : final AutovalidateMode autovalidate;
32 : final BoxDecoration backgroundBoxDecoration;
33 : final EdgeInsetsGeometry backgroundPadding;
34 : final EdgeInsetsGeometry textFormFieldPadding;
35 : final OnFieldChanged onChanged;
36 : final OnTextStyleChanged onTextStyleChanged;
37 : final TextInputType keyboardType;
38 : final TextStyle textStyle;
39 : final int maxLines;
40 : final int maximumCharacterLength;
41 : final int minimumCharacterLength;
42 : final String hintText;
43 : final List<TextInputFormatter> inputFormatters;
44 : final Stream<bool> outsideTapStream;
45 : final ToolbarType toolbarType;
46 : final String initialValue;
47 : final String fontFamilyKey;
48 :
49 4 : EditableTextField({
50 : Key key,
51 : this.id,
52 : this.textFormFieldKey,
53 : this.backgroundContainerKey,
54 : this.helperToolbarKey,
55 : this.outsideTapStream,
56 : this.maximumCharacterLength,
57 : this.minimumCharacterLength,
58 : this.onChanged,
59 : this.onTextStyleChanged,
60 : this.autovalidate = AutovalidateMode.onUserInteraction,
61 : this.backgroundPadding,
62 : this.textFormFieldPadding,
63 : this.backgroundBoxDecoration,
64 : this.maxLines = 1,
65 : this.fontFamilyKey,
66 : this.inputFormatters,
67 : this.hintText = 'Edit me!',
68 : this.keyboardType,
69 : this.initialValue,
70 : this.toolbarType = ToolbarType.text,
71 : @required this.textStyle,
72 4 : }) : super();
73 :
74 4 : factory EditableTextField.text({
75 : Key key,
76 : final String id,
77 : final Key textFormFieldKey,
78 : final Key backgroundContainerKey,
79 : final Key helperToolbarKey,
80 : final AutovalidateMode autovalidate = AutovalidateMode.onUserInteraction,
81 : final BoxDecoration backgroundBoxDecoration,
82 : final EdgeInsetsGeometry backgroundPadding,
83 : final EdgeInsetsGeometry textFormFieldPadding,
84 : final String Function(String) validator,
85 : final OnFieldChanged onChanged,
86 : final OnTextStyleChanged onTextStyleChanged,
87 : final TextInputType keyboardType,
88 : final TextStyle textStyle,
89 : final int maxLines = 1,
90 : final int maximumCharacterLength,
91 : final int minimumCharacterLength,
92 : final String hintText = 'Edit me!',
93 : final List<TextInputFormatter> inputFormatters,
94 : final Stream<bool> outsideTapStream,
95 : final String initialValue,
96 : final String fontFamilyKey,
97 : }) {
98 4 : return EditableTextField(
99 : key: key,
100 : id: id,
101 : textFormFieldKey: textFormFieldKey,
102 : backgroundContainerKey: backgroundContainerKey,
103 : helperToolbarKey: helperToolbarKey,
104 : outsideTapStream: outsideTapStream,
105 : maximumCharacterLength: maximumCharacterLength,
106 : minimumCharacterLength: minimumCharacterLength,
107 : onTextStyleChanged: onTextStyleChanged,
108 : onChanged: onChanged,
109 : fontFamilyKey: fontFamilyKey,
110 : autovalidate: autovalidate,
111 : backgroundPadding: backgroundPadding,
112 : textFormFieldPadding: textFormFieldPadding,
113 : backgroundBoxDecoration: backgroundBoxDecoration,
114 : maxLines: maxLines,
115 : inputFormatters: inputFormatters,
116 : hintText: hintText,
117 : keyboardType: keyboardType,
118 : textStyle: textStyle,
119 : toolbarType: ToolbarType.text,
120 : initialValue: initialValue,
121 : );
122 : }
123 :
124 : // factory EditableTextField.fromTextFormFieldNotifier({
125 : // final Stream<bool> outsideTapStream,
126 : // final TextFormFieldNotifier textFormFieldNotifier,
127 : // final TextStyle mergeWithFonts
128 : // }) => EditableTextField.text(
129 : // outsideTapStream: outsideTapStream,
130 : // fontFamilyKey: textFormFieldNotifier.fontFamily?.value,
131 : // initialValue: textFormFieldNotifier.text?.value,
132 : // textStyle: TextStyle(
133 : // color: textFormFieldNotifier.fontColor.value,
134 : // decoration: TextDecoration.none,
135 : // fontSize: textFormFieldNotifier.fontSize?.value?.toDouble(),
136 : // fontWeight: FontWeightMapper.toFontWeight(
137 : // textFormFieldNotifier.fontWeight?.value,
138 : // ),
139 : // ).merge(mergeWithFonts)
140 : // );
141 :
142 0 : factory EditableTextField.border({
143 : Key key,
144 : final String id,
145 : final Key textFormFieldKey,
146 : final Key backgroundContainerKey,
147 : final Key helperToolbarKey,
148 : final AutovalidateMode autovalidate = AutovalidateMode.onUserInteraction,
149 : final BoxDecoration backgroundBoxDecoration,
150 : final EdgeInsetsGeometry backgroundPadding,
151 : final EdgeInsetsGeometry textFormFieldPadding,
152 : final String Function(String) validator,
153 : final Function(String, String) onChanged,
154 : final Function(String, TextStyle, FontKeys) onTextStyleChanged,
155 : final TextInputType keyboardType,
156 : final TextStyle textStyle,
157 : final int maxLines = 1,
158 : final int maximumCharacterLength,
159 : final int minimumCharacterLength,
160 : final String hintText = 'Edit me!',
161 : final List<TextInputFormatter> inputFormatters,
162 : final Stream<bool> outsideTapStream,
163 : final String initialValue,
164 : final String fontFamilyKey,
165 : }) {
166 0 : return EditableTextField(
167 : key: key,
168 : id: id,
169 : textFormFieldKey: textFormFieldKey,
170 : backgroundContainerKey: backgroundContainerKey,
171 : helperToolbarKey: helperToolbarKey,
172 : outsideTapStream: outsideTapStream,
173 : maximumCharacterLength: maximumCharacterLength,
174 : minimumCharacterLength: minimumCharacterLength,
175 : onChanged: onChanged,
176 : onTextStyleChanged: onTextStyleChanged,
177 : autovalidate: autovalidate,
178 : backgroundPadding: backgroundPadding,
179 : textFormFieldPadding: textFormFieldPadding,
180 : backgroundBoxDecoration: backgroundBoxDecoration,
181 : maxLines: maxLines,
182 : inputFormatters: inputFormatters,
183 : hintText: hintText,
184 : keyboardType: keyboardType,
185 : textStyle: textStyle,
186 : toolbarType: ToolbarType.border,
187 : initialValue: initialValue,
188 : fontFamilyKey: fontFamilyKey,
189 : );
190 : }
191 :
192 4 : @override
193 4 : _EditableTextFieldState createState() => _EditableTextFieldState();
194 : }
195 :
196 : class _EditableTextFieldState extends State<EditableTextField> {
197 : bool _isToolbarVisible = false;
198 : FocusNode _focusNode = FocusNode();
199 : StreamSubscription _outsideSub;
200 : TextStyle _textStyle;
201 : String _fontFamilyKey;
202 :
203 4 : @override
204 : void initState() {
205 4 : super.initState();
206 : // Install listener when focus change
207 12 : _focusNode.addListener(_onFocusChange);
208 12 : _fontFamilyKey = widget.fontFamilyKey ?? 'Montserrat';
209 : // Listen on stream when outside tap is detected
210 15 : _outsideSub = widget.outsideTapStream?.listen((event) {
211 : if (event) {
212 0 : this._onClose();
213 : }
214 : });
215 12 : _textStyle = widget.textStyle;
216 : }
217 :
218 4 : @override
219 : void dispose() {
220 7 : _outsideSub?.cancel();
221 12 : _focusNode.removeListener(_onFocusChange);
222 4 : super.dispose();
223 : }
224 :
225 4 : @override
226 : Widget build(BuildContext context) {
227 4 : return Padding(
228 : padding: const EdgeInsets.all(1.0),
229 4 : child: Column(
230 4 : children: [
231 16 : if (_isToolbarVisible) _buildToolbar(widget.toolbarType),
232 4 : Padding(
233 : // FIXME: This is used to show element even if keyboard is shown
234 : // should be better to find a way to not use it :/
235 8 : padding: widget.backgroundPadding ?? EdgeInsets.zero,
236 4 : child: DottedBorder(
237 4 : dashPattern: [6, 3],
238 4 : color: Colors.white.withAlpha(80),
239 4 : child: Container(
240 8 : decoration: widget.backgroundBoxDecoration,
241 8 : key: widget.backgroundContainerKey,
242 4 : child: Padding(
243 8 : padding: widget.textFormFieldPadding ?? EdgeInsets.zero,
244 4 : child: TextFormField(
245 8 : key: widget.textFormFieldKey,
246 8 : autovalidateMode: widget.autovalidate,
247 4 : focusNode: _focusNode,
248 4 : onTap: _onTextFieldTapped,
249 4 : onChanged: (String newValue) {
250 8 : if (widget.onChanged != null) {
251 12 : widget.onChanged(
252 20 : widget.id ?? widget.textFormFieldKey.toString(),
253 : newValue);
254 : }
255 : },
256 4 : validator: (String value) {
257 : String error;
258 8 : if (widget.minimumCharacterLength != null) {
259 : if (value != null &&
260 16 : value.length < widget.minimumCharacterLength) {
261 : error =
262 6 : 'Minimum ${widget.minimumCharacterLength} ${widget.minimumCharacterLength <= 1 ? 'character' : 'characters'} allowed';
263 : }
264 : }
265 8 : if (widget.maximumCharacterLength != null) {
266 : if (value != null &&
267 16 : value.length >= widget.maximumCharacterLength) {
268 : error =
269 6 : 'Maximum ${widget.maximumCharacterLength} ${widget.maximumCharacterLength <= 1 ? 'character' : 'characters'} allowed';
270 : }
271 : }
272 : return error;
273 : },
274 8 : initialValue: widget.initialValue,
275 8 : keyboardType: widget.keyboardType,
276 8 : maxLines: widget.maxLines ?? 1,
277 : minLines: 1,
278 4 : onFieldSubmitted: _onFieldSubmitted,
279 7 : cursorColor: _textStyle?.color,
280 8 : inputFormatters: widget.inputFormatters,
281 4 : decoration: InputDecoration(
282 : border: InputBorder.none,
283 : enabledBorder: InputBorder.none,
284 8 : hintText: widget.hintText,
285 7 : hintStyle: _textStyle?.merge(
286 3 : TextStyle(
287 9 : color: _textStyle?.color?.withAlpha(80),
288 : ),
289 : ),
290 : ),
291 : textAlign: TextAlign.center,
292 4 : style: _textStyle,
293 : ),
294 : ),
295 : ),
296 : ),
297 : ),
298 : ],
299 : ),
300 : );
301 : }
302 :
303 4 : _buildToolbar(ToolbarType toolbarType) {
304 : Widget toolbar;
305 : switch (toolbarType) {
306 4 : case ToolbarType.text:
307 4 : toolbar = EditHelperToolbar.text(
308 8 : key: widget.helperToolbarKey,
309 4 : onChangeTextColor: _onChangeTextColor,
310 4 : onChangeTextFont: _onChangeTextFont,
311 4 : onClose: _onClose,
312 : );
313 : break;
314 0 : case ToolbarType.border:
315 0 : toolbar = EditHelperToolbar.border(
316 0 : key: widget.helperToolbarKey,
317 0 : onChangeTextColor: _onChangeTextColor,
318 0 : onChangeTextFont: _onChangeTextFont,
319 0 : onChangeBorder: _onChangeBorder,
320 0 : onClose: _onClose,
321 : );
322 : break;
323 : default:
324 : }
325 : return toolbar;
326 : }
327 :
328 : // Textfield stuff
329 4 : _onTextFieldTapped() {
330 8 : _focusNode.requestFocus();
331 8 : setState(() {
332 4 : _isToolbarVisible = true;
333 : });
334 : }
335 :
336 4 : _onFocusChange() {
337 8 : if (!_focusNode.hasFocus) {
338 6 : setState(() {
339 3 : _isToolbarVisible = false;
340 : });
341 : }
342 : }
343 :
344 0 : _onFieldSubmitted(String newValue) {
345 0 : this._onClose();
346 : }
347 :
348 0 : _onChangeTextFont() {
349 0 : var widgetBuilder = (context) => FontEditorDialogPage(
350 0 : onCancelPicker: () => Navigator.of(context).pop(),
351 0 : onValidatePicker: () => Navigator.of(context).pop(),
352 0 : actualTextStyle: _textStyle,
353 0 : fontFamilyKey: _fontFamilyKey,
354 0 : onFontModified: (TextStyle newTextStyle, FontKeys fontKeys) {
355 0 : setState(() {
356 0 : _textStyle = _textStyle.merge(
357 : newTextStyle,
358 : );
359 0 : _isToolbarVisible = true;
360 : });
361 :
362 0 : if (fontKeys?.fontFamilyNameKey != null &&
363 0 : fontKeys.fontFamilyNameKey.length > 0) {
364 0 : _fontFamilyKey = fontKeys.fontFamilyNameKey;
365 : }
366 0 : if (widget.onTextStyleChanged != null) {
367 0 : widget.onTextStyleChanged(
368 0 : widget.id ?? widget.textFormFieldKey.toString(),
369 0 : _textStyle,
370 : fontKeys,
371 : );
372 : }
373 : },
374 : );
375 0 : showDialog(context: context, builder: widgetBuilder);
376 : }
377 :
378 0 : _onChangeTextColor() {
379 0 : var widgetBuilder = (context) => ColorPickerDialog(
380 0 : placeholderColor: _textStyle?.color,
381 0 : onCancel: () => Navigator.of(context).pop(),
382 0 : onColorSelected: (Color newColor) {
383 0 : setState(() {
384 0 : _textStyle = _textStyle.merge(TextStyle(
385 : color: newColor,
386 : ));
387 0 : _isToolbarVisible = true;
388 : });
389 0 : if (widget.onTextStyleChanged != null) {
390 0 : widget.onTextStyleChanged(
391 0 : widget.id ?? widget.textFormFieldKey.toString(),
392 0 : _textStyle,
393 : null,
394 : );
395 : }
396 0 : Navigator.of(context).pop();
397 : },
398 : );
399 0 : showDialog(context: context, builder: widgetBuilder);
400 : }
401 :
402 0 : _onChangeBorder() {}
403 :
404 1 : _onClose() {
405 2 : _focusNode.unfocus();
406 2 : setState(() {
407 1 : _isToolbarVisible = false;
408 : });
409 : }
410 : }
|