Line data Source code
1 : import 'package:flutter/material.dart';
2 : import 'package:flutter/services.dart';
3 : import 'package:google_fonts/google_fonts.dart';
4 : import 'package:keyboard_visibility/keyboard_visibility.dart';
5 : import 'package:mvvm_builder/mvvm_builder.dart';
6 : import 'package:pal/src/database/entity/graphic_entity.dart';
7 : import 'package:pal/src/database/entity/helper/helper_entity.dart';
8 : import 'package:pal/src/injectors/editor_app/editor_app_injector.dart';
9 : import 'package:pal/src/services/editor/helper/helper_editor_service.dart';
10 : import 'package:pal/src/services/pal/pal_state_service.dart';
11 : import 'package:pal/src/theme.dart';
12 : import 'package:pal/src/ui/editor/pages/helper_editor/font_editor/pickers/font_weight_picker/font_weight_picker_loader.dart';
13 : import 'package:pal/src/ui/editor/pages/helper_editor/helper_editor.dart';
14 : import 'package:pal/src/ui/editor/pages/helper_editor/helper_editor_notifiers.dart';
15 : import 'package:pal/src/ui/editor/pages/helper_editor/helper_editor_viewmodel.dart';
16 : import 'package:pal/src/ui/editor/pages/helper_editor/widgets/color_picker.dart';
17 : import 'package:pal/src/ui/editor/pages/helper_editor/widgets/editor_actionsbar.dart';
18 : import 'package:pal/src/ui/editor/pages/helper_editor/widgets/editor_sending_overlay.dart';
19 : import 'package:pal/src/ui/editor/pages/media_gallery/media_gallery.dart';
20 : import 'package:pal/src/ui/editor/widgets/editable_background.dart';
21 : import 'package:pal/src/ui/editor/widgets/editable_media.dart';
22 : import 'package:pal/src/ui/editor/widgets/editable_textfield.dart';
23 : import 'package:pal/src/ui/shared/widgets/circle_button.dart';
24 : import 'package:pal/src/ui/shared/widgets/overlayed.dart';
25 :
26 : import '../../../../../../router.dart';
27 : import 'editor_update_helper_presenter.dart';
28 : import 'editor_update_helper_viewmodel.dart';
29 :
30 : abstract class EditorUpdateHelperView {
31 :
32 : void showColorPickerDialog(Color color, OnColorSelected onColorSelected, OnCancelPicker onCancel);
33 :
34 : void closeColorPickerDialog();
35 :
36 : void hidePalBubble();
37 :
38 : Future<void> scrollToBottomChangelogList();
39 :
40 : Future<GraphicEntity> pushToMediaGallery(final String mediaId);
41 :
42 : Future showLoadingScreen(ValueNotifier<SendingStatus> status);
43 :
44 : Future closeEditor();
45 :
46 : void closeLoadingScreen();
47 :
48 : }
49 :
50 : class EditorUpdateHelperPage extends StatelessWidget {
51 :
52 : // required params
53 : final UpdateHelperViewModel baseviewModel;
54 : final HelperEditorPageArguments arguments;
55 : final EditorHelperService helperService;
56 : final PalEditModeStateService palEditModeStateService;
57 :
58 : // inner page widgets
59 : final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();
60 : final GlobalKey<FormState> formKey = GlobalKey<FormState>();
61 : final ScrollController scrollController = ScrollController();
62 :
63 1 : EditorUpdateHelperPage._({
64 : Key key,
65 : this.helperService,
66 : this.palEditModeStateService,
67 : @required this.baseviewModel,
68 : @required this.arguments,
69 1 : }) : super(key: key);
70 :
71 1 : factory EditorUpdateHelperPage.create({
72 : Key key,
73 : HelperEditorPageArguments parameters,
74 : EditorHelperService helperService,
75 : PalEditModeStateService palEditModeStateService,
76 : @required HelperViewModel helperViewModel
77 1 : }) => EditorUpdateHelperPage._(
78 : key: key,
79 : helperService: helperService,
80 : palEditModeStateService: palEditModeStateService,
81 1 : baseviewModel: UpdateHelperViewModel.fromHelperViewModel(helperViewModel),
82 : arguments: parameters,
83 : );
84 :
85 1 : factory EditorUpdateHelperPage.edit({
86 : Key key,
87 : HelperEditorPageArguments parameters,
88 : PalEditModeStateService palEditModeStateService,
89 : EditorHelperService helperService,
90 : @required HelperEntity helperEntity //FIXME should be an id and not entire entity
91 1 : }) => EditorUpdateHelperPage._(
92 : key: key,
93 : helperService: helperService,
94 : palEditModeStateService: palEditModeStateService,
95 1 : baseviewModel: UpdateHelperViewModel.fromHelperEntity(helperEntity),
96 : arguments: parameters,
97 : );
98 :
99 :
100 1 : @override
101 : Widget build(BuildContext context) {
102 2 : return MVVMPageBuilder<EditorUpdateHelperPresenter, UpdateHelperViewModel>().build(
103 1 : key: ValueKey('pal_EditorUpdateHelperWidget_Builder'),
104 : context: context,
105 1 : presenterBuilder: (context) {
106 1 : var presenter = EditorUpdateHelperPresenter(
107 1 : _EditorUpdateHelperPage(
108 2 : context, _scaffoldKey, scrollController,
109 3 : palEditModeStateService ?? EditorInjector.of(context).palEditModeStateService
110 : ),
111 1 : baseviewModel,
112 1 : helperService ?? EditorInjector.of(context).helperService,
113 1 : arguments
114 : );
115 1 : KeyboardVisibilityNotification()
116 2 : .addNewListener(onChange: presenter.onKeyboardVisibilityChange);
117 : return presenter;
118 : },
119 3 : builder: (context, presenter, model) => this._buildPage(context.buildContext, presenter, model),
120 : );
121 : }
122 :
123 1 : Widget _buildPage(
124 : final BuildContext context,
125 : final EditorUpdateHelperPresenter presenter,
126 : final UpdateHelperViewModel viewModel,
127 : ) {
128 1 : return Scaffold(
129 1 : key: _scaffoldKey,
130 : resizeToAvoidBottomPadding: true,
131 : backgroundColor: Colors.transparent,
132 1 : body: EditorActionsBar(
133 1 : onCancel: presenter.onCancel,
134 1 : onValidate: presenter.onValidate,
135 1 : canValidate: viewModel.canValidate,
136 1 : child: GestureDetector(
137 1 : onTap: presenter.onOutsideTap,
138 1 : child: SafeArea(
139 : bottom: false,
140 1 : child: Form(
141 1 : key: formKey,
142 : autovalidateMode: AutovalidateMode.always,
143 1 : child: EditableBackground(
144 3 : backgroundColor: viewModel.bodyBox?.backgroundColor?.value,
145 : circleIconKey: 'pal_EditorUpdateHelperWidget_BackgroundColorPicker',
146 1 : onColorChange: presenter.changeBackgroundColor,
147 1 : widget: Padding(
148 : padding: const EdgeInsets.all(2.0),
149 1 : child: Container(
150 : width: double.infinity,
151 3 : color: viewModel.bodyBox?.backgroundColor?.value,
152 1 : child: Column(
153 : mainAxisAlignment: MainAxisAlignment.spaceBetween,
154 1 : children: _buildEditableContent(presenter, viewModel, context),
155 : ),
156 : ),
157 : ),
158 : ),
159 : ),
160 : ),
161 : ),
162 : ),
163 : );
164 : }
165 :
166 1 : List<Widget> _buildEditableContent(
167 : final EditorUpdateHelperPresenter presenter,
168 : final UpdateHelperViewModel viewModel,
169 : final BuildContext context)
170 1 : => [
171 1 : Expanded(
172 1 : child: Center(
173 1 : child: SingleChildScrollView(
174 : reverse: false,
175 1 : controller: scrollController,
176 1 : child: Padding(
177 : padding: const EdgeInsets.only(
178 : left: 10.0,
179 : right: 10.0,
180 : top: 25.0,
181 : ),
182 1 : child: Column(
183 : crossAxisAlignment: CrossAxisAlignment.center,
184 1 : children: [
185 1 : EditableMedia(
186 : editKey: 'pal_EditorUpdateHelperWidget_EditableMedia_EditButton',
187 : mediaSize: 123.0,
188 1 : onEdit: presenter.editMedia,
189 3 : url: viewModel.media?.url?.value,
190 : ),
191 1 : SizedBox(height: 40),
192 1 : _buildTitleField(context, presenter, viewModel),
193 1 : SizedBox(height: 25.0),
194 1 : _buildChangelogFields(context, presenter, viewModel),
195 : ],
196 : ),
197 : ),
198 : ),
199 : ),
200 : ),
201 1 : Padding(
202 : padding: const EdgeInsets.only(
203 : bottom: 15.0,
204 : left: 10.0,
205 : right: 10.0,
206 : ),
207 1 : child: _buildThanksButton(context, presenter, viewModel),
208 : ),
209 : ];
210 :
211 1 : Widget _buildTitleField(
212 : final BuildContext context,
213 : final EditorUpdateHelperPresenter presenter,
214 : final UpdateHelperViewModel viewModel,
215 : ) {
216 1 : return EditableTextField.text(
217 1 : helperToolbarKey: ValueKey(
218 : 'pal_EditorUpdateHelperWidget_TitleToolbar',
219 : ),
220 1 : textFormFieldKey: ValueKey(
221 : 'pal_EditorUpdateHelperWidget_TitleField',
222 : ),
223 2 : hintText: viewModel.titleField?.hintText,
224 1 : onChanged: presenter.onTitleFieldChanged,
225 1 : onTextStyleChanged: presenter.onTitleTextStyleChanged,
226 1 : validator: presenter.validateTitleTextField,
227 : autovalidate: AutovalidateMode.disabled,
228 : maximumCharacterLength: 60,
229 : minimumCharacterLength: 1,
230 3 : fontFamilyKey: viewModel?.titleField?.fontFamily?.value,
231 2 : outsideTapStream: presenter.editableTextFieldController.stream,
232 3 : initialValue: viewModel?.titleField?.text?.value,
233 1 : textStyle: TextStyle(
234 3 : color: viewModel.titleField?.fontColor?.value,
235 4 : fontSize: viewModel.titleField?.fontSize?.value?.toDouble(),
236 1 : fontWeight: FontWeightMapper.toFontWeight(
237 3 : viewModel.titleField?.fontWeight?.value,
238 : ),
239 5 : ).merge(googleCustomFont(viewModel.titleField?.fontFamily?.value)),
240 : );
241 : }
242 :
243 1 : Widget _buildChangelogFields(
244 : final BuildContext context,
245 : final EditorUpdateHelperPresenter presenter,
246 : final UpdateHelperViewModel viewmodel,
247 : ) {
248 1 : List<Widget> changelogsTextfieldWidgets = List();
249 3 : viewmodel.changelogsFields.forEach((key, field) {
250 2 : changelogsTextfieldWidgets.add(editableField(
251 2 : presenter.editableTextFieldController.stream,
252 : field,
253 1 : presenter.onChangelogTextChanged,
254 1 : presenter.onChangelogTextStyleFieldChanged,
255 : id: key
256 : ));
257 : });
258 1 : return Column(
259 1 : children: [
260 1 : Wrap(
261 : children: changelogsTextfieldWidgets,
262 : spacing: 5.0,
263 : runSpacing: 5.0,
264 : ),
265 1 : Padding(
266 : padding: const EdgeInsets.only(top: 5.0, bottom: 16.0),
267 1 : child: CircleIconButton(
268 1 : key: ValueKey('pal_EditorUpdateHelperWidget_AddNote'),
269 : backgroundColor: Colors.transparent,
270 1 : icon: Icon(
271 : Icons.add,
272 1 : color: Colors.white.withAlpha(170),
273 : size: 35.0,
274 : ),
275 : displayShadow: false,
276 1 : onTapCallback: () {
277 1 : HapticFeedback.selectionClick();
278 1 : presenter.addChangelogNote();
279 : },
280 : ),
281 : ),
282 : ],
283 : );
284 : }
285 :
286 1 : Widget _buildThanksButton(
287 : final BuildContext context,
288 : final EditorUpdateHelperPresenter presenter,
289 : final UpdateHelperViewModel viewModel,
290 : ) {
291 1 : return SizedBox(
292 : width: double.infinity,
293 1 : child: EditableTextField.text(
294 1 : helperToolbarKey: ValueKey('pal_EditorUpdateHelperWidget_ThanksButtonToolbar',),
295 1 : textFormFieldKey: ValueKey('pal_EditorUpdateHelperWidget_ThanksButtonField',),
296 2 : outsideTapStream: presenter.editableTextFieldController.stream,
297 1 : onChanged: presenter.onThanksFieldChanged,
298 1 : onTextStyleChanged: presenter.onThanksTextStyleFieldChanged,
299 2 : hintText: viewModel.thanksButton?.hintText,
300 : maximumCharacterLength: 25,
301 3 : fontFamilyKey: viewModel?.thanksButton?.fontFamily?.value,
302 3 : initialValue: viewModel?.thanksButton?.text?.value,
303 1 : backgroundBoxDecoration: BoxDecoration(
304 3 : color: PalTheme.of(context).colors.dark,
305 1 : borderRadius: BorderRadius.circular(10.0),
306 : ),
307 1 : textStyle: TextStyle(
308 3 : color: viewModel.thanksButton?.fontColor?.value ?? Colors.white,
309 4 : fontSize: viewModel.thanksButton?.fontSize?.value?.toDouble() ?? 22.0,
310 4 : fontWeight: FontWeightMapper.toFontWeight(viewModel.thanksButton?.fontWeight?.value) ?? FontWeight.w400,
311 5 : ).merge(googleCustomFont(viewModel.thanksButton?.fontFamily?.value)),
312 : ),
313 : );
314 : }
315 :
316 :
317 :
318 1 : EditableTextField editableField(
319 : Stream<bool> outsideTapStream,
320 : TextFormFieldNotifier textNotifier,
321 : OnFieldChanged onFieldValueChange,
322 : OnTextStyleChanged onTextStyleChanged,
323 : { String id,
324 : Key helperToolbarKey,
325 : Key textFormFieldKey,
326 : TextStyle baseStyle,
327 : int minimumCharacterLength = 1,
328 : int maximumCharacterLength = 255,
329 : int maxLines = 5,
330 : BoxDecoration backgroundDecoration})
331 1 : => EditableTextField.text(
332 : id: id,
333 : backgroundBoxDecoration: backgroundDecoration,
334 : outsideTapStream: outsideTapStream,
335 : helperToolbarKey: helperToolbarKey,
336 : textFormFieldKey: textFormFieldKey,
337 : onChanged: onFieldValueChange,
338 : onTextStyleChanged: onTextStyleChanged,
339 : maximumCharacterLength: maximumCharacterLength,
340 : minimumCharacterLength: minimumCharacterLength,
341 : maxLines: maxLines,
342 2 : fontFamilyKey: textNotifier?.fontFamily?.value,
343 2 : initialValue: textNotifier?.text?.value,
344 1 : textStyle: TextStyle(
345 2 : color: textNotifier?.fontColor?.value,
346 : decoration: TextDecoration.none,
347 3 : fontSize: textNotifier?.fontSize?.value?.toDouble(),
348 3 : fontWeight: FontWeightMapper.toFontWeight(textNotifier?.fontWeight?.value),
349 : )
350 1 : .merge(baseStyle),
351 : );
352 :
353 :
354 : //FIXME CONsider extension
355 1 : TextStyle googleCustomFont(String fontFamily) {
356 2 : return (fontFamily != null && fontFamily.length > 0)
357 1 : ? GoogleFonts.getFont(fontFamily)
358 : : null;
359 : }
360 : }
361 :
362 : class _EditorUpdateHelperPage with EditorSendingOverlayMixin, EditorNavigationMixin implements EditorUpdateHelperView {
363 :
364 : final BuildContext context;
365 : final GlobalKey<ScaffoldState> scaffoldKey;
366 : final ScrollController scrollController;
367 : final PalEditModeStateService palEditModeStateService;
368 :
369 1 : _EditorUpdateHelperPage(this.context, this.scaffoldKey, this.scrollController, this.palEditModeStateService);
370 :
371 2 : BuildContext get overlayContext => context;
372 :
373 1 : @override
374 : void showColorPickerDialog(Color color, OnColorSelected onColorSelected, OnCancelPicker onCancel) {
375 1 : HapticFeedback.selectionClick();
376 1 : showOverlayedInContext(
377 2 : (context) => ColorPickerDialog(
378 : placeholderColor: color,
379 : onColorSelected: onColorSelected,
380 : onCancel: onCancel
381 : ),
382 : key: OverlayKeys.PAGE_OVERLAY_KEY
383 : );
384 : }
385 :
386 0 : @override
387 0 : void closeColorPickerDialog() => closeOverlayed(OverlayKeys.PAGE_OVERLAY_KEY);
388 :
389 : @override
390 0 : Future<GraphicEntity> pushToMediaGallery(final String mediaId) async {
391 0 : final media = await Navigator.pushNamed(
392 0 : scaffoldKey.currentContext,
393 : '/editor/media-gallery',
394 0 : arguments: MediaGalleryPageArguments(
395 : mediaId,
396 : ),
397 : ) as GraphicEntity;
398 : return media;
399 : }
400 :
401 1 : Future scrollToBottomChangelogList() async {
402 2 : if (scrollController.hasClients) {
403 3 : await scrollController.animateTo(
404 3 : scrollController.position.maxScrollExtent,
405 : curve: Curves.easeOut,
406 : duration: const Duration(milliseconds: 500),
407 : );
408 : }
409 : }
410 :
411 1 : @override
412 3 : void hidePalBubble() => this.palEditModeStateService.showBubble(context, false);
413 :
414 : }
|