From c0237fc7d9276e774fd8012e9355fa64b3cdf01d Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Tue, 30 Oct 2018 22:36:41 -0700 Subject: [PATCH 01/11] Moved editor toolbar to overlay --- packages/zefyr/lib/src/widgets/buttons.dart | 1 + packages/zefyr/lib/src/widgets/editor.dart | 94 ++++++++++++++++--- packages/zefyr/lib/src/widgets/selection.dart | 3 - 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/packages/zefyr/lib/src/widgets/buttons.dart b/packages/zefyr/lib/src/widgets/buttons.dart index b96bc0a08..eb65028f7 100644 --- a/packages/zefyr/lib/src/widgets/buttons.dart +++ b/packages/zefyr/lib/src/widgets/buttons.dart @@ -424,6 +424,7 @@ class _LinkButtonState extends State { } Widget buildOverlay(BuildContext context) { + final toolbar = ZefyrToolbar.of(context); final style = toolbar.editor.selectionStyle; diff --git a/packages/zefyr/lib/src/widgets/editor.dart b/packages/zefyr/lib/src/widgets/editor.dart index 2f6d2d86f..743412e5a 100644 --- a/packages/zefyr/lib/src/widgets/editor.dart +++ b/packages/zefyr/lib/src/widgets/editor.dart @@ -184,6 +184,37 @@ class _ZefyrEditorState extends State { final FocusNode _toolbarFocusNode = new FocusNode(); ZefyrImageDelegate _imageDelegate; ZefyrEditorScope _scope; + ZefyrThemeData _themeData; + + OverlayEntry _toolbar; + OverlayState _overlay; + + void showToolbar() { + _toolbar = new OverlayEntry( + builder: (context) => _ZefyrToolbarContainer( + theme: _themeData, + toolbar: ZefyrToolbar( + focusNode: _toolbarFocusNode, + editor: _scope, + delegate: widget.toolbarDelegate, + ), + ), + ); + _overlay.insert(_toolbar); + } + + void hideToolbar() { + _toolbar?.remove(); + _toolbar = null; + } + + void _handleChange() { + if (_scope.focusOwner == FocusOwner.none) { + hideToolbar(); + } else if (_toolbar == null) { + showToolbar(); + } + } @override void initState() { @@ -195,6 +226,7 @@ class _ZefyrEditorState extends State { controller: widget.controller, focusNode: widget.focusNode, ); + _scope.addListener(_handleChange); } @override @@ -208,8 +240,28 @@ class _ZefyrEditorState extends State { } } + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + final parentTheme = ZefyrTheme.of(context, nullOk: true); + final fallbackTheme = ZefyrThemeData.fallback(context); + _themeData = (parentTheme != null) + ? fallbackTheme.merge(parentTheme) + : fallbackTheme; + + final overlay = Overlay.of(context, debugRequiredFor: widget); + if (_overlay != overlay) { + hideToolbar(); + _overlay = overlay; + // TODO: update toolbar. + } + } + @override void dispose() { + hideToolbar(); + _scope.removeListener(_handleChange); _scope.dispose(); _toolbarFocusNode.dispose(); super.dispose(); @@ -228,21 +280,18 @@ class _ZefyrEditorState extends State { final children = []; children.add(Expanded(child: editable)); - final toolbar = ZefyrToolbar( - editor: _scope, - focusNode: _toolbarFocusNode, - delegate: widget.toolbarDelegate, - ); - children.add(toolbar); - - final parentTheme = ZefyrTheme.of(context, nullOk: true); - final fallbackTheme = ZefyrThemeData.fallback(context); - final actualTheme = (parentTheme != null) - ? fallbackTheme.merge(parentTheme) - : fallbackTheme; + if (_toolbar != null) { + children.add(SizedBox(height: ZefyrToolbar.kToolbarHeight)); + } +// final toolbar = ZefyrToolbar( +// editor: _scope, +// focusNode: _toolbarFocusNode, +// delegate: widget.toolbarDelegate, +// ); +// children.add(toolbar); return ZefyrTheme( - data: actualTheme, + data: _themeData, child: _ZefyrEditorScope( scope: _scope, child: Column(children: children), @@ -250,3 +299,22 @@ class _ZefyrEditorState extends State { ); } } + +class _ZefyrToolbarContainer extends StatelessWidget { + final ZefyrThemeData theme; + final Widget toolbar; + + const _ZefyrToolbarContainer({Key key, this.theme, this.toolbar}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final media = MediaQuery.of(context); + return Positioned( + bottom: media.viewInsets.bottom, + left: 0.0, + right: 0.0, + child: ZefyrTheme(data: theme, child: toolbar), + ); + } +} diff --git a/packages/zefyr/lib/src/widgets/selection.dart b/packages/zefyr/lib/src/widgets/selection.dart index d858f8b31..27c9be4b2 100644 --- a/packages/zefyr/lib/src/widgets/selection.dart +++ b/packages/zefyr/lib/src/widgets/selection.dart @@ -82,7 +82,6 @@ class _ZefyrSelectionOverlayState extends State editable: editable, controls: widget.controls, delegate: this, - visible: true, ), ), ); @@ -423,13 +422,11 @@ class _SelectionToolbar extends StatefulWidget { @required this.editable, @required this.controls, @required this.delegate, - @required this.visible, }) : super(key: key); final ZefyrEditableTextScope editable; final TextSelectionControls controls; final TextSelectionDelegate delegate; - final bool visible; @override _SelectionToolbarState createState() => new _SelectionToolbarState(); From c1485749108fd6b09b3d8919d90ca2945d2d7d1c Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Fri, 2 Nov 2018 14:58:02 -0700 Subject: [PATCH 02/11] Re-arranged handling of focus scope so that it works with toolbar in an Overlay --- packages/zefyr/lib/src/widgets/buttons.dart | 42 +++++++++---- packages/zefyr/lib/src/widgets/editor.dart | 59 ++++++++++++------- packages/zefyr/lib/src/widgets/toolbar.dart | 31 ++++------ packages/zefyr/test/widgets/buttons_test.dart | 1 + .../zefyr/test/widgets/selection_test.dart | 2 +- 5 files changed, 83 insertions(+), 52 deletions(-) diff --git a/packages/zefyr/lib/src/widgets/buttons.dart b/packages/zefyr/lib/src/widgets/buttons.dart index eb65028f7..534526581 100644 --- a/packages/zefyr/lib/src/widgets/buttons.dart +++ b/packages/zefyr/lib/src/widgets/buttons.dart @@ -300,11 +300,31 @@ class LinkButton extends StatefulWidget { } class _LinkButtonState extends State { - final TextEditingController _inputController = new TextEditingController(); + final FocusNode _focusNode = FocusNode(); + final TextEditingController _inputController = TextEditingController(); Key _inputKey; bool _formatError = false; + ZefyrEditorScope _editor; + bool get isEditing => _inputKey != null; + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final toolbar = ZefyrToolbar.of(context); + if (_editor != toolbar.editor) { + _editor?.setToolbarFocusNode(null); + _editor = toolbar.editor; + _editor.setToolbarFocusNode(_focusNode); + } + } + @override Widget build(BuildContext context) { final toolbar = ZefyrToolbar.of(context); @@ -376,7 +396,7 @@ class _LinkButtonState extends State { _inputController.text = ''; _inputController.removeListener(_handleInputChange); toolbar.markNeedsRebuild(); - toolbar.editor.focus(context); + toolbar.editor.focus(); } }); } @@ -388,7 +408,7 @@ class _LinkButtonState extends State { _inputKey = null; _inputController.text = ''; _inputController.removeListener(_handleInputChange); - editor.focus(context); + editor.focus(); }); } } @@ -424,7 +444,6 @@ class _LinkButtonState extends State { } Widget buildOverlay(BuildContext context) { - final toolbar = ZefyrToolbar.of(context); final style = toolbar.editor.selectionStyle; @@ -438,7 +457,7 @@ class _LinkButtonState extends State { : _LinkInput( key: _inputKey, controller: _inputController, - focusNode: toolbar.editor.toolbarFocusNode, + focusNode: _focusNode, formatError: _formatError, ); final items = [Expanded(child: body)]; @@ -517,13 +536,14 @@ class _LinkInputState extends State<_LinkInput> { keyboardType: TextInputType.url, focusNode: widget.focusNode, controller: widget.controller, - autofocus: true, +// autofocus: true, decoration: new InputDecoration( - hintText: 'https://', - filled: true, - fillColor: toolbarTheme.color, - border: InputBorder.none, - contentPadding: const EdgeInsets.all(10.0)), + hintText: 'https://', + filled: true, + fillColor: toolbarTheme.color, + border: InputBorder.none, + contentPadding: const EdgeInsets.all(10.0), + ), ); } } diff --git a/packages/zefyr/lib/src/widgets/editor.dart b/packages/zefyr/lib/src/widgets/editor.dart index 743412e5a..8f128f9d7 100644 --- a/packages/zefyr/lib/src/widgets/editor.dart +++ b/packages/zefyr/lib/src/widgets/editor.dart @@ -15,15 +15,14 @@ class ZefyrEditorScope extends ChangeNotifier { @required ZefyrImageDelegate imageDelegate, @required ZefyrController controller, @required FocusNode focusNode, - @required FocusNode toolbarFocusNode, + @required FocusScopeNode focusScope, }) : _controller = controller, _imageDelegate = imageDelegate, - _focusNode = focusNode, - _toolbarFocusNode = toolbarFocusNode { + _focusScope = focusScope, + _focusNode = focusNode { _selectionStyle = _controller.getSelectionStyle(); _selection = _controller.selection; _controller.addListener(_handleControllerChange); - toolbarFocusNode.addListener(_handleFocusChange); _focusNode.addListener(_handleFocusChange); } @@ -32,9 +31,9 @@ class ZefyrEditorScope extends ChangeNotifier { ZefyrImageDelegate _imageDelegate; ZefyrImageDelegate get imageDelegate => _imageDelegate; + FocusScopeNode get focusScope => _focusScope; + FocusScopeNode _focusScope; FocusNode _focusNode; - FocusNode _toolbarFocusNode; - FocusNode get toolbarFocusNode => _toolbarFocusNode; ZefyrController _controller; NotusStyle get selectionStyle => _selectionStyle; @@ -46,7 +45,6 @@ class ZefyrEditorScope extends ChangeNotifier { void dispose() { assert(!_disposed); _controller.removeListener(_handleControllerChange); - _toolbarFocusNode.removeListener(_handleFocusChange); _focusNode.removeListener(_handleFocusChange); _disposed = true; super.dispose(); @@ -102,11 +100,23 @@ class ZefyrEditorScope extends ChangeNotifier { notifyListeners(); } + FocusNode _toolbarFocusNode; + + void setToolbarFocusNode(FocusNode node) { + assert(!_disposed); + if (_toolbarFocusNode != node) { + _toolbarFocusNode?.removeListener(_handleFocusChange); + _toolbarFocusNode = node; + _toolbarFocusNode.addListener(_handleFocusChange); + notifyListeners(); + } + } + FocusOwner get focusOwner { assert(!_disposed); if (_focusNode.hasFocus) { return FocusOwner.editor; - } else if (toolbarFocusNode.hasFocus) { + } else if (_toolbarFocusNode?.hasFocus == true) { return FocusOwner.toolbar; } else { return FocusOwner.none; @@ -124,9 +134,9 @@ class ZefyrEditorScope extends ChangeNotifier { _controller.formatSelection(value); } - void focus(BuildContext context) { + void focus() { assert(!_disposed); - FocusScope.of(context).requestFocus(_focusNode); + _focusScope.requestFocus(_focusNode); } void hideKeyboard() { @@ -181,7 +191,6 @@ class ZefyrEditor extends StatefulWidget { } class _ZefyrEditorState extends State { - final FocusNode _toolbarFocusNode = new FocusNode(); ZefyrImageDelegate _imageDelegate; ZefyrEditorScope _scope; ZefyrThemeData _themeData; @@ -194,7 +203,6 @@ class _ZefyrEditorState extends State { builder: (context) => _ZefyrToolbarContainer( theme: _themeData, toolbar: ZefyrToolbar( - focusNode: _toolbarFocusNode, editor: _scope, delegate: widget.toolbarDelegate, ), @@ -213,6 +221,10 @@ class _ZefyrEditorState extends State { hideToolbar(); } else if (_toolbar == null) { showToolbar(); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + _toolbar?.markNeedsBuild(); + }); } } @@ -220,13 +232,6 @@ class _ZefyrEditorState extends State { void initState() { super.initState(); _imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate(); - _scope = ZefyrEditorScope( - toolbarFocusNode: _toolbarFocusNode, - imageDelegate: _imageDelegate, - controller: widget.controller, - focusNode: widget.focusNode, - ); - _scope.addListener(_handleChange); } @override @@ -244,6 +249,21 @@ class _ZefyrEditorState extends State { void didChangeDependencies() { super.didChangeDependencies(); + if (_scope == null) { + _scope = ZefyrEditorScope( + imageDelegate: _imageDelegate, + controller: widget.controller, + focusNode: widget.focusNode, + focusScope: FocusScope.of(context), + ); + _scope.addListener(_handleChange); + } else { + final focusScope = FocusScope.of(context); + if (focusScope != _scope._focusScope) { + _scope._focusScope = focusScope; + } + } + final parentTheme = ZefyrTheme.of(context, nullOk: true); final fallbackTheme = ZefyrThemeData.fallback(context); _themeData = (parentTheme != null) @@ -263,7 +283,6 @@ class _ZefyrEditorState extends State { hideToolbar(); _scope.removeListener(_handleChange); _scope.dispose(); - _toolbarFocusNode.dispose(); super.dispose(); } diff --git a/packages/zefyr/lib/src/widgets/toolbar.dart b/packages/zefyr/lib/src/widgets/toolbar.dart index 8d12adf22..ed3e8eff0 100644 --- a/packages/zefyr/lib/src/widgets/toolbar.dart +++ b/packages/zefyr/lib/src/widgets/toolbar.dart @@ -102,13 +102,11 @@ class ZefyrToolbar extends StatefulWidget implements PreferredSizeWidget { const ZefyrToolbar({ Key key, - @required this.focusNode, @required this.editor, this.autoHide: true, this.delegate, }) : super(key: key); - final FocusNode focusNode; final ZefyrToolbarDelegate delegate; final ZefyrEditorScope editor; @@ -187,21 +185,12 @@ class ZefyrToolbarState extends State ZefyrEditorScope get editor => widget.editor; - void _handleChange() { - if (_selection != editor.selection) { - _selection = editor.selection; - closeOverlay(); - } - setState(() {}); - } - @override void initState() { super.initState(); _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate(); _overlayAnimation = new AnimationController( vsync: this, duration: Duration(milliseconds: 100)); - widget.editor.addListener(_handleChange); } @override @@ -210,15 +199,14 @@ class ZefyrToolbarState extends State if (widget.delegate != oldWidget.delegate) { _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate(); } - if (widget.editor != oldWidget.editor) { - oldWidget.editor.removeListener(_handleChange); - widget.editor.addListener(_handleChange); + if (_selection != editor.selection) { + _selection = editor.selection; + closeOverlay(); } } @override void dispose() { - widget.editor.removeListener(_handleChange); super.dispose(); } @@ -253,11 +241,14 @@ class ZefyrToolbarState extends State final constraints = BoxConstraints.tightFor(height: ZefyrToolbar.kToolbarHeight); - return new _ZefyrToolbarScope( - toolbar: this, - child: Container( - constraints: constraints, - child: Stack(children: layers), + return FocusScope( + node: editor.focusScope, + child: _ZefyrToolbarScope( + toolbar: this, + child: Container( + constraints: constraints, + child: Stack(children: layers), + ), ), ); } diff --git a/packages/zefyr/test/widgets/buttons_test.dart b/packages/zefyr/test/widgets/buttons_test.dart index 9adc37694..034342e02 100644 --- a/packages/zefyr/test/widgets/buttons_test.dart +++ b/packages/zefyr/test/widgets/buttons_test.dart @@ -104,6 +104,7 @@ void main() { await tester .tap(find.widgetWithText(GestureDetector, 'Tap to edit link')); await tester.pumpAndSettle(); + expect(editor.focusNode.hasFocus, isFalse); await editor.updateSelection(base: 10, extent: 10); expect(find.byIcon(Icons.link_off), findsNothing); }); diff --git a/packages/zefyr/test/widgets/selection_test.dart b/packages/zefyr/test/widgets/selection_test.dart index 562153bb6..05d535461 100644 --- a/packages/zefyr/test/widgets/selection_test.dart +++ b/packages/zefyr/test/widgets/selection_test.dart @@ -65,7 +65,7 @@ void main() { RenderBox renderObject = tester.firstRenderObject(find.byType(ZefyrEditableText)); var offset = renderObject.localToGlobal(Offset.zero); - offset += Offset(50.0, renderObject.size.height - 5.0); + offset += Offset(50.0, renderObject.size.height - 500.0); await tester.tapAt(offset); await tester.pumpAndSettle(); expect(editor.controller.selection.isCollapsed, isTrue); From ca41a41e01aa3f3f70090ea6cda10c9d03942390 Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Fri, 2 Nov 2018 15:22:54 -0700 Subject: [PATCH 03/11] Dispose overlay animation in the toolbar --- packages/zefyr/lib/src/widgets/toolbar.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/zefyr/lib/src/widgets/toolbar.dart b/packages/zefyr/lib/src/widgets/toolbar.dart index ed3e8eff0..fc24e4e2e 100644 --- a/packages/zefyr/lib/src/widgets/toolbar.dart +++ b/packages/zefyr/lib/src/widgets/toolbar.dart @@ -207,6 +207,7 @@ class ZefyrToolbarState extends State @override void dispose() { + _overlayAnimation.dispose(); super.dispose(); } From 53c1de6a4488b5522d5712563727cc9e8de3eb47 Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Fri, 2 Nov 2018 16:22:36 -0700 Subject: [PATCH 04/11] Moved focus node to LinkInput to avoid lifycycle issues, fixed tests --- packages/zefyr/lib/src/widgets/buttons.dart | 53 +++++++++---------- packages/zefyr/lib/src/widgets/editor.dart | 3 +- packages/zefyr/lib/src/widgets/toolbar.dart | 1 + packages/zefyr/test/widgets/buttons_test.dart | 3 +- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/packages/zefyr/lib/src/widgets/buttons.dart b/packages/zefyr/lib/src/widgets/buttons.dart index 534526581..f28c7fbb8 100644 --- a/packages/zefyr/lib/src/widgets/buttons.dart +++ b/packages/zefyr/lib/src/widgets/buttons.dart @@ -300,7 +300,6 @@ class LinkButton extends StatefulWidget { } class _LinkButtonState extends State { - final FocusNode _focusNode = FocusNode(); final TextEditingController _inputController = TextEditingController(); Key _inputKey; bool _formatError = false; @@ -308,23 +307,6 @@ class _LinkButtonState extends State { bool get isEditing => _inputKey != null; - @override - void dispose() { - _focusNode.dispose(); - super.dispose(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final toolbar = ZefyrToolbar.of(context); - if (_editor != toolbar.editor) { - _editor?.setToolbarFocusNode(null); - _editor = toolbar.editor; - _editor.setToolbarFocusNode(_focusNode); - } - } - @override Widget build(BuildContext context) { final toolbar = ZefyrToolbar.of(context); @@ -457,7 +439,6 @@ class _LinkButtonState extends State { : _LinkInput( key: _inputKey, controller: _inputController, - focusNode: _focusNode, formatError: _formatError, ); final items = [Expanded(child: body)]; @@ -494,16 +475,13 @@ class _LinkButtonState extends State { } class _LinkInput extends StatefulWidget { - final FocusNode focusNode; final TextEditingController controller; final bool formatError; - const _LinkInput({ - Key key, - @required this.focusNode, - @required this.controller, - this.formatError: false, - }) : super(key: key); + const _LinkInput( + {Key key, @required this.controller, this.formatError: false}) + : super(key: key); + @override _LinkInputState createState() { return new _LinkInputState(); @@ -511,20 +489,37 @@ class _LinkInput extends StatefulWidget { } class _LinkInputState extends State<_LinkInput> { + final FocusNode _focusNode = FocusNode(); + bool _didAutoFocus = false; + ZefyrEditorScope _editor; @override void didChangeDependencies() { super.didChangeDependencies(); + final toolbar = ZefyrToolbar.of(context); if (!_didAutoFocus) { - FocusScope.of(context).requestFocus(widget.focusNode); + FocusScope.of(context).requestFocus(_focusNode); _didAutoFocus = true; } + + if (_editor != toolbar.editor) { + _editor?.setToolbarFocusNode(null); + _editor = toolbar.editor; + _editor.setToolbarFocusNode(_focusNode); + } + } + + @override + void dispose() { + _focusNode.dispose(); + _editor = null; + super.dispose(); } @override Widget build(BuildContext context) { - FocusScope.of(context).reparentIfNeeded(widget.focusNode); + FocusScope.of(context).reparentIfNeeded(_focusNode); final theme = Theme.of(context); final toolbarTheme = ZefyrTheme.of(context).toolbarTheme; @@ -534,7 +529,7 @@ class _LinkInputState extends State<_LinkInput> { return TextField( style: style, keyboardType: TextInputType.url, - focusNode: widget.focusNode, + focusNode: _focusNode, controller: widget.controller, // autofocus: true, decoration: new InputDecoration( diff --git a/packages/zefyr/lib/src/widgets/editor.dart b/packages/zefyr/lib/src/widgets/editor.dart index 8f128f9d7..badf2b63f 100644 --- a/packages/zefyr/lib/src/widgets/editor.dart +++ b/packages/zefyr/lib/src/widgets/editor.dart @@ -108,7 +108,8 @@ class ZefyrEditorScope extends ChangeNotifier { _toolbarFocusNode?.removeListener(_handleFocusChange); _toolbarFocusNode = node; _toolbarFocusNode.addListener(_handleFocusChange); - notifyListeners(); + // We do not notify listeners here because it will happen when + // focus state changes. } } diff --git a/packages/zefyr/lib/src/widgets/toolbar.dart b/packages/zefyr/lib/src/widgets/toolbar.dart index fc24e4e2e..54f4d6b10 100644 --- a/packages/zefyr/lib/src/widgets/toolbar.dart +++ b/packages/zefyr/lib/src/widgets/toolbar.dart @@ -191,6 +191,7 @@ class ZefyrToolbarState extends State _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate(); _overlayAnimation = new AnimationController( vsync: this, duration: Duration(milliseconds: 100)); + _selection = editor.selection; } @override diff --git a/packages/zefyr/test/widgets/buttons_test.dart b/packages/zefyr/test/widgets/buttons_test.dart index 034342e02..9585e66cf 100644 --- a/packages/zefyr/test/widgets/buttons_test.dart +++ b/packages/zefyr/test/widgets/buttons_test.dart @@ -119,7 +119,8 @@ void main() { .tap(find.widgetWithText(GestureDetector, 'Tap to edit link')); await tester.pumpAndSettle(); // TODO: figure out why below finder finds 2 instances of TextField - expect(find.widgetWithText(TextField, 'https://'), findsWidgets); + expect(find.byType(TextField), findsOneWidget); + await tester.enterText(find.widgetWithText(TextField, 'https://').first, 'https://github.com'); await tester.pumpAndSettle(); From 3345df5ec2e9ec8471997029f766c4342255934b Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Mon, 5 Nov 2018 09:17:01 -0800 Subject: [PATCH 05/11] Introduced ZefyrScaffold instead of putting toolbar in Overlay --- packages/zefyr/example/lib/main.dart | 127 +++++------------- packages/zefyr/example/lib/src/form.dart | 78 +++++++++++ packages/zefyr/example/lib/src/full_page.dart | 108 +++++++++++++++ packages/zefyr/lib/src/widgets/buttons.dart | 10 +- .../zefyr/lib/src/widgets/editable_text.dart | 5 +- packages/zefyr/lib/src/widgets/editor.dart | 102 ++++++-------- packages/zefyr/lib/src/widgets/scaffold.dart | 60 +++++++++ packages/zefyr/lib/src/widgets/selection.dart | 25 ++-- packages/zefyr/lib/src/widgets/toolbar.dart | 28 ++-- packages/zefyr/lib/zefyr.dart | 1 + packages/zefyr/test/testing.dart | 4 +- packages/zefyr/test/widgets/image_test.dart | 2 +- .../test/widgets/render_context_test.dart | 14 +- 13 files changed, 367 insertions(+), 197 deletions(-) create mode 100644 packages/zefyr/example/lib/src/form.dart create mode 100644 packages/zefyr/example/lib/src/full_page.dart create mode 100644 packages/zefyr/lib/src/widgets/scaffold.dart diff --git a/packages/zefyr/example/lib/main.dart b/packages/zefyr/example/lib/main.dart index ef157bb7a..899e44902 100644 --- a/packages/zefyr/example/lib/main.dart +++ b/packages/zefyr/example/lib/main.dart @@ -1,125 +1,68 @@ // Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:convert'; - import 'package:flutter/material.dart'; -import 'package:quill_delta/quill_delta.dart'; -import 'package:zefyr/zefyr.dart'; + +import 'src/full_page.dart'; +import 'src/form.dart'; void main() { runApp(new ZefyrApp()); } -class ZefyrLogo extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Ze'), - FlutterLogo(size: 24.0), - Text('yr'), - ], - ); - } -} - class ZefyrApp extends StatelessWidget { @override Widget build(BuildContext context) { - return new MaterialApp( + return MaterialApp( debugShowCheckedModeBanner: false, title: 'Zefyr Editor', - theme: new ThemeData(primarySwatch: Colors.cyan), - home: new MyHomePage(), + theme: ThemeData(primarySwatch: Colors.cyan), + home: HomePage(), + routes: { + "/fullPage": buildFullPage, + "/form": buildFormPage, + }, ); } -} - -class MyHomePage extends StatefulWidget { - @override - _MyHomePageState createState() => new _MyHomePageState(); -} -final doc = - r'[{"insert":"Zefyr"},{"insert":"\n","attributes":{"heading":1}},{"insert":"Soft and gentle rich text editing for Flutter applications.","attributes":{"i":true}},{"insert":"\n"},{"insert":"​","attributes":{"embed":{"type":"image","source":"asset://images/breeze.jpg"}}},{"insert":"\n"},{"insert":"Photo by Hiroyuki Takeda.","attributes":{"i":true}},{"insert":"\nZefyr is currently in "},{"insert":"early preview","attributes":{"b":true}},{"insert":". If you have a feature request or found a bug, please file it at the "},{"insert":"issue tracker","attributes":{"a":"https://github.com/memspace/zefyr/issues"}},{"insert":' - r'".\nDocumentation"},{"insert":"\n","attributes":{"heading":3}},{"insert":"Quick Start","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/quick_start.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Data Format and Document Model","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/data_and_document.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Style Attributes","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/attr' - r'ibutes.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Heuristic Rules","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/heuristics.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"FAQ","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/faq.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Clean and modern look"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Zefyr’s rich text editor is built with simplicity and fle' - r'xibility in mind. It provides clean interface for distraction-free editing. Think Medium.com-like experience.\nMarkdown inspired semantics"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Ever needed to have a heading line inside of a quote block, like this:\nI’m a Markdown heading"},{"insert":"\n","attributes":{"block":"quote","heading":3}},{"insert":"And I’m a regular paragraph"},{"insert":"\n","attributes":{"block":"quote"}},{"insert":"Code blocks"},{"insert":"\n","attributes":{"headin' - r'g":2}},{"insert":"Of course:\nimport ‘package:flutter/material.dart’;"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"import ‘package:zefyr/zefyr.dart’;"},{"insert":"\n\n","attributes":{"block":"code"}},{"insert":"void main() {"},{"insert":"\n","attributes":{"block":"code"}},{"insert":" runApp(MyZefyrApp());"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"}"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"\n\n\n"}]'; + Widget buildFullPage(BuildContext context) { + return FullPageEditorScreen(); + } -Delta getDelta() { - return Delta.fromJson(json.decode(doc)); + Widget buildFormPage(BuildContext context) { + return FormEmbeddedScreen(); + } } -class _MyHomePageState extends State { - final ZefyrController _controller = - ZefyrController(NotusDocument.fromDelta(getDelta())); - final FocusNode _focusNode = new FocusNode(); - bool _editing = false; - +class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = new ZefyrThemeData( - toolbarTheme: ZefyrToolbarTheme.fallback(context).copyWith( - color: Colors.grey.shade800, - toggleColor: Colors.grey.shade900, - iconColor: Colors.white, - disabledIconColor: Colors.grey.shade500, - ), - ); - - final done = _editing - ? [new FlatButton(onPressed: _stopEditing, child: Text('DONE'))] - : [new FlatButton(onPressed: _startEditing, child: Text('EDIT'))]; + final nav = Navigator.of(context); return Scaffold( - resizeToAvoidBottomPadding: true, appBar: AppBar( elevation: 1.0, backgroundColor: Colors.grey.shade200, brightness: Brightness.light, title: ZefyrLogo(), - actions: done, ), - body: ZefyrTheme( - data: theme, - child: ZefyrEditor( - controller: _controller, - focusNode: _focusNode, - enabled: _editing, - imageDelegate: new CustomImageDelegate(), - ), + body: Column( + children: [ + Expanded(child: Container()), + FlatButton( + onPressed: () => nav.pushNamed('/fullPage'), + child: Text('Full page editor'), + color: Colors.lightBlue, + textColor: Colors.white, + ), + FlatButton( + onPressed: () => nav.pushNamed('/form'), + child: Text('Embedded in a form'), + color: Colors.lightBlue, + textColor: Colors.white, + ), + Expanded(child: Container()), + ], ), ); } - - void _startEditing() { - setState(() { - _editing = true; - }); - } - - void _stopEditing() { - setState(() { - _editing = false; - }); - } -} - -/// Custom image delegate used by this example to load image from application -/// assets. -/// -/// Default image delegate only supports [FileImage]s. -class CustomImageDelegate extends ZefyrDefaultImageDelegate { - @override - Widget buildImage(BuildContext context, String imageSource) { - // We use custom "asset" scheme to distinguish asset images from other files. - if (imageSource.startsWith('asset://')) { - final asset = new AssetImage(imageSource.replaceFirst('asset://', '')); - return new Image(image: asset); - } else { - return super.buildImage(context, imageSource); - } - } } diff --git a/packages/zefyr/example/lib/src/form.dart b/packages/zefyr/example/lib/src/form.dart new file mode 100644 index 000000000..9050738da --- /dev/null +++ b/packages/zefyr/example/lib/src/form.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:zefyr/zefyr.dart'; + +import 'full_page.dart'; + +class FormEmbeddedScreen extends StatefulWidget { + @override + _FormEmbeddedScreenState createState() => _FormEmbeddedScreenState(); +} + +class _FormEmbeddedScreenState extends State { + final ZefyrController _controller = ZefyrController(NotusDocument()); + final FocusNode _focusNode = new FocusNode(); + + @override + Widget build(BuildContext context) { + final form = ListView( + children: [ + TextField(decoration: InputDecoration(labelText: 'Name')), + TextField(decoration: InputDecoration(labelText: 'Email')), + Container( + padding: EdgeInsets.only(top: 16.0), + child: Text( + 'Description', + style: TextStyle(color: Colors.black54, fontSize: 16.0), + ), + alignment: Alignment.centerLeft, + ), + buildEditor(), + ], + ); + + return Scaffold( + resizeToAvoidBottomPadding: true, + appBar: AppBar( + elevation: 1.0, + backgroundColor: Colors.grey.shade200, + brightness: Brightness.light, + title: ZefyrLogo(), + ), + body: ZefyrScaffold( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: form, + ), + ), + ); + } + + Widget buildEditor() { + final theme = new ZefyrThemeData( + toolbarTheme: ZefyrToolbarTheme.fallback(context).copyWith( + color: Colors.grey.shade800, + toggleColor: Colors.grey.shade900, + iconColor: Colors.white, + disabledIconColor: Colors.grey.shade500, + ), + ); + + return Container( + margin: EdgeInsets.symmetric(vertical: 8.0), + constraints: BoxConstraints.tightFor(height: 300.0), + decoration: + BoxDecoration(border: Border.all(color: Colors.grey.shade400)), + child: ZefyrTheme( + data: theme, + child: ZefyrEditor( + padding: EdgeInsets.all(8.0), + controller: _controller, + focusNode: _focusNode, + autofocus: false, + imageDelegate: new CustomImageDelegate(), + physics: ClampingScrollPhysics(), + ), + ), + ); + } +} diff --git a/packages/zefyr/example/lib/src/full_page.dart b/packages/zefyr/example/lib/src/full_page.dart new file mode 100644 index 000000000..8115230ec --- /dev/null +++ b/packages/zefyr/example/lib/src/full_page.dart @@ -0,0 +1,108 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:quill_delta/quill_delta.dart'; +import 'package:zefyr/zefyr.dart'; + +class ZefyrLogo extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Ze'), + FlutterLogo(size: 24.0), + Text('yr'), + ], + ); + } +} + +class FullPageEditorScreen extends StatefulWidget { + @override + _FullPageEditorScreenState createState() => new _FullPageEditorScreenState(); +} + +final doc = + r'[{"insert":"Zefyr"},{"insert":"\n","attributes":{"heading":1}},{"insert":"Soft and gentle rich text editing for Flutter applications.","attributes":{"i":true}},{"insert":"\n"},{"insert":"​","attributes":{"embed":{"type":"image","source":"asset://images/breeze.jpg"}}},{"insert":"\n"},{"insert":"Photo by Hiroyuki Takeda.","attributes":{"i":true}},{"insert":"\nZefyr is currently in "},{"insert":"early preview","attributes":{"b":true}},{"insert":". If you have a feature request or found a bug, please file it at the "},{"insert":"issue tracker","attributes":{"a":"https://github.com/memspace/zefyr/issues"}},{"insert":' + r'".\nDocumentation"},{"insert":"\n","attributes":{"heading":3}},{"insert":"Quick Start","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/quick_start.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Data Format and Document Model","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/data_and_document.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Style Attributes","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/attr' + r'ibutes.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Heuristic Rules","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/heuristics.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"FAQ","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/faq.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Clean and modern look"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Zefyr’s rich text editor is built with simplicity and fle' + r'xibility in mind. It provides clean interface for distraction-free editing. Think Medium.com-like experience.\nMarkdown inspired semantics"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Ever needed to have a heading line inside of a quote block, like this:\nI’m a Markdown heading"},{"insert":"\n","attributes":{"block":"quote","heading":3}},{"insert":"And I’m a regular paragraph"},{"insert":"\n","attributes":{"block":"quote"}},{"insert":"Code blocks"},{"insert":"\n","attributes":{"headin' + r'g":2}},{"insert":"Of course:\nimport ‘package:flutter/material.dart’;"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"import ‘package:zefyr/zefyr.dart’;"},{"insert":"\n\n","attributes":{"block":"code"}},{"insert":"void main() {"},{"insert":"\n","attributes":{"block":"code"}},{"insert":" runApp(MyZefyrApp());"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"}"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"\n\n\n"}]'; + +Delta getDelta() { + return Delta.fromJson(json.decode(doc)); +} + +class _FullPageEditorScreenState extends State { + final ZefyrController _controller = + ZefyrController(NotusDocument.fromDelta(getDelta())); + final FocusNode _focusNode = new FocusNode(); + bool _editing = false; + + @override + Widget build(BuildContext context) { + final theme = new ZefyrThemeData( + toolbarTheme: ZefyrToolbarTheme.fallback(context).copyWith( + color: Colors.grey.shade800, + toggleColor: Colors.grey.shade900, + iconColor: Colors.white, + disabledIconColor: Colors.grey.shade500, + ), + ); + + final done = _editing + ? [new FlatButton(onPressed: _stopEditing, child: Text('DONE'))] + : [new FlatButton(onPressed: _startEditing, child: Text('EDIT'))]; + return Scaffold( + resizeToAvoidBottomPadding: true, + appBar: AppBar( + elevation: 1.0, + backgroundColor: Colors.grey.shade200, + brightness: Brightness.light, + title: ZefyrLogo(), + actions: done, + ), + body: ZefyrScaffold( + child: ZefyrTheme( + data: theme, + child: ZefyrEditor( + controller: _controller, + focusNode: _focusNode, + enabled: _editing, + imageDelegate: new CustomImageDelegate(), + ), + ), + ), + ); + } + + void _startEditing() { + setState(() { + _editing = true; + }); + } + + void _stopEditing() { + setState(() { + _editing = false; + }); + } +} + +/// Custom image delegate used by this example to load image from application +/// assets. +/// +/// Default image delegate only supports [FileImage]s. +class CustomImageDelegate extends ZefyrDefaultImageDelegate { + @override + Widget buildImage(BuildContext context, String imageSource) { + // We use custom "asset" scheme to distinguish asset images from other files. + if (imageSource.startsWith('asset://')) { + final asset = new AssetImage(imageSource.replaceFirst('asset://', '')); + return new Image(image: asset); + } else { + return super.buildImage(context, imageSource); + } + } +} diff --git a/packages/zefyr/lib/src/widgets/buttons.dart b/packages/zefyr/lib/src/widgets/buttons.dart index f28c7fbb8..d762cba3f 100644 --- a/packages/zefyr/lib/src/widgets/buttons.dart +++ b/packages/zefyr/lib/src/widgets/buttons.dart @@ -491,18 +491,19 @@ class _LinkInput extends StatefulWidget { class _LinkInputState extends State<_LinkInput> { final FocusNode _focusNode = FocusNode(); - bool _didAutoFocus = false; ZefyrEditorScope _editor; + bool _didAutoFocus = false; @override void didChangeDependencies() { super.didChangeDependencies(); - final toolbar = ZefyrToolbar.of(context); if (!_didAutoFocus) { FocusScope.of(context).requestFocus(_focusNode); _didAutoFocus = true; } + final toolbar = ZefyrToolbar.of(context); + if (_editor != toolbar.editor) { _editor?.setToolbarFocusNode(null); _editor = toolbar.editor; @@ -512,6 +513,7 @@ class _LinkInputState extends State<_LinkInput> { @override void dispose() { + _editor?.setToolbarFocusNode(null); _focusNode.dispose(); _editor = null; super.dispose(); @@ -519,8 +521,6 @@ class _LinkInputState extends State<_LinkInput> { @override Widget build(BuildContext context) { - FocusScope.of(context).reparentIfNeeded(_focusNode); - final theme = Theme.of(context); final toolbarTheme = ZefyrTheme.of(context).toolbarTheme; final color = @@ -531,7 +531,7 @@ class _LinkInputState extends State<_LinkInput> { keyboardType: TextInputType.url, focusNode: _focusNode, controller: widget.controller, -// autofocus: true, + autofocus: true, decoration: new InputDecoration( hintText: 'https://', filled: true, diff --git a/packages/zefyr/lib/src/widgets/editable_text.dart b/packages/zefyr/lib/src/widgets/editable_text.dart index c3ef396dd..b3b671ac1 100644 --- a/packages/zefyr/lib/src/widgets/editable_text.dart +++ b/packages/zefyr/lib/src/widgets/editable_text.dart @@ -36,6 +36,7 @@ class ZefyrEditableText extends StatefulWidget { this.autofocus: true, this.enabled: true, this.padding: const EdgeInsets.symmetric(horizontal: 16.0), + this.physics, }) : super(key: key); final ZefyrController controller; @@ -43,6 +44,7 @@ class ZefyrEditableText extends StatefulWidget { final ZefyrImageDelegate imageDelegate; final bool autofocus; final bool enabled; + final ScrollPhysics physics; /// Padding around editable area. final EdgeInsets padding; @@ -132,8 +134,7 @@ class _ZefyrEditableTextState extends State body = new Padding(padding: widget.padding, child: body); } final scrollable = SingleChildScrollView( - padding: EdgeInsets.only(top: 16.0), - physics: AlwaysScrollableScrollPhysics(), + physics: widget.physics, controller: _scrollController, child: body, ); diff --git a/packages/zefyr/lib/src/widgets/editor.dart b/packages/zefyr/lib/src/widgets/editor.dart index badf2b63f..23ad357ab 100644 --- a/packages/zefyr/lib/src/widgets/editor.dart +++ b/packages/zefyr/lib/src/widgets/editor.dart @@ -7,6 +7,7 @@ import 'package:notus/notus.dart'; import 'controller.dart'; import 'editable_text.dart'; import 'image.dart'; +import 'scaffold.dart'; import 'theme.dart'; import 'toolbar.dart'; @@ -31,7 +32,6 @@ class ZefyrEditorScope extends ChangeNotifier { ZefyrImageDelegate _imageDelegate; ZefyrImageDelegate get imageDelegate => _imageDelegate; - FocusScopeNode get focusScope => _focusScope; FocusScopeNode _focusScope; FocusNode _focusNode; @@ -103,13 +103,13 @@ class ZefyrEditorScope extends ChangeNotifier { FocusNode _toolbarFocusNode; void setToolbarFocusNode(FocusNode node) { - assert(!_disposed); + assert(!_disposed || node == null); if (_toolbarFocusNode != node) { _toolbarFocusNode?.removeListener(_handleFocusChange); _toolbarFocusNode = node; - _toolbarFocusNode.addListener(_handleFocusChange); + _toolbarFocusNode?.addListener(_handleFocusChange); // We do not notify listeners here because it will happen when - // focus state changes. + // focus state changes, see [_handleFocusChange]. } } @@ -169,6 +169,7 @@ class ZefyrEditor extends StatefulWidget { this.padding: const EdgeInsets.symmetric(horizontal: 16.0), this.toolbarDelegate, this.imageDelegate, + this.physics, }) : super(key: key); final ZefyrController controller; @@ -177,6 +178,7 @@ class ZefyrEditor extends StatefulWidget { final bool enabled; final ZefyrToolbarDelegate toolbarDelegate; final ZefyrImageDelegate imageDelegate; + final ScrollPhysics physics; /// Padding around editable area. final EdgeInsets padding; @@ -195,36 +197,42 @@ class _ZefyrEditorState extends State { ZefyrImageDelegate _imageDelegate; ZefyrEditorScope _scope; ZefyrThemeData _themeData; + GlobalKey _toolbarKey; + ZefyrScaffoldState _scaffold; - OverlayEntry _toolbar; - OverlayState _overlay; + bool get hasToolbar => _toolbarKey != null; void showToolbar() { - _toolbar = new OverlayEntry( - builder: (context) => _ZefyrToolbarContainer( - theme: _themeData, - toolbar: ZefyrToolbar( - editor: _scope, - delegate: widget.toolbarDelegate, - ), - ), - ); - _overlay.insert(_toolbar); + assert(_toolbarKey == null); + _toolbarKey = GlobalKey(); + _scaffold.showToolbar(buildToolbar); } void hideToolbar() { - _toolbar?.remove(); - _toolbar = null; + if (_toolbarKey == null) return; + _scaffold.hideToolbar(); + _toolbarKey = null; + } + + Widget buildToolbar(BuildContext) { + return ZefyrTheme( + data: _themeData, + child: ZefyrToolbar( + key: _toolbarKey, + editor: _scope, + delegate: widget.toolbarDelegate, + ), + ); } void _handleChange() { if (_scope.focusOwner == FocusOwner.none) { hideToolbar(); - } else if (_toolbar == null) { + } else if (!hasToolbar) { showToolbar(); } else { WidgetsBinding.instance.addPostFrameCallback((_) { - _toolbar?.markNeedsBuild(); + _toolbarKey?.currentState?.markNeedsRebuild(); }); } } @@ -249,6 +257,11 @@ class _ZefyrEditorState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); + final parentTheme = ZefyrTheme.of(context, nullOk: true); + final fallbackTheme = ZefyrThemeData.fallback(context); + _themeData = (parentTheme != null) + ? fallbackTheme.merge(parentTheme) + : fallbackTheme; if (_scope == null) { _scope = ZefyrEditorScope( @@ -265,17 +278,12 @@ class _ZefyrEditorState extends State { } } - final parentTheme = ZefyrTheme.of(context, nullOk: true); - final fallbackTheme = ZefyrThemeData.fallback(context); - _themeData = (parentTheme != null) - ? fallbackTheme.merge(parentTheme) - : fallbackTheme; - - final overlay = Overlay.of(context, debugRequiredFor: widget); - if (_overlay != overlay) { + final scaffold = ZefyrScaffold.of(context); + if (_scaffold != scaffold) { + bool didHaveToolbar = hasToolbar; hideToolbar(); - _overlay = overlay; - // TODO: update toolbar. + _scaffold = scaffold; + if (didHaveToolbar) showToolbar(); } } @@ -296,45 +304,15 @@ class _ZefyrEditorState extends State { autofocus: widget.autofocus, enabled: widget.enabled, padding: widget.padding, + physics: widget.physics, ); - final children = []; - children.add(Expanded(child: editable)); - if (_toolbar != null) { - children.add(SizedBox(height: ZefyrToolbar.kToolbarHeight)); - } -// final toolbar = ZefyrToolbar( -// editor: _scope, -// focusNode: _toolbarFocusNode, -// delegate: widget.toolbarDelegate, -// ); -// children.add(toolbar); - return ZefyrTheme( data: _themeData, child: _ZefyrEditorScope( scope: _scope, - child: Column(children: children), + child: editable, ), ); } } - -class _ZefyrToolbarContainer extends StatelessWidget { - final ZefyrThemeData theme; - final Widget toolbar; - - const _ZefyrToolbarContainer({Key key, this.theme, this.toolbar}) - : super(key: key); - - @override - Widget build(BuildContext context) { - final media = MediaQuery.of(context); - return Positioned( - bottom: media.viewInsets.bottom, - left: 0.0, - right: 0.0, - child: ZefyrTheme(data: theme, child: toolbar), - ); - } -} diff --git a/packages/zefyr/lib/src/widgets/scaffold.dart b/packages/zefyr/lib/src/widgets/scaffold.dart new file mode 100644 index 000000000..f711070e5 --- /dev/null +++ b/packages/zefyr/lib/src/widgets/scaffold.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; + +class ZefyrScaffold extends StatefulWidget { + final Widget child; + + const ZefyrScaffold({Key key, this.child}) : super(key: key); + + static ZefyrScaffoldState of(BuildContext context) { + final _ZefyrScaffoldAccess widget = + context.inheritFromWidgetOfExactType(_ZefyrScaffoldAccess); + return widget.scaffold; + } + + @override + ZefyrScaffoldState createState() => ZefyrScaffoldState(); +} + +class ZefyrScaffoldState extends State { + WidgetBuilder _toolbarBuilder; + + void showToolbar(WidgetBuilder builder) { + setState(() { + _toolbarBuilder = builder; + }); + } + + void hideToolbar() { + if (_toolbarBuilder != null) { + setState(() { + _toolbarBuilder = null; + }); + } + } + + @override + Widget build(BuildContext context) { + final toolbar = + (_toolbarBuilder == null) ? Container() : _toolbarBuilder(context); + return _ZefyrScaffoldAccess( + scaffold: this, + child: Column( + children: [ + Expanded(child: widget.child), + toolbar, + ], + ), + ); + } +} + +class _ZefyrScaffoldAccess extends InheritedWidget { + final ZefyrScaffoldState scaffold; + + _ZefyrScaffoldAccess({Widget child, this.scaffold}) : super(child: child); + + @override + bool updateShouldNotify(_ZefyrScaffoldAccess oldWidget) { + return oldWidget.scaffold != scaffold; + } +} diff --git a/packages/zefyr/lib/src/widgets/selection.dart b/packages/zefyr/lib/src/widgets/selection.dart index 27c9be4b2..667f1a667 100644 --- a/packages/zefyr/lib/src/widgets/selection.dart +++ b/packages/zefyr/lib/src/widgets/selection.dart @@ -66,6 +66,7 @@ class _ZefyrSelectionOverlayState extends State @override void hideToolbar() { + _didCaretTap = false; // reset double tap. _toolbar?.remove(); _toolbar = null; _toolbarController.stop(); @@ -98,8 +99,6 @@ class _ZefyrSelectionOverlayState extends State super.initState(); _toolbarController = new AnimationController( duration: _kFadeDuration, vsync: widget.overlay); - _selection = widget.controller.selection; - widget.controller.addListener(_handleChange); } static const Duration _kFadeDuration = const Duration(milliseconds: 150); @@ -113,23 +112,24 @@ class _ZefyrSelectionOverlayState extends State _toolbarController = new AnimationController( duration: _kFadeDuration, vsync: widget.overlay); } - if (oldWidget.controller != widget.controller) { - oldWidget.controller.removeListener(_handleChange); - widget.controller.addListener(_handleChange); - } } @override void didChangeDependencies() { super.didChangeDependencies(); - WidgetsBinding.instance.addPostFrameCallback((_) { - _updateToolbar(); - }); + final editor = ZefyrEditor.of(context); + if (_editor != editor) { + _editor?.removeListener(_handleChange); + _editor = editor; + _editor.addListener(_handleChange); + _selection = _editor.selection; + _focusOwner = _editor.focusOwner; + } } @override void dispose() { - widget.controller.removeListener(_handleChange); + _editor.removeListener(_handleChange); hideToolbar(); _toolbarController.dispose(); _toolbarController = null; @@ -174,12 +174,14 @@ class _ZefyrSelectionOverlayState extends State OverlayEntry _toolbar; AnimationController _toolbarController; + ZefyrEditorScope _editor; TextSelection _selection; + FocusOwner _focusOwner; bool _didCaretTap = false; void _handleChange() { - if (_selection != widget.controller.selection) { + if (_selection != _editor.selection || _focusOwner != _editor.focusOwner) { _updateToolbar(); } } @@ -208,6 +210,7 @@ class _ZefyrSelectionOverlayState extends State } } _selection = selection; + _focusOwner = focusOwner; }); } diff --git a/packages/zefyr/lib/src/widgets/toolbar.dart b/packages/zefyr/lib/src/widgets/toolbar.dart index 54f4d6b10..dc4e8624f 100644 --- a/packages/zefyr/lib/src/widgets/toolbar.dart +++ b/packages/zefyr/lib/src/widgets/toolbar.dart @@ -151,7 +151,12 @@ class ZefyrToolbarState extends State TextSelection _selection; void markNeedsRebuild() { - setState(() {}); + setState(() { + if (_selection != editor.selection) { + _selection = editor.selection; + closeOverlay(); + } + }); } Widget buildButton(BuildContext context, ZefyrToolbarAction action, @@ -200,10 +205,6 @@ class ZefyrToolbarState extends State if (widget.delegate != oldWidget.delegate) { _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate(); } - if (_selection != editor.selection) { - _selection = editor.selection; - closeOverlay(); - } } @override @@ -214,10 +215,6 @@ class ZefyrToolbarState extends State @override Widget build(BuildContext context) { - if (editor.focusOwner == FocusOwner.none) { - return new Container(); - } - final layers = []; // Must set unique key for the toolbar to prevent it from reconstructing @@ -243,14 +240,11 @@ class ZefyrToolbarState extends State final constraints = BoxConstraints.tightFor(height: ZefyrToolbar.kToolbarHeight); - return FocusScope( - node: editor.focusScope, - child: _ZefyrToolbarScope( - toolbar: this, - child: Container( - constraints: constraints, - child: Stack(children: layers), - ), + return _ZefyrToolbarScope( + toolbar: this, + child: Container( + constraints: constraints, + child: Stack(children: layers), ), ); } diff --git a/packages/zefyr/lib/zefyr.dart b/packages/zefyr/lib/zefyr.dart index 335deded3..9aeb8bad0 100644 --- a/packages/zefyr/lib/zefyr.dart +++ b/packages/zefyr/lib/zefyr.dart @@ -20,6 +20,7 @@ export 'src/widgets/image.dart'; export 'src/widgets/list.dart'; export 'src/widgets/paragraph.dart'; export 'src/widgets/quote.dart'; +export 'src/widgets/scaffold.dart'; export 'src/widgets/selection.dart' hide SelectionHandleDriver; export 'src/widgets/theme.dart'; export 'src/widgets/toolbar.dart'; diff --git a/packages/zefyr/test/testing.dart b/packages/zefyr/test/testing.dart index c45cc82f3..58861ae4d 100644 --- a/packages/zefyr/test/testing.dart +++ b/packages/zefyr/test/testing.dart @@ -30,7 +30,9 @@ class EditorSandBox { if (theme != null) { widget = ZefyrTheme(data: theme, child: widget); } - widget = MaterialApp(home: widget); + widget = MaterialApp( + home: ZefyrScaffold(child: widget), + ); return EditorSandBox._(tester, focusNode, document, controller, widget); } diff --git a/packages/zefyr/test/widgets/image_test.dart b/packages/zefyr/test/widgets/image_test.dart index ff73b7ee4..46234814f 100644 --- a/packages/zefyr/test/widgets/image_test.dart +++ b/packages/zefyr/test/widgets/image_test.dart @@ -75,7 +75,7 @@ void main() { expect(editor.selection.extentOffset, embed.documentOffset); }); - testWidgets('tap right side of horizontal rule puts caret after it', + testWidgets('tap right side of image puts caret after it', (tester) async { final editor = new EditorSandBox(tester: tester); await editor.tapEditor(); diff --git a/packages/zefyr/test/widgets/render_context_test.dart b/packages/zefyr/test/widgets/render_context_test.dart index 6b681dafc..1dcb8e293 100644 --- a/packages/zefyr/test/widgets/render_context_test.dart +++ b/packages/zefyr/test/widgets/render_context_test.dart @@ -52,12 +52,14 @@ void main() { }); testWidgets('notifyListeners is delayed to next frame', (tester) async { - var focusNode = new FocusNode(); - var controller = new ZefyrController(new NotusDocument()); - var widget = new MaterialApp( - home: new ZefyrEditor( - controller: controller, - focusNode: focusNode, + var focusNode = FocusNode(); + var controller = ZefyrController(new NotusDocument()); + var widget = MaterialApp( + home: ZefyrScaffold( + child: ZefyrEditor( + controller: controller, + focusNode: focusNode, + ), ), ); await tester.pumpWidget(widget); From 5f5bd033551cfe340e9f6b8c4fa6fb093b9d3363 Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Mon, 5 Nov 2018 09:29:15 -0800 Subject: [PATCH 06/11] Removed obsolete todo comment --- packages/zefyr/test/widgets/buttons_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/zefyr/test/widgets/buttons_test.dart b/packages/zefyr/test/widgets/buttons_test.dart index 9585e66cf..af7fc1d59 100644 --- a/packages/zefyr/test/widgets/buttons_test.dart +++ b/packages/zefyr/test/widgets/buttons_test.dart @@ -118,7 +118,6 @@ void main() { await tester .tap(find.widgetWithText(GestureDetector, 'Tap to edit link')); await tester.pumpAndSettle(); - // TODO: figure out why below finder finds 2 instances of TextField expect(find.byType(TextField), findsOneWidget); await tester.enterText(find.widgetWithText(TextField, 'https://').first, From 47ee2f5698e44af78e110817ca5165530a511359 Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Mon, 5 Nov 2018 09:49:43 -0800 Subject: [PATCH 07/11] Prepare release --- packages/zefyr/CHANGELOG.md | 11 +++++++++++ packages/zefyr/example/lib/src/form.dart | 7 ++++++- packages/zefyr/lib/src/widgets/theme.dart | 2 +- packages/zefyr/pubspec.yaml | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/zefyr/CHANGELOG.md b/packages/zefyr/CHANGELOG.md index 46f22dd7c..4600f08e5 100644 --- a/packages/zefyr/CHANGELOG.md +++ b/packages/zefyr/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.3.0 + +This version introduces new widget `ZefyrScaffold` which allows embedding Zefyr in custom +layouts, like forms with multiple input fields. + +It is now required to always wrap `ZefyrEditor` with an instance of this new widget. See examples +and readme for more details. + +* Breaking change: `ZefyrEditor` requires an ancestor `ZefyrScaffold`. +* Exposed `ZefyrEditor.physics` property to allow customization of `ScrollPhysics`. + ## 0.2.0 * Breaking change: `ZefyrImageDelegate.createImageProvider` replaced with diff --git a/packages/zefyr/example/lib/src/form.dart b/packages/zefyr/example/lib/src/form.dart index 9050738da..d9d399cf5 100644 --- a/packages/zefyr/example/lib/src/form.dart +++ b/packages/zefyr/example/lib/src/form.dart @@ -18,14 +18,19 @@ class _FormEmbeddedScreenState extends State { children: [ TextField(decoration: InputDecoration(labelText: 'Name')), TextField(decoration: InputDecoration(labelText: 'Email')), + TextField( + decoration: InputDecoration(labelText: 'Regular multiline text'), + maxLines: 5, + ), Container( padding: EdgeInsets.only(top: 16.0), child: Text( - 'Description', + 'Zefyr rich text', style: TextStyle(color: Colors.black54, fontSize: 16.0), ), alignment: Alignment.centerLeft, ), + buildEditor(), ], ); diff --git a/packages/zefyr/lib/src/widgets/theme.dart b/packages/zefyr/lib/src/widgets/theme.dart index 832042a1d..d775dbef2 100644 --- a/packages/zefyr/lib/src/widgets/theme.dart +++ b/packages/zefyr/lib/src/widgets/theme.dart @@ -172,7 +172,7 @@ class HeadingTheme { height: 1.25, fontWeight: FontWeight.w600, ), - padding: EdgeInsets.only(bottom: 16.0), + padding: EdgeInsets.only(top: 16.0, bottom: 16.0), ), level2: StyleTheme( textStyle: TextStyle( diff --git a/packages/zefyr/pubspec.yaml b/packages/zefyr/pubspec.yaml index f881a2878..17eeb86d5 100644 --- a/packages/zefyr/pubspec.yaml +++ b/packages/zefyr/pubspec.yaml @@ -1,6 +1,6 @@ name: zefyr description: Clean, minimalistic and collaboration-ready rich text editor for Flutter. -version: 0.2.0 +version: 0.3.0 author: Anatoly Pulyaevskiy homepage: https://github.com/memspace/zefyr From e1402c8d1bf1eb9461e54018b29eb3b83588d14f Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Mon, 5 Nov 2018 09:58:59 -0800 Subject: [PATCH 08/11] Updated docs --- .gitignore | 1 + doc/quick_start.md | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..62c893550 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/doc/quick_start.md b/doc/quick_start.md index b38700cef..6e6bb3176 100644 --- a/doc/quick_start.md +++ b/doc/quick_start.md @@ -12,7 +12,7 @@ Add `zefyr` package as a dependency to your `pubspec.yaml`: ```yaml dependencies: - zefyr: ^0.1.0 + zefyr: ^0.3.0 ``` And run `flutter packages get` to install. This installs both `zefyr` @@ -20,7 +20,7 @@ and `notus` packages. ### Usage -There are 3 main objects you would normally interact with in your code: +There are 4 main objects you would normally interact with in your code: * `NotusDocument`, represents a rich text document and provides high-level methods for manipulating the document's state, like @@ -30,6 +30,9 @@ There are 3 main objects you would normally interact with in your code: * `ZefyrEditor`, a Flutter widget responsible for rendering of rich text on the screen and reacting to user actions. * `ZefyrController`, ties the above two objects together. +* `ZefyrScaffold`, allows embedding Zefyr toolbar into any custom layout. + +`ZefyrEditor` depends on presence of `ZefyrScaffold` somewhere up the widget tree. Normally you would need to place `ZefyrEditor` inside of a `StatefulWidget`. Shown below is a minimal setup required to use the @@ -60,9 +63,11 @@ class MyWidgetState extends State { @override Widget build(BuildContext context) { - return ZefyrEditor( - controller: _controller, - focusNode: _focusNode, + return ZefyrScaffold( + child: ZefyrEditor( + controller: _controller, + focusNode: _focusNode, + ), ); } } From b63232641f0d525d3e7c063d3b4a0861fc0b8081 Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Mon, 5 Nov 2018 11:11:05 -0800 Subject: [PATCH 09/11] Added basic ZefyrField widget --- packages/zefyr/CHANGELOG.md | 3 + packages/zefyr/example/lib/src/form.dart | 40 +++-------- packages/zefyr/lib/src/widgets/field.dart | 86 +++++++++++++++++++++++ packages/zefyr/lib/zefyr.dart | 2 +- 4 files changed, 101 insertions(+), 30 deletions(-) create mode 100644 packages/zefyr/lib/src/widgets/field.dart diff --git a/packages/zefyr/CHANGELOG.md b/packages/zefyr/CHANGELOG.md index 4600f08e5..429f4e718 100644 --- a/packages/zefyr/CHANGELOG.md +++ b/packages/zefyr/CHANGELOG.md @@ -6,8 +6,11 @@ layouts, like forms with multiple input fields. It is now required to always wrap `ZefyrEditor` with an instance of this new widget. See examples and readme for more details. +There is also new `ZefyrField` widget which integrates Zefyr with material design decorations. + * Breaking change: `ZefyrEditor` requires an ancestor `ZefyrScaffold`. * Exposed `ZefyrEditor.physics` property to allow customization of `ScrollPhysics`. +* Added basic `ZefyrField` widget. ## 0.2.0 diff --git a/packages/zefyr/example/lib/src/form.dart b/packages/zefyr/example/lib/src/form.dart index d9d399cf5..50c45652c 100644 --- a/packages/zefyr/example/lib/src/form.dart +++ b/packages/zefyr/example/lib/src/form.dart @@ -17,21 +17,8 @@ class _FormEmbeddedScreenState extends State { final form = ListView( children: [ TextField(decoration: InputDecoration(labelText: 'Name')), - TextField(decoration: InputDecoration(labelText: 'Email')), - TextField( - decoration: InputDecoration(labelText: 'Regular multiline text'), - maxLines: 5, - ), - Container( - padding: EdgeInsets.only(top: 16.0), - child: Text( - 'Zefyr rich text', - style: TextStyle(color: Colors.black54, fontSize: 16.0), - ), - alignment: Alignment.centerLeft, - ), - buildEditor(), + TextField(decoration: InputDecoration(labelText: 'Email')), ], ); @@ -62,21 +49,16 @@ class _FormEmbeddedScreenState extends State { ), ); - return Container( - margin: EdgeInsets.symmetric(vertical: 8.0), - constraints: BoxConstraints.tightFor(height: 300.0), - decoration: - BoxDecoration(border: Border.all(color: Colors.grey.shade400)), - child: ZefyrTheme( - data: theme, - child: ZefyrEditor( - padding: EdgeInsets.all(8.0), - controller: _controller, - focusNode: _focusNode, - autofocus: false, - imageDelegate: new CustomImageDelegate(), - physics: ClampingScrollPhysics(), - ), + return ZefyrTheme( + data: theme, + child: ZefyrField( + height: 200.0, + decoration: InputDecoration(labelText: 'Description'), + controller: _controller, + focusNode: _focusNode, + autofocus: false, + imageDelegate: new CustomImageDelegate(), + physics: ClampingScrollPhysics(), ), ); } diff --git a/packages/zefyr/lib/src/widgets/field.dart b/packages/zefyr/lib/src/widgets/field.dart new file mode 100644 index 000000000..d23f3ca78 --- /dev/null +++ b/packages/zefyr/lib/src/widgets/field.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +import 'controller.dart'; +import 'editor.dart'; +import 'image.dart'; +import 'toolbar.dart'; + +/// Zefyr editor with material design decorations. +class ZefyrField extends StatefulWidget { + /// Decoration to paint around this editor. + final InputDecoration decoration; + + /// Height of this editor field. + final double height; + final ZefyrController controller; + final FocusNode focusNode; + final bool autofocus; + final bool enabled; + final ZefyrToolbarDelegate toolbarDelegate; + final ZefyrImageDelegate imageDelegate; + final ScrollPhysics physics; + + const ZefyrField({ + Key key, + this.decoration, + this.height, + this.controller, + this.focusNode, + this.autofocus: false, + this.enabled, + this.toolbarDelegate, + this.imageDelegate, + this.physics, + }) : super(key: key); + + @override + _ZefyrFieldState createState() => _ZefyrFieldState(); +} + +class _ZefyrFieldState extends State { + @override + Widget build(BuildContext context) { + Widget child = ZefyrEditor( + padding: EdgeInsets.symmetric(vertical: 6.0), + controller: widget.controller, + focusNode: widget.focusNode, + autofocus: widget.autofocus, + enabled: widget.enabled ?? true, + toolbarDelegate: widget.toolbarDelegate, + imageDelegate: widget.imageDelegate, + physics: widget.physics, + ); + + if (widget.height != null) { + child = ConstrainedBox( + constraints: BoxConstraints.tightFor(height: widget.height), + child: child, + ); + } + + return AnimatedBuilder( + animation: + Listenable.merge([widget.focusNode, widget.controller]), + builder: (BuildContext context, Widget child) { + return InputDecorator( + decoration: _getEffectiveDecoration(), + isFocused: widget.focusNode.hasFocus, + isEmpty: widget.controller.document.length == 1, + child: child, + ); + }, + child: child, + ); + } + + InputDecoration _getEffectiveDecoration() { + final InputDecoration effectiveDecoration = + (widget.decoration ?? const InputDecoration()) + .applyDefaults(Theme.of(context).inputDecorationTheme) + .copyWith( + enabled: widget.enabled ?? true, + ); + + return effectiveDecoration; + } +} diff --git a/packages/zefyr/lib/zefyr.dart b/packages/zefyr/lib/zefyr.dart index 9aeb8bad0..db08132c6 100644 --- a/packages/zefyr/lib/zefyr.dart +++ b/packages/zefyr/lib/zefyr.dart @@ -15,6 +15,7 @@ export 'src/widgets/common.dart'; export 'src/widgets/controller.dart'; export 'src/widgets/editable_text.dart'; export 'src/widgets/editor.dart'; +export 'src/widgets/field.dart'; export 'src/widgets/horizontal_rule.dart'; export 'src/widgets/image.dart'; export 'src/widgets/list.dart'; @@ -24,4 +25,3 @@ export 'src/widgets/scaffold.dart'; export 'src/widgets/selection.dart' hide SelectionHandleDriver; export 'src/widgets/theme.dart'; export 'src/widgets/toolbar.dart'; -//export 'src/widgets/render_context.dart'; From 5b8ae0697cf0d39346edf815fa4d2a96bdbacedc Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Thu, 15 Nov 2018 09:20:46 -0800 Subject: [PATCH 10/11] Updated gitignore --- packages/zefyr/.gitignore | 2 ++ packages/zefyr/example/lib/main.dart | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/zefyr/.gitignore b/packages/zefyr/.gitignore index 0ff7b798f..febd91438 100644 --- a/packages/zefyr/.gitignore +++ b/packages/zefyr/.gitignore @@ -14,3 +14,5 @@ example/ios/.symlinks example/ios/Flutter/Generated.xcconfig doc/api/ build/ + +example/feather diff --git a/packages/zefyr/example/lib/main.dart b/packages/zefyr/example/lib/main.dart index 899e44902..112764c01 100644 --- a/packages/zefyr/example/lib/main.dart +++ b/packages/zefyr/example/lib/main.dart @@ -2,9 +2,8 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:flutter/material.dart'; - -import 'src/full_page.dart'; import 'src/form.dart'; +import 'src/full_page.dart'; void main() { runApp(new ZefyrApp()); From 60e85bd98f83cc2050f912b72d2dec2c75d0b3e9 Mon Sep 17 00:00:00 2001 From: Anatoly Pulyaevskiy Date: Thu, 15 Nov 2018 09:31:44 -0800 Subject: [PATCH 11/11] Upgraded url_launcher --- packages/zefyr/CHANGELOG.md | 3 ++- packages/zefyr/analysis_options.yaml | 2 +- packages/zefyr/lib/src/widgets/toolbar.dart | 1 - packages/zefyr/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/zefyr/CHANGELOG.md b/packages/zefyr/CHANGELOG.md index 429f4e718..a18423601 100644 --- a/packages/zefyr/CHANGELOG.md +++ b/packages/zefyr/CHANGELOG.md @@ -9,8 +9,9 @@ and readme for more details. There is also new `ZefyrField` widget which integrates Zefyr with material design decorations. * Breaking change: `ZefyrEditor` requires an ancestor `ZefyrScaffold`. +* Upgraded to `url_launcher` version 4.0.0. * Exposed `ZefyrEditor.physics` property to allow customization of `ScrollPhysics`. -* Added basic `ZefyrField` widget. +* Added basic `ZefyrField` widget with material design decorations. ## 0.2.0 diff --git a/packages/zefyr/analysis_options.yaml b/packages/zefyr/analysis_options.yaml index 0f92e6543..cf554432c 100644 --- a/packages/zefyr/analysis_options.yaml +++ b/packages/zefyr/analysis_options.yaml @@ -1,6 +1,6 @@ analyzer: language: - enableSuperMixins: true +# enableSuperMixins: true # Lint rules and documentation, see http://dart-lang.github.io/linter/lints linter: diff --git a/packages/zefyr/lib/src/widgets/toolbar.dart b/packages/zefyr/lib/src/widgets/toolbar.dart index dc4e8624f..7490c22d7 100644 --- a/packages/zefyr/lib/src/widgets/toolbar.dart +++ b/packages/zefyr/lib/src/widgets/toolbar.dart @@ -8,7 +8,6 @@ import 'package:flutter/material.dart'; import 'package:notus/notus.dart'; import 'buttons.dart'; -import 'controller.dart'; import 'editor.dart'; import 'theme.dart'; diff --git a/packages/zefyr/pubspec.yaml b/packages/zefyr/pubspec.yaml index 17eeb86d5..49925df04 100644 --- a/packages/zefyr/pubspec.yaml +++ b/packages/zefyr/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter collection: ^1.14.6 - url_launcher: ^3.0.0 + url_launcher: ^4.0.0 image_picker: ^0.4.5 quill_delta: ^1.0.0-dev.1.0 notus: ^0.1.0