Browse Source

Re-arranged handling of focus scope so that it works with toolbar in an Overlay

Anatoly Pulyaevskiy 6 years ago
parent
commit
c148574910

+ 31
- 11
packages/zefyr/lib/src/widgets/buttons.dart View File

300
 }
300
 }
301
 
301
 
302
 class _LinkButtonState extends State<LinkButton> {
302
 class _LinkButtonState extends State<LinkButton> {
303
-  final TextEditingController _inputController = new TextEditingController();
303
+  final FocusNode _focusNode = FocusNode();
304
+  final TextEditingController _inputController = TextEditingController();
304
   Key _inputKey;
305
   Key _inputKey;
305
   bool _formatError = false;
306
   bool _formatError = false;
307
+  ZefyrEditorScope _editor;
308
+
306
   bool get isEditing => _inputKey != null;
309
   bool get isEditing => _inputKey != null;
307
 
310
 
311
+  @override
312
+  void dispose() {
313
+    _focusNode.dispose();
314
+    super.dispose();
315
+  }
316
+
317
+  @override
318
+  void didChangeDependencies() {
319
+    super.didChangeDependencies();
320
+    final toolbar = ZefyrToolbar.of(context);
321
+    if (_editor != toolbar.editor) {
322
+      _editor?.setToolbarFocusNode(null);
323
+      _editor = toolbar.editor;
324
+      _editor.setToolbarFocusNode(_focusNode);
325
+    }
326
+  }
327
+
308
   @override
328
   @override
309
   Widget build(BuildContext context) {
329
   Widget build(BuildContext context) {
310
     final toolbar = ZefyrToolbar.of(context);
330
     final toolbar = ZefyrToolbar.of(context);
376
         _inputController.text = '';
396
         _inputController.text = '';
377
         _inputController.removeListener(_handleInputChange);
397
         _inputController.removeListener(_handleInputChange);
378
         toolbar.markNeedsRebuild();
398
         toolbar.markNeedsRebuild();
379
-        toolbar.editor.focus(context);
399
+        toolbar.editor.focus();
380
       }
400
       }
381
     });
401
     });
382
   }
402
   }
388
         _inputKey = null;
408
         _inputKey = null;
389
         _inputController.text = '';
409
         _inputController.text = '';
390
         _inputController.removeListener(_handleInputChange);
410
         _inputController.removeListener(_handleInputChange);
391
-        editor.focus(context);
411
+        editor.focus();
392
       });
412
       });
393
     }
413
     }
394
   }
414
   }
424
   }
444
   }
425
 
445
 
426
   Widget buildOverlay(BuildContext context) {
446
   Widget buildOverlay(BuildContext context) {
427
-
428
     final toolbar = ZefyrToolbar.of(context);
447
     final toolbar = ZefyrToolbar.of(context);
429
     final style = toolbar.editor.selectionStyle;
448
     final style = toolbar.editor.selectionStyle;
430
 
449
 
438
         : _LinkInput(
457
         : _LinkInput(
439
             key: _inputKey,
458
             key: _inputKey,
440
             controller: _inputController,
459
             controller: _inputController,
441
-            focusNode: toolbar.editor.toolbarFocusNode,
460
+            focusNode: _focusNode,
442
             formatError: _formatError,
461
             formatError: _formatError,
443
           );
462
           );
444
     final items = <Widget>[Expanded(child: body)];
463
     final items = <Widget>[Expanded(child: body)];
517
       keyboardType: TextInputType.url,
536
       keyboardType: TextInputType.url,
518
       focusNode: widget.focusNode,
537
       focusNode: widget.focusNode,
519
       controller: widget.controller,
538
       controller: widget.controller,
520
-      autofocus: true,
539
+//      autofocus: true,
521
       decoration: new InputDecoration(
540
       decoration: new InputDecoration(
522
-          hintText: 'https://',
523
-          filled: true,
524
-          fillColor: toolbarTheme.color,
525
-          border: InputBorder.none,
526
-          contentPadding: const EdgeInsets.all(10.0)),
541
+        hintText: 'https://',
542
+        filled: true,
543
+        fillColor: toolbarTheme.color,
544
+        border: InputBorder.none,
545
+        contentPadding: const EdgeInsets.all(10.0),
546
+      ),
527
     );
547
     );
528
   }
548
   }
529
 }
549
 }

+ 39
- 20
packages/zefyr/lib/src/widgets/editor.dart View File

15
     @required ZefyrImageDelegate imageDelegate,
15
     @required ZefyrImageDelegate imageDelegate,
16
     @required ZefyrController controller,
16
     @required ZefyrController controller,
17
     @required FocusNode focusNode,
17
     @required FocusNode focusNode,
18
-    @required FocusNode toolbarFocusNode,
18
+    @required FocusScopeNode focusScope,
19
   })  : _controller = controller,
19
   })  : _controller = controller,
20
         _imageDelegate = imageDelegate,
20
         _imageDelegate = imageDelegate,
21
-        _focusNode = focusNode,
22
-        _toolbarFocusNode = toolbarFocusNode {
21
+        _focusScope = focusScope,
22
+        _focusNode = focusNode {
23
     _selectionStyle = _controller.getSelectionStyle();
23
     _selectionStyle = _controller.getSelectionStyle();
24
     _selection = _controller.selection;
24
     _selection = _controller.selection;
25
     _controller.addListener(_handleControllerChange);
25
     _controller.addListener(_handleControllerChange);
26
-    toolbarFocusNode.addListener(_handleFocusChange);
27
     _focusNode.addListener(_handleFocusChange);
26
     _focusNode.addListener(_handleFocusChange);
28
   }
27
   }
29
 
28
 
32
   ZefyrImageDelegate _imageDelegate;
31
   ZefyrImageDelegate _imageDelegate;
33
   ZefyrImageDelegate get imageDelegate => _imageDelegate;
32
   ZefyrImageDelegate get imageDelegate => _imageDelegate;
34
 
33
 
34
+  FocusScopeNode get focusScope => _focusScope;
35
+  FocusScopeNode _focusScope;
35
   FocusNode _focusNode;
36
   FocusNode _focusNode;
36
-  FocusNode _toolbarFocusNode;
37
-  FocusNode get toolbarFocusNode => _toolbarFocusNode;
38
 
37
 
39
   ZefyrController _controller;
38
   ZefyrController _controller;
40
   NotusStyle get selectionStyle => _selectionStyle;
39
   NotusStyle get selectionStyle => _selectionStyle;
46
   void dispose() {
45
   void dispose() {
47
     assert(!_disposed);
46
     assert(!_disposed);
48
     _controller.removeListener(_handleControllerChange);
47
     _controller.removeListener(_handleControllerChange);
49
-    _toolbarFocusNode.removeListener(_handleFocusChange);
50
     _focusNode.removeListener(_handleFocusChange);
48
     _focusNode.removeListener(_handleFocusChange);
51
     _disposed = true;
49
     _disposed = true;
52
     super.dispose();
50
     super.dispose();
102
     notifyListeners();
100
     notifyListeners();
103
   }
101
   }
104
 
102
 
103
+  FocusNode _toolbarFocusNode;
104
+
105
+  void setToolbarFocusNode(FocusNode node) {
106
+    assert(!_disposed);
107
+    if (_toolbarFocusNode != node) {
108
+      _toolbarFocusNode?.removeListener(_handleFocusChange);
109
+      _toolbarFocusNode = node;
110
+      _toolbarFocusNode.addListener(_handleFocusChange);
111
+      notifyListeners();
112
+    }
113
+  }
114
+
105
   FocusOwner get focusOwner {
115
   FocusOwner get focusOwner {
106
     assert(!_disposed);
116
     assert(!_disposed);
107
     if (_focusNode.hasFocus) {
117
     if (_focusNode.hasFocus) {
108
       return FocusOwner.editor;
118
       return FocusOwner.editor;
109
-    } else if (toolbarFocusNode.hasFocus) {
119
+    } else if (_toolbarFocusNode?.hasFocus == true) {
110
       return FocusOwner.toolbar;
120
       return FocusOwner.toolbar;
111
     } else {
121
     } else {
112
       return FocusOwner.none;
122
       return FocusOwner.none;
124
     _controller.formatSelection(value);
134
     _controller.formatSelection(value);
125
   }
135
   }
126
 
136
 
127
-  void focus(BuildContext context) {
137
+  void focus() {
128
     assert(!_disposed);
138
     assert(!_disposed);
129
-    FocusScope.of(context).requestFocus(_focusNode);
139
+    _focusScope.requestFocus(_focusNode);
130
   }
140
   }
131
 
141
 
132
   void hideKeyboard() {
142
   void hideKeyboard() {
181
 }
191
 }
182
 
192
 
183
 class _ZefyrEditorState extends State<ZefyrEditor> {
193
 class _ZefyrEditorState extends State<ZefyrEditor> {
184
-  final FocusNode _toolbarFocusNode = new FocusNode();
185
   ZefyrImageDelegate _imageDelegate;
194
   ZefyrImageDelegate _imageDelegate;
186
   ZefyrEditorScope _scope;
195
   ZefyrEditorScope _scope;
187
   ZefyrThemeData _themeData;
196
   ZefyrThemeData _themeData;
194
       builder: (context) => _ZefyrToolbarContainer(
203
       builder: (context) => _ZefyrToolbarContainer(
195
             theme: _themeData,
204
             theme: _themeData,
196
             toolbar: ZefyrToolbar(
205
             toolbar: ZefyrToolbar(
197
-              focusNode: _toolbarFocusNode,
198
               editor: _scope,
206
               editor: _scope,
199
               delegate: widget.toolbarDelegate,
207
               delegate: widget.toolbarDelegate,
200
             ),
208
             ),
213
       hideToolbar();
221
       hideToolbar();
214
     } else if (_toolbar == null) {
222
     } else if (_toolbar == null) {
215
       showToolbar();
223
       showToolbar();
224
+    } else {
225
+      WidgetsBinding.instance.addPostFrameCallback((_) {
226
+        _toolbar?.markNeedsBuild();
227
+      });
216
     }
228
     }
217
   }
229
   }
218
 
230
 
220
   void initState() {
232
   void initState() {
221
     super.initState();
233
     super.initState();
222
     _imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate();
234
     _imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate();
223
-    _scope = ZefyrEditorScope(
224
-      toolbarFocusNode: _toolbarFocusNode,
225
-      imageDelegate: _imageDelegate,
226
-      controller: widget.controller,
227
-      focusNode: widget.focusNode,
228
-    );
229
-    _scope.addListener(_handleChange);
230
   }
235
   }
231
 
236
 
232
   @override
237
   @override
244
   void didChangeDependencies() {
249
   void didChangeDependencies() {
245
     super.didChangeDependencies();
250
     super.didChangeDependencies();
246
 
251
 
252
+    if (_scope == null) {
253
+      _scope = ZefyrEditorScope(
254
+        imageDelegate: _imageDelegate,
255
+        controller: widget.controller,
256
+        focusNode: widget.focusNode,
257
+        focusScope: FocusScope.of(context),
258
+      );
259
+      _scope.addListener(_handleChange);
260
+    } else {
261
+      final focusScope = FocusScope.of(context);
262
+      if (focusScope != _scope._focusScope) {
263
+        _scope._focusScope = focusScope;
264
+      }
265
+    }
266
+
247
     final parentTheme = ZefyrTheme.of(context, nullOk: true);
267
     final parentTheme = ZefyrTheme.of(context, nullOk: true);
248
     final fallbackTheme = ZefyrThemeData.fallback(context);
268
     final fallbackTheme = ZefyrThemeData.fallback(context);
249
     _themeData = (parentTheme != null)
269
     _themeData = (parentTheme != null)
263
     hideToolbar();
283
     hideToolbar();
264
     _scope.removeListener(_handleChange);
284
     _scope.removeListener(_handleChange);
265
     _scope.dispose();
285
     _scope.dispose();
266
-    _toolbarFocusNode.dispose();
267
     super.dispose();
286
     super.dispose();
268
   }
287
   }
269
 
288
 

+ 11
- 20
packages/zefyr/lib/src/widgets/toolbar.dart View File

102
 
102
 
103
   const ZefyrToolbar({
103
   const ZefyrToolbar({
104
     Key key,
104
     Key key,
105
-    @required this.focusNode,
106
     @required this.editor,
105
     @required this.editor,
107
     this.autoHide: true,
106
     this.autoHide: true,
108
     this.delegate,
107
     this.delegate,
109
   }) : super(key: key);
108
   }) : super(key: key);
110
 
109
 
111
-  final FocusNode focusNode;
112
   final ZefyrToolbarDelegate delegate;
110
   final ZefyrToolbarDelegate delegate;
113
   final ZefyrEditorScope editor;
111
   final ZefyrEditorScope editor;
114
 
112
 
187
 
185
 
188
   ZefyrEditorScope get editor => widget.editor;
186
   ZefyrEditorScope get editor => widget.editor;
189
 
187
 
190
-  void _handleChange() {
191
-    if (_selection != editor.selection) {
192
-      _selection = editor.selection;
193
-      closeOverlay();
194
-    }
195
-    setState(() {});
196
-  }
197
-
198
   @override
188
   @override
199
   void initState() {
189
   void initState() {
200
     super.initState();
190
     super.initState();
201
     _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate();
191
     _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate();
202
     _overlayAnimation = new AnimationController(
192
     _overlayAnimation = new AnimationController(
203
         vsync: this, duration: Duration(milliseconds: 100));
193
         vsync: this, duration: Duration(milliseconds: 100));
204
-    widget.editor.addListener(_handleChange);
205
   }
194
   }
206
 
195
 
207
   @override
196
   @override
210
     if (widget.delegate != oldWidget.delegate) {
199
     if (widget.delegate != oldWidget.delegate) {
211
       _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate();
200
       _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate();
212
     }
201
     }
213
-    if (widget.editor != oldWidget.editor) {
214
-      oldWidget.editor.removeListener(_handleChange);
215
-      widget.editor.addListener(_handleChange);
202
+    if (_selection != editor.selection) {
203
+      _selection = editor.selection;
204
+      closeOverlay();
216
     }
205
     }
217
   }
206
   }
218
 
207
 
219
   @override
208
   @override
220
   void dispose() {
209
   void dispose() {
221
-    widget.editor.removeListener(_handleChange);
222
     super.dispose();
210
     super.dispose();
223
   }
211
   }
224
 
212
 
253
 
241
 
254
     final constraints =
242
     final constraints =
255
         BoxConstraints.tightFor(height: ZefyrToolbar.kToolbarHeight);
243
         BoxConstraints.tightFor(height: ZefyrToolbar.kToolbarHeight);
256
-    return new _ZefyrToolbarScope(
257
-      toolbar: this,
258
-      child: Container(
259
-        constraints: constraints,
260
-        child: Stack(children: layers),
244
+    return FocusScope(
245
+      node: editor.focusScope,
246
+      child: _ZefyrToolbarScope(
247
+        toolbar: this,
248
+        child: Container(
249
+          constraints: constraints,
250
+          child: Stack(children: layers),
251
+        ),
261
       ),
252
       ),
262
     );
253
     );
263
   }
254
   }

+ 1
- 0
packages/zefyr/test/widgets/buttons_test.dart View File

104
       await tester
104
       await tester
105
           .tap(find.widgetWithText(GestureDetector, 'Tap to edit link'));
105
           .tap(find.widgetWithText(GestureDetector, 'Tap to edit link'));
106
       await tester.pumpAndSettle();
106
       await tester.pumpAndSettle();
107
+      expect(editor.focusNode.hasFocus, isFalse);
107
       await editor.updateSelection(base: 10, extent: 10);
108
       await editor.updateSelection(base: 10, extent: 10);
108
       expect(find.byIcon(Icons.link_off), findsNothing);
109
       expect(find.byIcon(Icons.link_off), findsNothing);
109
     });
110
     });

+ 1
- 1
packages/zefyr/test/widgets/selection_test.dart View File

65
       RenderBox renderObject =
65
       RenderBox renderObject =
66
           tester.firstRenderObject(find.byType(ZefyrEditableText));
66
           tester.firstRenderObject(find.byType(ZefyrEditableText));
67
       var offset = renderObject.localToGlobal(Offset.zero);
67
       var offset = renderObject.localToGlobal(Offset.zero);
68
-      offset += Offset(50.0, renderObject.size.height - 5.0);
68
+      offset += Offset(50.0, renderObject.size.height - 500.0);
69
       await tester.tapAt(offset);
69
       await tester.tapAt(offset);
70
       await tester.pumpAndSettle();
70
       await tester.pumpAndSettle();
71
       expect(editor.controller.selection.isCollapsed, isTrue);
71
       expect(editor.controller.selection.isCollapsed, isTrue);