zefyr

editable_text.dart 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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:notus/notus.dart';
  8. import '../../zefyr.dart';
  9. import 'code.dart';
  10. import 'common.dart';
  11. import 'controller.dart';
  12. import 'cursor_timer.dart';
  13. import 'editor.dart';
  14. import 'image.dart';
  15. import 'input.dart';
  16. import 'list.dart';
  17. import 'mode.dart';
  18. import 'paragraph.dart';
  19. import 'quote.dart';
  20. import 'render_context.dart';
  21. import 'scope.dart';
  22. import 'selection.dart';
  23. import 'theme.dart';
  24. /// Core widget responsible for editing Zefyr documents.
  25. ///
  26. /// Depends on presence of [ZefyrTheme] and [ZefyrScope] somewhere up the
  27. /// widget tree.
  28. ///
  29. /// Consider using [ZefyrEditor] which wraps this widget and adds a toolbar to
  30. /// edit style attributes.
  31. class ZefyrEditableText extends StatefulWidget {
  32. const ZefyrEditableText({
  33. Key key,
  34. @required this.controller,
  35. @required this.focusNode,
  36. @required this.imageDelegate,
  37. this.selectionControls,
  38. this.autofocus = true,
  39. this.mode = ZefyrMode.edit,
  40. @required this.input,
  41. this.padding = const EdgeInsets.symmetric(horizontal: 16.0),
  42. this.physics,
  43. this.keyboardAppearance = Brightness.light,
  44. }) : assert(mode != null),
  45. assert(controller != null),
  46. assert(focusNode != null),
  47. assert(keyboardAppearance != null),
  48. super(key: key);
  49. /// Controls the document being edited.
  50. final ZefyrController controller;
  51. /// Controls whether this editor has keyboard focus.
  52. final FocusNode focusNode;
  53. final ZefyrImageDelegate imageDelegate;
  54. /// Whether this text field should focus itself if nothing else is already
  55. /// focused.
  56. ///
  57. /// If true, the keyboard will open as soon as this text field obtains focus.
  58. /// Otherwise, the keyboard is only shown after the user taps the text field.
  59. ///
  60. /// Defaults to true. Cannot be null.
  61. final bool autofocus;
  62. /// Editing mode of this text field.
  63. final ZefyrMode mode;
  64. final InputConnectionController input;
  65. /// Controls physics of scrollable text field.
  66. final ScrollPhysics physics;
  67. /// Optional delegate for building the text selection handles and toolbar.
  68. ///
  69. /// If not provided then platform-specific implementation is used by default.
  70. final TextSelectionControls selectionControls;
  71. /// Padding around editable area.
  72. final EdgeInsets padding;
  73. /// The appearance of the keyboard.
  74. ///
  75. /// This setting is only honored on iOS devices.
  76. ///
  77. /// If unset, defaults to the brightness of [Brightness.light].
  78. final Brightness keyboardAppearance;
  79. @override
  80. _ZefyrEditableTextState createState() => _ZefyrEditableTextState();
  81. }
  82. class _ZefyrEditableTextState extends State<ZefyrEditableText>
  83. with AutomaticKeepAliveClientMixin {
  84. //
  85. // New public members
  86. //
  87. /// Document controlled by this widget.
  88. NotusDocument get document => widget.controller.document;
  89. /// Current text selection.
  90. TextSelection get selection => widget.controller.selection;
  91. FocusNode _focusNode;
  92. FocusAttachment _focusAttachment;
  93. /// Express interest in interacting with the keyboard.
  94. ///
  95. /// If this control is already attached to the keyboard, this function will
  96. /// request that the keyboard become visible. Otherwise, this function will
  97. /// ask the focus system that it become focused. If successful in acquiring
  98. /// focus, the control will then attach to the keyboard and request that the
  99. /// keyboard become visible.
  100. void requestKeyboard() {
  101. final scope = ZefyrScope.of(context);
  102. if (_focusNode.hasFocus) {
  103. var show = scope.toolbarStatus == ToolbarStatus.open ? false : true;
  104. _input.openConnection(widget.controller.plainTextEditingValue, widget.keyboardAppearance, show: show);
  105. if (show) {
  106. print(MediaQuery.of(context).viewInsets.bottom);
  107. }
  108. } else {
  109. FocusScope.of(context).requestFocus(_focusNode);
  110. }
  111. }
  112. void focusOrUnfocusIfNeeded() {
  113. if (!_didAutoFocus && widget.autofocus && widget.mode.canEdit) {
  114. FocusScope.of(context).autofocus(_focusNode);
  115. _didAutoFocus = true;
  116. }
  117. if (!widget.mode.canEdit && _focusNode.hasFocus) {
  118. _didAutoFocus = false;
  119. _focusNode.unfocus();
  120. }
  121. }
  122. TextSelectionControls defaultSelectionControls(BuildContext context) {
  123. TargetPlatform platform = Theme.of(context).platform;
  124. if (platform == TargetPlatform.iOS) {
  125. return cupertinoTextSelectionControls;
  126. }
  127. return materialTextSelectionControls;
  128. }
  129. //
  130. // Overridden members of State
  131. //
  132. @override
  133. Widget build(BuildContext context) {
  134. _focusAttachment.reparent();
  135. super.build(context); // See AutomaticKeepAliveState.
  136. Widget body = ListBody(children: _buildChildren(context));
  137. if (widget.padding != null) {
  138. body = Padding(padding: widget.padding, child: body);
  139. }
  140. body = SingleChildScrollView(
  141. physics: widget.physics,
  142. controller: _scrollController,
  143. child: body,
  144. );
  145. final layers = <Widget>[body];
  146. layers.add(ZefyrSelectionOverlay(
  147. controls: widget.selectionControls ?? defaultSelectionControls(context),
  148. ));
  149. return Stack(fit: StackFit.expand, children: layers);
  150. }
  151. @override
  152. void initState() {
  153. _focusNode = widget.focusNode;
  154. super.initState();
  155. _focusAttachment = _focusNode.attach(context);
  156. _updateSubscriptions();
  157. }
  158. @override
  159. void didUpdateWidget(ZefyrEditableText oldWidget) {
  160. super.didUpdateWidget(oldWidget);
  161. if (_focusNode != widget.focusNode) {
  162. _focusAttachment.detach();
  163. _focusNode = widget.focusNode;
  164. _focusAttachment = _focusNode.attach(context);
  165. }
  166. _updateSubscriptions(oldWidget);
  167. focusOrUnfocusIfNeeded();
  168. }
  169. @override
  170. void didChangeDependencies() {
  171. super.didChangeDependencies();
  172. final scope = ZefyrScope.of(context);
  173. if (_renderContext != scope.renderContext) {
  174. _renderContext?.removeListener(_handleRenderContextChange);
  175. _renderContext = scope.renderContext;
  176. _renderContext.addListener(_handleRenderContextChange);
  177. }
  178. if (_cursorTimer != scope.cursorTimer) {
  179. _cursorTimer?.stop();
  180. _cursorTimer = scope.cursorTimer;
  181. _cursorTimer.startOrStop(_focusNode, selection);
  182. }
  183. focusOrUnfocusIfNeeded();
  184. }
  185. @override
  186. void dispose() {
  187. _focusAttachment.detach();
  188. _cancelSubscriptions();
  189. super.dispose();
  190. }
  191. //
  192. // Overridden members of AutomaticKeepAliveClientMixin
  193. //
  194. @override
  195. bool get wantKeepAlive => _focusNode.hasFocus;
  196. //
  197. // Private members
  198. //
  199. final ScrollController _scrollController = ScrollController();
  200. ZefyrRenderContext _renderContext;
  201. CursorTimer _cursorTimer;
  202. InputConnectionController get _input => widget.input;
  203. bool _didAutoFocus = false;
  204. List<Widget> _buildChildren(BuildContext context) {
  205. final result = <Widget>[];
  206. for (var node in document.root.children) {
  207. result.add(_defaultChildBuilder(context, node));
  208. }
  209. return result;
  210. }
  211. Widget _defaultChildBuilder(BuildContext context, Node node) {
  212. if (node is LineNode) {
  213. if (node.hasEmbed) {
  214. return ZefyrLine(node: node);
  215. } else if (node.style.contains(NotusAttribute.heading)) {
  216. return ZefyrHeading(node: node);
  217. }
  218. return ZefyrParagraph(node: node);
  219. }
  220. final BlockNode block = node;
  221. final blockStyle = block.style.get(NotusAttribute.block);
  222. if (blockStyle == NotusAttribute.block.code) {
  223. return ZefyrCode(node: block);
  224. } else if (blockStyle == NotusAttribute.block.bulletList) {
  225. return ZefyrList(node: block);
  226. } else if (blockStyle == NotusAttribute.block.numberList) {
  227. return ZefyrList(node: block);
  228. } else if (blockStyle == NotusAttribute.block.quote) {
  229. return ZefyrQuote(node: block);
  230. }
  231. throw UnimplementedError('Block format $blockStyle.');
  232. }
  233. void _updateSubscriptions([ZefyrEditableText oldWidget]) {
  234. if (oldWidget == null) {
  235. widget.controller.addListener(_handleLocalValueChange);
  236. _focusNode.addListener(_handleFocusChange);
  237. return;
  238. }
  239. if (widget.controller != oldWidget.controller) {
  240. oldWidget.controller.removeListener(_handleLocalValueChange);
  241. widget.controller.addListener(_handleLocalValueChange);
  242. _input.updateRemoteValue(widget.controller.plainTextEditingValue);
  243. }
  244. if (widget.focusNode != oldWidget.focusNode) {
  245. oldWidget.focusNode.removeListener(_handleFocusChange);
  246. widget.focusNode.addListener(_handleFocusChange);
  247. updateKeepAlive();
  248. }
  249. }
  250. void _cancelSubscriptions() {
  251. _renderContext.removeListener(_handleRenderContextChange);
  252. widget.controller.removeListener(_handleLocalValueChange);
  253. _focusNode.removeListener(_handleFocusChange);
  254. _input.closeConnection();
  255. _cursorTimer.stop();
  256. }
  257. // Triggered for both text and selection changes.
  258. void _handleLocalValueChange() {
  259. if (widget.mode.canEdit &&
  260. widget.controller.lastChangeSource == ChangeSource.local) {
  261. // Only request keyboard for user actions.
  262. requestKeyboard();
  263. }
  264. _input.updateRemoteValue(widget.controller.plainTextEditingValue);
  265. _cursorTimer.startOrStop(_focusNode, selection);
  266. setState(() {
  267. // nothing to update internally.
  268. });
  269. }
  270. void _handleFocusChange() {
  271. _input.openOrCloseConnection(_focusNode,
  272. widget.controller.plainTextEditingValue, widget.keyboardAppearance, show: !_focusNode.consumeKeyboardToken());
  273. _cursorTimer.startOrStop(_focusNode, selection);
  274. updateKeepAlive();
  275. }
  276. void _handleRemoteValueChange(
  277. int start, String deleted, String inserted, TextSelection selection) {
  278. widget.controller
  279. .replaceText(start, deleted.length, inserted, selection: selection);
  280. }
  281. void _handleRenderContextChange() {
  282. setState(() {
  283. // nothing to update internally.
  284. });
  285. }
  286. }