Line data Source code
1 : import 'dart:typed_data';
2 : import 'dart:ui' as ui;
3 :
4 : import 'package:flutter/material.dart';
5 : import 'package:flutter/rendering.dart';
6 : import 'package:flutter/services.dart';
7 : import 'package:mvvm_builder/mvvm_builder.dart';
8 : import 'package:pal/src/database/entity/helper/helper_entity.dart';
9 : import 'package:pal/src/database/entity/helper/helper_trigger_type.dart';
10 : import 'package:pal/src/database/entity/helper/helper_type.dart';
11 : import 'package:pal/src/injectors/editor_app/editor_app_injector.dart';
12 : import 'package:pal/src/services/editor/helper/helper_editor_service.dart';
13 : import 'package:pal/src/services/pal/pal_state_service.dart';
14 : import 'package:pal/src/ui/editor/pages/helper_details/helper_details_view.dart';
15 : import 'package:pal/src/ui/editor/pages/helpers_list/helpers_list_loader.dart';
16 : import 'package:pal/src/ui/editor/pages/helpers_list/helpers_list_modal_presenter.dart';
17 : import 'package:pal/src/ui/editor/pages/helpers_list/helpers_list_modal_viewmodel.dart';
18 : import 'package:pal/src/ui/editor/pages/helpers_list/widgets/helper_tile_widget.dart';
19 : import 'package:pal/src/ui/editor/pages/create_helper/create_helper.dart';
20 : import 'package:pal/src/ui/editor/pages/helper_details/helper_details_model.dart';
21 :
22 : abstract class HelpersListModalView {
23 :
24 : void lookupHostedAppStruct(GlobalKey<NavigatorState> hostedAppNavigatorKey);
25 :
26 : void processElement(Element element, {int n = 0});
27 :
28 : Future<void> capturePng(
29 : final HelpersListModalPresenter presenter,
30 : final HelpersListModalModel model,
31 : );
32 :
33 : Future<bool> openHelperCreationPage(
34 : final String pageId,
35 : );
36 :
37 : Future<void> openAppSettingsPage();
38 :
39 : Future<HelperDetailsPopState> openHelperDetailPage(
40 : final HelperEntity helperEntity,
41 : final String pageId,
42 : final String pageRouteName,
43 : );
44 :
45 : void reorganizeHelper(
46 : final int oldIndex,
47 : final int newIndex,
48 : final HelpersListModalPresenter presenter,
49 : final List<HelperEntity> helpers,
50 : );
51 :
52 : void onCloseButton();
53 :
54 : void popModalDialog();
55 : }
56 :
57 : class HelpersListModal extends StatefulWidget {
58 : final GlobalKey<NavigatorState>
59 : hostedAppNavigatorKey; //FIXME remove this from here
60 :
61 : final GlobalKey repaintBoundaryKey;
62 : final BuildContext bottomModalContext;
63 : final HelpersListModalLoader loader;
64 : final PalEditModeStateService palEditModeStateService;
65 : final EditorHelperService helperService;
66 :
67 2 : HelpersListModal({
68 : Key key,
69 : this.loader,
70 : this.helperService,
71 : this.hostedAppNavigatorKey,
72 : this.repaintBoundaryKey,
73 : this.bottomModalContext,
74 : this.palEditModeStateService,
75 : });
76 :
77 2 : @override
78 2 : _HelpersListModalState createState() => _HelpersListModalState();
79 : }
80 :
81 : class _HelpersListModalState extends State<HelpersListModal>
82 : implements HelpersListModalView {
83 : final ScrollController _listController = ScrollController();
84 :
85 : final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey(); // ignore: unused_field
86 : final _mvvmPageBuilder =
87 : MVVMPageBuilder<HelpersListModalPresenter, HelpersListModalModel>();
88 :
89 2 : @override
90 : Widget build(BuildContext context) {
91 4 : return _mvvmPageBuilder.build(
92 2 : key: ValueKey('pal_HelpersListModal_MvvmBuilder'),
93 : context: context,
94 4 : presenterBuilder: (context) => HelpersListModalPresenter(
95 : this,
96 4 : loader: this.widget.loader ??
97 1 : HelpersListModalLoader(
98 2 : EditorInjector.of(context).pageEditorService,
99 2 : EditorInjector.of(context).helperService,
100 2 : EditorInjector.of(context).routeObserver),
101 4 : palEditModeStateService: this.widget.palEditModeStateService ??
102 2 : EditorInjector.of(context).palEditModeStateService,
103 4 : helperService: this.widget.helperService ??
104 2 : EditorInjector.of(context).helperService,
105 : ),
106 2 : builder: (context, presenter, model) {
107 2 : return Scaffold(
108 2 : body: this._buildPage(
109 2 : context.buildContext,
110 : presenter,
111 : model,
112 : ),
113 : );
114 : },
115 : );
116 : }
117 :
118 2 : Widget _buildPage(
119 : final BuildContext context,
120 : final HelpersListModalPresenter presenter,
121 : final HelpersListModalModel model,
122 : ) {
123 2 : return SafeArea(
124 2 : child: Padding(
125 : padding: const EdgeInsets.only(top: 15.0, bottom: 5.0),
126 2 : child: Column(
127 : mainAxisSize: MainAxisSize.max,
128 2 : children: [
129 2 : Padding(
130 : padding: const EdgeInsets.symmetric(horizontal: 24.0),
131 2 : child: _buildHeader(context, model, presenter),
132 : ),
133 2 : Expanded(
134 2 : child: Padding(
135 : padding: const EdgeInsets.symmetric(vertical: 5.0),
136 2 : child: _buildList(context, presenter, model),
137 : ),
138 : ),
139 2 : Padding(
140 : padding: const EdgeInsets.only(
141 : bottom: 5.0,
142 : top: 2.0,
143 : ),
144 6 : child: !model.isLoading && model.helpers != null && model.helpers.isNotEmpty
145 1 : ? Text(
146 : '💡 You can re-order helpers by long tap on them.',
147 1 : key: ValueKey('pal_HelpersListModal_ReorderTip'),
148 1 : style: TextStyle(
149 : fontSize: 12.0,
150 : ),
151 : )
152 2 : : Container(),
153 : ),
154 2 : Padding(
155 : padding: const EdgeInsets.symmetric(horizontal: 24.0),
156 2 : child: Align(
157 : alignment: Alignment.bottomCenter,
158 2 : child: _buildCloseButton(context),
159 : ),
160 : )
161 : ],
162 : ),
163 : ),
164 : );
165 : }
166 :
167 2 : Widget _buildCloseButton(
168 : final BuildContext context,
169 : ) {
170 2 : return SizedBox(
171 : width: double.infinity,
172 2 : child: OutlineButton(
173 2 : key: ValueKey('pal_HelpersListModal_Close'),
174 2 : child: Text(
175 : 'Close',
176 2 : style: TextStyle(
177 : fontWeight: FontWeight.w300,
178 4 : color: Theme.of(context).accentColor,
179 : ),
180 : ),
181 2 : onPressed: onCloseButton,
182 2 : borderSide: BorderSide(
183 4 : color: Theme.of(context).accentColor,
184 : ),
185 2 : shape: RoundedRectangleBorder(
186 2 : borderRadius: BorderRadius.circular(8.0),
187 : ),
188 : ),
189 : );
190 : }
191 :
192 2 : Widget _buildList(
193 : final BuildContext context,
194 : final HelpersListModalPresenter presenter,
195 : final HelpersListModalModel model,
196 : ) {
197 5 : return (model.helpers != null && model.helpers.length > 0)
198 1 : ? ReorderableListView(
199 2 : onReorder: (oldIndex, newIndex) => this.reorganizeHelper(
200 : oldIndex,
201 : newIndex,
202 : presenter,
203 1 : model.helpers,
204 : ),
205 : padding: const EdgeInsets.only(top: 8.0),
206 1 : key: ValueKey('palHelpersListModalContent'),
207 1 : scrollController: _listController
208 1 : ..addListener(() {
209 0 : if (_listController.position.extentAfter <= 100) {
210 0 : presenter.loadMore();
211 : }
212 : }),
213 1 : children: _buildHelpersList(model, presenter),
214 : )
215 2 : : Center(
216 2 : key: ValueKey('palHelpersListModalNoHelpers'),
217 3 : child: (model.isLoading || model.loadingMore)
218 2 : ? CircularProgressIndicator()
219 1 : : Text('No helpers on this page.'),
220 : );
221 : }
222 :
223 1 : List<Widget> _buildHelpersList(
224 : HelpersListModalModel model, HelpersListModalPresenter presenter) {
225 1 : List<Widget> helpers = [];
226 :
227 : int index = 0;
228 2 : for (HelperEntity anHelper in model.helpers) {
229 1 : Widget cell = Padding(
230 3 : key: ValueKey('pal_HelpersListModal_Tile${index++}'),
231 : padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 24.0),
232 1 : child: HelperTileWidget(
233 1 : name: anHelper?.name,
234 2 : trigger: getHelperTriggerTypeDescription(anHelper?.triggerType),
235 1 : versionMin: anHelper?.versionMin,
236 1 : versionMax: anHelper?.versionMax,
237 : isDisabled: false,
238 2 : type: getHelperTypeDescription(anHelper?.type),
239 1 : onTapCallback: () {
240 1 : HapticFeedback.selectionClick();
241 1 : presenter.onClickHelper(anHelper);
242 : },
243 : // onTapCallback: () =>
244 : // this.openHelperDetailPage(anHelper, model.pageId, presenter),
245 : ),
246 : );
247 1 : helpers.add(cell);
248 : }
249 : return helpers;
250 : }
251 :
252 2 : Widget _buildHeader(
253 : final BuildContext context,
254 : final HelpersListModalModel model,
255 : final HelpersListModalPresenter presenter,
256 : ) {
257 2 : return Row(
258 : mainAxisAlignment: MainAxisAlignment.spaceBetween,
259 : crossAxisAlignment: CrossAxisAlignment.center,
260 2 : children: [
261 2 : Image.asset(
262 : 'packages/pal/assets/images/logo.png',
263 : height: 36.0,
264 : ),
265 2 : SizedBox(width: 12),
266 2 : Column(
267 : crossAxisAlignment: CrossAxisAlignment.start,
268 2 : children: [
269 2 : Text(
270 : 'PAL editor',
271 2 : style: TextStyle(
272 : fontSize: 20,
273 : fontWeight: FontWeight.w500,
274 : ),
275 : ),
276 2 : SizedBox(height: 3.0),
277 2 : Text(
278 : 'List of available helpers on this page',
279 2 : style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w300),
280 : )
281 : ],
282 : ),
283 2 : Flexible(
284 2 : child: Row(
285 : mainAxisAlignment: MainAxisAlignment.end,
286 : mainAxisSize: MainAxisSize.max,
287 2 : children: [
288 2 : _buildCircleButton(
289 : 'pal_HelpersListModal_Settings',
290 2 : Icon(
291 : Icons.settings,
292 : size: 20,
293 : ),
294 2 : presenter.onClickSettings,
295 : ),
296 2 : SizedBox(width: 14.0),
297 2 : _buildCircleButton(
298 : 'pal_HelpersListModal_New',
299 2 : Icon(
300 : Icons.add,
301 : size: 25,
302 : ),
303 2 : presenter.onClickAdd,
304 : ),
305 : ],
306 : ),
307 : ),
308 : ],
309 : );
310 : }
311 :
312 0 : @override
313 : void lookupHostedAppStruct(GlobalKey<NavigatorState> hostedAppNavigatorKey) {
314 : if (hostedAppNavigatorKey == null) {
315 : return;
316 : }
317 : }
318 :
319 0 : @override
320 : processElement(Element element, {int n = 0}) {
321 0 : if (element.widget.key != null) {
322 : var parentObject =
323 0 : widget.repaintBoundaryKey.currentContext.findRenderObject();
324 0 : if (element.widget is Scaffold) {
325 0 : print("SCAFFOLD");
326 : }
327 : var translation =
328 0 : element.renderObject.getTransformTo(parentObject).getTranslation();
329 0 : print("$n - key " +
330 0 : element.widget.key.toString() +
331 0 : " " +
332 0 : element.size.toString());
333 0 : print("translation ${translation.t} ${translation.r} ${translation.s}");
334 0 : print(
335 0 : "::bounds ${element.renderObject.paintBounds.shift(Offset(translation.x, translation.y))}");
336 0 : print("::bounds ${parentObject.paintBounds}");
337 : }
338 0 : element.visitChildElements((visitor) => processElement(visitor, n: n + 1));
339 : }
340 :
341 : @override
342 0 : Future<void> capturePng(
343 : final HelpersListModalPresenter presenter,
344 : final HelpersListModalModel model,
345 : ) async {
346 : try {
347 : RenderRepaintBoundary boundary =
348 0 : widget.repaintBoundaryKey.currentContext.findRenderObject();
349 0 : ui.Image image = await boundary.toImage(pixelRatio: 3.0);
350 : ByteData byteData =
351 0 : await image.toByteData(format: ui.ImageByteFormat.png);
352 :
353 0 : presenter.setImage(byteData);
354 : } catch (e) {
355 0 : print('error while catching screenshot');
356 0 : print(e);
357 : }
358 : }
359 :
360 : @override
361 1 : Future<bool> openHelperCreationPage(
362 : final String pageId,
363 : ) async {
364 1 : HapticFeedback.selectionClick();
365 : // Display the helper creation view
366 2 : final shouldOpenEditor = await Navigator.pushNamed(
367 1 : context,
368 : '/editor/new',
369 1 : arguments: CreateHelperPageArguments(
370 2 : widget.hostedAppNavigatorKey,
371 : pageId,
372 : ),
373 : );
374 :
375 : if (shouldOpenEditor != null && shouldOpenEditor) {
376 : // Dismiss the bottom modal when next was tapped
377 0 : Navigator.pop(widget.bottomModalContext);
378 : }
379 : return shouldOpenEditor;
380 : }
381 :
382 : @override
383 1 : Future<HelperDetailsPopState> openHelperDetailPage(
384 : final HelperEntity helperEntity,
385 : final String pageId,
386 : final String pageRouteName,
387 : ) async {
388 : // Display the helper detail view
389 2 : final helperDetailsPopState = await Navigator.pushNamed(
390 1 : context,
391 : '/editor/helper',
392 1 : arguments: HelperDetailsComponentArguments(
393 2 : widget.hostedAppNavigatorKey,
394 : helperEntity,
395 : pageId,
396 : pageRouteName
397 : ),
398 : ) as HelperDetailsPopState;
399 :
400 : return helperDetailsPopState;
401 : }
402 :
403 : @override
404 0 : Future openAppSettingsPage() async {
405 0 : HapticFeedback.selectionClick();
406 : // Display the helper creation view
407 0 : final shouldOpenEditor = await Navigator.pushNamed(
408 0 : context,
409 : '/settings',
410 : );
411 :
412 : if (shouldOpenEditor != null && shouldOpenEditor) {
413 : // Dismiss the bottom modal when next was tapped
414 0 : Navigator.pop(widget.bottomModalContext);
415 : }
416 : }
417 :
418 2 : Widget _buildCircleButton(
419 : final String key,
420 : final Icon icon,
421 : final Function callback,
422 : ) {
423 2 : return SizedBox(
424 : height: 32.0,
425 : width: 32.0,
426 2 : child: FloatingActionButton(
427 : heroTag: key,
428 2 : key: ValueKey(key),
429 : onPressed: callback,
430 : child: icon,
431 2 : shape: CircleBorder(),
432 : ),
433 : );
434 : }
435 :
436 1 : @override
437 : void reorganizeHelper(
438 : int oldIndex,
439 : int newIndex,
440 : HelpersListModalPresenter presenter,
441 : List<HelperEntity> helpers,
442 : ) {
443 : // First backup list before re-organize
444 1 : presenter.backupHelpersList();
445 :
446 : // Change on Front
447 1 : if (newIndex > oldIndex) {
448 1 : newIndex -= 1;
449 : }
450 1 : final HelperEntity helperEntity = helpers.removeAt(oldIndex);
451 1 : helpers.insert(newIndex, helperEntity);
452 1 : presenter.refreshView();
453 :
454 : // Then submit change on back
455 1 : presenter.sendNewHelpersOrder(oldIndex, newIndex);
456 : }
457 :
458 0 : @override
459 : void onCloseButton() {
460 0 : HapticFeedback.selectionClick();
461 0 : Navigator.pop(context);
462 : }
463 :
464 0 : @override
465 : void popModalDialog() {
466 0 : Navigator.pop(widget.bottomModalContext);
467 : }
468 : }
|