zefyr

editor.dart 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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 'package:zefyr/src/widgets/link.dart';
  8. import 'controller.dart';
  9. import 'editable_text.dart';
  10. import 'image.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.linkDelegate,
  28. this.onSave,
  29. this.selectionControls,
  30. this.physics,
  31. this.keyboardAppearance,
  32. }) : assert(mode != null),
  33. assert(controller != null),
  34. assert(focusNode != null),
  35. super(key: key);
  36. /// Controls the document being edited.
  37. final ZefyrController controller;
  38. /// Controls whether this editor has keyboard focus.
  39. final FocusNode focusNode;
  40. /// Whether this editor should focus itself if nothing else is already
  41. /// focused.
  42. ///
  43. /// If true, the keyboard will open as soon as this text field obtains focus.
  44. /// Otherwise, the keyboard is only shown after the user taps the text field.
  45. ///
  46. /// Defaults to true. Cannot be null.
  47. final bool autofocus;
  48. /// Editing mode of this editor.
  49. final ZefyrMode mode;
  50. /// Optional delegate for customizing this editor's toolbar.
  51. final ZefyrToolbarDelegate toolbarDelegate;
  52. /// Delegate for resolving embedded images.
  53. ///
  54. /// This delegate is required if embedding images is allowed.
  55. final ZefyrImageDelegate imageDelegate;
  56. final ZefyrLinkDelegate linkDelegate;
  57. final void Function() onSave;
  58. /// Optional delegate for building the text selection handles and toolbar.
  59. ///
  60. /// If not provided then platform-specific implementation is used by default.
  61. final TextSelectionControls selectionControls;
  62. /// Controls physics of scrollable editor.
  63. final ScrollPhysics physics;
  64. /// Padding around editable area.
  65. final EdgeInsets padding;
  66. /// The appearance of the keyboard.
  67. ///
  68. /// This setting is only honored on iOS devices.
  69. ///
  70. /// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness].
  71. final Brightness keyboardAppearance;
  72. @override
  73. _ZefyrEditorState createState() => _ZefyrEditorState();
  74. }
  75. class _ZefyrEditorState extends State<ZefyrEditor> {
  76. ZefyrImageDelegate _imageDelegate;
  77. ZefyrLinkDelegate _linkDelegate;
  78. ZefyrScope _scope;
  79. ZefyrThemeData _themeData;
  80. GlobalKey<ZefyrToolbarState> _toolbarKey;
  81. ZefyrScaffoldState _scaffold;
  82. bool get hasToolbar => _toolbarKey != null;
  83. void showToolbar() {
  84. assert(_toolbarKey == null);
  85. _toolbarKey = GlobalKey();
  86. _scaffold.showToolbar(buildToolbar);
  87. }
  88. void hideToolbar() {
  89. if (_toolbarKey == null) return;
  90. _scaffold.hideToolbar(buildToolbar);
  91. _toolbarKey = null;
  92. }
  93. Widget buildToolbar(BuildContext context) {
  94. return ZefyrTheme(
  95. data: _themeData,
  96. child: ZefyrToolbar(
  97. key: _toolbarKey,
  98. editor: _scope,
  99. delegate: widget.toolbarDelegate,
  100. ),
  101. );
  102. }
  103. void _handleChange() {
  104. if (_scope.focusOwner == FocusOwner.none) {
  105. if (!_scope.keepOverlay) {
  106. _scope.toolbarAction = null;
  107. hideToolbar();
  108. }
  109. } else if (!hasToolbar) {
  110. showToolbar();
  111. } else {
  112. // TODO: is there a nicer way to do this?
  113. WidgetsBinding.instance.addPostFrameCallback((_) {
  114. _toolbarKey?.currentState?.markNeedsRebuild();
  115. });
  116. }
  117. }
  118. @override
  119. void initState() {
  120. super.initState();
  121. _imageDelegate = widget.imageDelegate;
  122. _linkDelegate = widget.linkDelegate;
  123. }
  124. @override
  125. void didUpdateWidget(ZefyrEditor oldWidget) {
  126. super.didUpdateWidget(oldWidget);
  127. _scope.mode = widget.mode;
  128. _scope.controller = widget.controller;
  129. _scope.focusNode = widget.focusNode;
  130. _scope.onSave = widget.onSave;
  131. if (widget.imageDelegate != oldWidget.imageDelegate) {
  132. _imageDelegate = widget.imageDelegate;
  133. _scope.imageDelegate = _imageDelegate;
  134. }
  135. if (widget.linkDelegate != oldWidget.linkDelegate) {
  136. _linkDelegate = widget.linkDelegate;
  137. _scope.linkDelegate = _linkDelegate;
  138. }
  139. }
  140. @override
  141. void didChangeDependencies() {
  142. super.didChangeDependencies();
  143. final parentTheme = ZefyrTheme.of(context, nullOk: true);
  144. final fallbackTheme = ZefyrThemeData.fallback(context);
  145. _themeData = (parentTheme != null)
  146. ? fallbackTheme.merge(parentTheme)
  147. : fallbackTheme;
  148. if (_scope == null) {
  149. _scope = ZefyrScope.editable(
  150. mode: widget.mode,
  151. imageDelegate: _imageDelegate,
  152. linkDelegate: _linkDelegate,
  153. onSave: widget.onSave,
  154. controller: widget.controller,
  155. focusNode: widget.focusNode,
  156. focusScope: FocusScope.of(context),
  157. );
  158. _scope.addListener(_handleChange);
  159. } else {
  160. final focusScope = FocusScope.of(context);
  161. _scope.focusScope = focusScope;
  162. }
  163. final scaffold = ZefyrScaffold.of(context);
  164. if (_scaffold != scaffold) {
  165. bool didHaveToolbar = hasToolbar;
  166. hideToolbar();
  167. _scaffold = scaffold;
  168. if (didHaveToolbar) showToolbar();
  169. }
  170. }
  171. @override
  172. void dispose() {
  173. hideToolbar();
  174. _scope.removeListener(_handleChange);
  175. _scope.dispose();
  176. super.dispose();
  177. }
  178. @override
  179. Widget build(BuildContext context) {
  180. final ThemeData themeData = Theme.of(context);
  181. final Brightness keyboardAppearance =
  182. widget.keyboardAppearance ?? themeData.primaryColorBrightness;
  183. Widget editable = ZefyrEditableText(
  184. controller: _scope.controller,
  185. focusNode: _scope.focusNode,
  186. imageDelegate: _scope.imageDelegate,
  187. selectionControls: widget.selectionControls,
  188. autofocus: widget.autofocus,
  189. mode: widget.mode,
  190. input: _scope.input,
  191. padding: widget.padding,
  192. physics: widget.physics,
  193. keyboardAppearance: keyboardAppearance,
  194. );
  195. return ZefyrTheme(
  196. data: _themeData,
  197. child: ZefyrScopeAccess(
  198. scope: _scope,
  199. child: editable,
  200. ),
  201. );
  202. }
  203. }