zefyr

editor.dart 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file
  2. // for details. All rights reserved. Use of this source code is governed by a
  3. // BSD-style license that can be found in the LICENSE file.
  4. import 'package:flutter/cupertino.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:flutter/widgets.dart';
  7. import 'controller.dart';
  8. import 'editable_text.dart';
  9. import 'image.dart';
  10. import 'input.dart';
  11. import 'mode.dart';
  12. import 'scaffold.dart';
  13. import 'scope.dart';
  14. import 'theme.dart';
  15. import 'toolbar.dart';
  16. /// Widget for editing Zefyr documents.
  17. class ZefyrEditor extends StatefulWidget {
  18. const ZefyrEditor({
  19. Key key,
  20. @required this.controller,
  21. @required this.focusNode,
  22. this.autofocus = true,
  23. this.mode = ZefyrMode.edit,
  24. this.padding = const EdgeInsets.symmetric(horizontal: 16.0),
  25. this.toolbarDelegate,
  26. this.imageDelegate,
  27. this.selectionControls,
  28. this.physics,
  29. this.keyboardAppearance,
  30. }) : assert(mode != null),
  31. assert(controller != null),
  32. assert(focusNode != null),
  33. super(key: key);
  34. /// Controls the document being edited.
  35. final ZefyrController controller;
  36. /// Controls whether this editor has keyboard focus.
  37. final FocusNode focusNode;
  38. /// Whether this editor should focus itself if nothing else is already
  39. /// focused.
  40. ///
  41. /// If true, the keyboard will open as soon as this text field obtains focus.
  42. /// Otherwise, the keyboard is only shown after the user taps the text field.
  43. ///
  44. /// Defaults to true. Cannot be null.
  45. final bool autofocus;
  46. /// Editing mode of this editor.
  47. final ZefyrMode mode;
  48. /// Optional delegate for customizing this editor's toolbar.
  49. final ZefyrToolbarDelegate toolbarDelegate;
  50. /// Delegate for resolving embedded images.
  51. ///
  52. /// This delegate is required if embedding images is allowed.
  53. final ZefyrImageDelegate imageDelegate;
  54. /// Optional delegate for building the text selection handles and toolbar.
  55. ///
  56. /// If not provided then platform-specific implementation is used by default.
  57. final TextSelectionControls selectionControls;
  58. /// Controls physics of scrollable editor.
  59. final ScrollPhysics physics;
  60. /// Padding around editable area.
  61. final EdgeInsets padding;
  62. /// The appearance of the keyboard.
  63. ///
  64. /// This setting is only honored on iOS devices.
  65. ///
  66. /// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness].
  67. final Brightness keyboardAppearance;
  68. @override
  69. _ZefyrEditorState createState() => _ZefyrEditorState();
  70. }
  71. class _ZefyrEditorState extends State<ZefyrEditor> {
  72. ZefyrImageDelegate _imageDelegate;
  73. ZefyrScope _scope;
  74. ZefyrThemeData _themeData;
  75. GlobalKey<ZefyrToolbarState> _toolbarKey;
  76. ZefyrScaffoldState _scaffold;
  77. bool get hasToolbar => _toolbarKey != null;
  78. void showToolbar() {
  79. assert(_toolbarKey == null);
  80. _toolbarKey = GlobalKey();
  81. _scaffold.showToolbar(buildToolbar);
  82. _scope.toolbarStatus = ToolbarStatus.show;
  83. }
  84. void hideToolbar() {
  85. if (_toolbarKey == null) return;
  86. _scaffold.hideToolbar(buildToolbar);
  87. _toolbarKey = null;
  88. _scope.toolbarStatus = ToolbarStatus.hide;
  89. }
  90. Widget buildToolbar(BuildContext context) {
  91. return ZefyrTheme(
  92. data: _themeData,
  93. child: ZefyrToolbar(
  94. key: _toolbarKey,
  95. editor: _scope,
  96. delegate: widget.toolbarDelegate,
  97. ),
  98. );
  99. }
  100. void _handleChange() {
  101. if (_scope.focusOwner == FocusOwner.none) {
  102. hideToolbar();
  103. } else if (!hasToolbar) {
  104. showToolbar();
  105. } else {
  106. // TODO: is there a nicer way to do this?
  107. WidgetsBinding.instance.addPostFrameCallback((_) {
  108. _toolbarKey?.currentState?.markNeedsRebuild();
  109. });
  110. }
  111. }
  112. @override
  113. void initState() {
  114. super.initState();
  115. _imageDelegate = widget.imageDelegate;
  116. }
  117. @override
  118. void didUpdateWidget(ZefyrEditor oldWidget) {
  119. super.didUpdateWidget(oldWidget);
  120. _scope.mode = widget.mode;
  121. _scope.controller = widget.controller;
  122. _scope.focusNode = widget.focusNode;
  123. if (widget.imageDelegate != oldWidget.imageDelegate) {
  124. _imageDelegate = widget.imageDelegate;
  125. _scope.imageDelegate = _imageDelegate;
  126. }
  127. }
  128. @override
  129. void didChangeDependencies() {
  130. super.didChangeDependencies();
  131. final parentTheme = ZefyrTheme.of(context, nullOk: true);
  132. final fallbackTheme = ZefyrThemeData.fallback(context);
  133. _themeData = (parentTheme != null)
  134. ? fallbackTheme.merge(parentTheme)
  135. : fallbackTheme;
  136. if (_scope == null) {
  137. _scope = ZefyrScope.editable(
  138. mode: widget.mode,
  139. imageDelegate: _imageDelegate,
  140. controller: widget.controller,
  141. focusNode: widget.focusNode,
  142. focusScope: FocusScope.of(context),
  143. );
  144. _scope.addListener(_handleChange);
  145. } else {
  146. final focusScope = FocusScope.of(context);
  147. _scope.focusScope = focusScope;
  148. }
  149. final scaffold = ZefyrScaffold.of(context);
  150. if (_scaffold != scaffold) {
  151. bool didHaveToolbar = hasToolbar;
  152. hideToolbar();
  153. _scaffold = scaffold;
  154. if (didHaveToolbar) showToolbar();
  155. }
  156. }
  157. @override
  158. void dispose() {
  159. hideToolbar();
  160. _scope.removeListener(_handleChange);
  161. _scope.dispose();
  162. super.dispose();
  163. }
  164. @override
  165. Widget build(BuildContext context) {
  166. final ThemeData themeData = Theme.of(context);
  167. final Brightness keyboardAppearance =
  168. widget.keyboardAppearance ?? themeData.primaryColorBrightness;
  169. Widget editable = ZefyrEditableText(
  170. controller: _scope.controller,
  171. focusNode: _scope.focusNode,
  172. imageDelegate: _scope.imageDelegate,
  173. selectionControls: widget.selectionControls,
  174. autofocus: widget.autofocus,
  175. mode: widget.mode,
  176. input: _scope.input,
  177. padding: widget.padding,
  178. physics: widget.physics,
  179. keyboardAppearance: keyboardAppearance,
  180. );
  181. return ZefyrTheme(
  182. data: _themeData,
  183. child: ZefyrScopeAccess(
  184. scope: _scope,
  185. child: editable,
  186. ),
  187. );
  188. }
  189. }