123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331 |
- // 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 'package:flutter/cupertino.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/widgets.dart';
- import 'package:notus/notus.dart';
-
- import '../../zefyr.dart';
- import 'code.dart';
- import 'common.dart';
- import 'controller.dart';
- import 'cursor_timer.dart';
- import 'editor.dart';
- import 'image.dart';
- import 'input.dart';
- import 'list.dart';
- import 'mode.dart';
- import 'paragraph.dart';
- import 'quote.dart';
- import 'render_context.dart';
- import 'scope.dart';
- import 'selection.dart';
- import 'theme.dart';
-
- /// Core widget responsible for editing Zefyr documents.
- ///
- /// Depends on presence of [ZefyrTheme] and [ZefyrScope] somewhere up the
- /// widget tree.
- ///
- /// Consider using [ZefyrEditor] which wraps this widget and adds a toolbar to
- /// edit style attributes.
- class ZefyrEditableText extends StatefulWidget {
- const ZefyrEditableText({
- Key key,
- @required this.controller,
- @required this.focusNode,
- @required this.imageDelegate,
- this.selectionControls,
- this.autofocus = true,
- this.mode = ZefyrMode.edit,
- @required this.input,
- this.padding = const EdgeInsets.symmetric(horizontal: 16.0),
- this.physics,
- this.keyboardAppearance = Brightness.light,
- }) : assert(mode != null),
- assert(controller != null),
- assert(focusNode != null),
- assert(keyboardAppearance != null),
- super(key: key);
-
- /// Controls the document being edited.
- final ZefyrController controller;
-
- /// Controls whether this editor has keyboard focus.
- final FocusNode focusNode;
- final ZefyrImageDelegate imageDelegate;
-
- /// Whether this text field should focus itself if nothing else is already
- /// focused.
- ///
- /// If true, the keyboard will open as soon as this text field obtains focus.
- /// Otherwise, the keyboard is only shown after the user taps the text field.
- ///
- /// Defaults to true. Cannot be null.
- final bool autofocus;
-
- /// Editing mode of this text field.
- final ZefyrMode mode;
-
- final InputConnectionController input;
-
- /// Controls physics of scrollable text field.
- final ScrollPhysics physics;
-
- /// Optional delegate for building the text selection handles and toolbar.
- ///
- /// If not provided then platform-specific implementation is used by default.
- final TextSelectionControls selectionControls;
-
- /// Padding around editable area.
- final EdgeInsets padding;
-
- /// The appearance of the keyboard.
- ///
- /// This setting is only honored on iOS devices.
- ///
- /// If unset, defaults to the brightness of [Brightness.light].
- final Brightness keyboardAppearance;
-
- @override
- _ZefyrEditableTextState createState() => _ZefyrEditableTextState();
- }
-
- class _ZefyrEditableTextState extends State<ZefyrEditableText>
- with AutomaticKeepAliveClientMixin {
- //
- // New public members
- //
-
- /// Document controlled by this widget.
- NotusDocument get document => widget.controller.document;
-
- /// Current text selection.
- TextSelection get selection => widget.controller.selection;
-
- FocusNode _focusNode;
- FocusAttachment _focusAttachment;
-
- /// Express interest in interacting with the keyboard.
- ///
- /// If this control is already attached to the keyboard, this function will
- /// request that the keyboard become visible. Otherwise, this function will
- /// ask the focus system that it become focused. If successful in acquiring
- /// focus, the control will then attach to the keyboard and request that the
- /// keyboard become visible.
- void requestKeyboard() {
- final scope = ZefyrScope.of(context);
- if (_focusNode.hasFocus) {
- var show = scope.toolbarStatus == ToolbarStatus.open ? false : true;
- _input.openConnection(widget.controller.plainTextEditingValue, widget.keyboardAppearance, show: show);
- if (show) {
- print(MediaQuery.of(context).viewInsets.bottom);
- }
- } else {
- FocusScope.of(context).requestFocus(_focusNode);
- }
- }
-
- void focusOrUnfocusIfNeeded() {
- if (!_didAutoFocus && widget.autofocus && widget.mode.canEdit) {
- FocusScope.of(context).autofocus(_focusNode);
- _didAutoFocus = true;
- }
- if (!widget.mode.canEdit && _focusNode.hasFocus) {
- _didAutoFocus = false;
- _focusNode.unfocus();
- }
- }
-
- TextSelectionControls defaultSelectionControls(BuildContext context) {
- TargetPlatform platform = Theme.of(context).platform;
- if (platform == TargetPlatform.iOS) {
- return cupertinoTextSelectionControls;
- }
- return materialTextSelectionControls;
- }
-
- //
- // Overridden members of State
- //
-
- @override
- Widget build(BuildContext context) {
- _focusAttachment.reparent();
- super.build(context); // See AutomaticKeepAliveState.
-
- Widget body = ListBody(children: _buildChildren(context));
- if (widget.padding != null) {
- body = Padding(padding: widget.padding, child: body);
- }
-
- body = SingleChildScrollView(
- physics: widget.physics,
- controller: _scrollController,
- child: body,
- );
-
- final layers = <Widget>[body];
- layers.add(ZefyrSelectionOverlay(
- controls: widget.selectionControls ?? defaultSelectionControls(context),
- ));
-
- return Stack(fit: StackFit.expand, children: layers);
- }
-
- @override
- void initState() {
- _focusNode = widget.focusNode;
- super.initState();
- _focusAttachment = _focusNode.attach(context);
- _updateSubscriptions();
- }
-
- @override
- void didUpdateWidget(ZefyrEditableText oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (_focusNode != widget.focusNode) {
- _focusAttachment.detach();
- _focusNode = widget.focusNode;
- _focusAttachment = _focusNode.attach(context);
- }
- _updateSubscriptions(oldWidget);
- focusOrUnfocusIfNeeded();
- }
-
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- final scope = ZefyrScope.of(context);
- if (_renderContext != scope.renderContext) {
- _renderContext?.removeListener(_handleRenderContextChange);
- _renderContext = scope.renderContext;
- _renderContext.addListener(_handleRenderContextChange);
- }
- if (_cursorTimer != scope.cursorTimer) {
- _cursorTimer?.stop();
- _cursorTimer = scope.cursorTimer;
- _cursorTimer.startOrStop(_focusNode, selection);
- }
- focusOrUnfocusIfNeeded();
- }
-
- @override
- void dispose() {
- _focusAttachment.detach();
- _cancelSubscriptions();
- super.dispose();
- }
-
- //
- // Overridden members of AutomaticKeepAliveClientMixin
- //
-
- @override
- bool get wantKeepAlive => _focusNode.hasFocus;
-
- //
- // Private members
- //
-
- final ScrollController _scrollController = ScrollController();
- ZefyrRenderContext _renderContext;
- CursorTimer _cursorTimer;
- InputConnectionController get _input => widget.input;
- bool _didAutoFocus = false;
-
- List<Widget> _buildChildren(BuildContext context) {
- final result = <Widget>[];
- for (var node in document.root.children) {
- result.add(_defaultChildBuilder(context, node));
- }
- return result;
- }
-
- Widget _defaultChildBuilder(BuildContext context, Node node) {
- if (node is LineNode) {
- if (node.hasEmbed) {
- return ZefyrLine(node: node);
- } else if (node.style.contains(NotusAttribute.heading)) {
- return ZefyrHeading(node: node);
- }
- return ZefyrParagraph(node: node);
- }
-
- final BlockNode block = node;
- final blockStyle = block.style.get(NotusAttribute.block);
- if (blockStyle == NotusAttribute.block.code) {
- return ZefyrCode(node: block);
- } else if (blockStyle == NotusAttribute.block.bulletList) {
- return ZefyrList(node: block);
- } else if (blockStyle == NotusAttribute.block.numberList) {
- return ZefyrList(node: block);
- } else if (blockStyle == NotusAttribute.block.quote) {
- return ZefyrQuote(node: block);
- }
-
- throw UnimplementedError('Block format $blockStyle.');
- }
-
- void _updateSubscriptions([ZefyrEditableText oldWidget]) {
- if (oldWidget == null) {
- widget.controller.addListener(_handleLocalValueChange);
- _focusNode.addListener(_handleFocusChange);
- return;
- }
-
- if (widget.controller != oldWidget.controller) {
- oldWidget.controller.removeListener(_handleLocalValueChange);
- widget.controller.addListener(_handleLocalValueChange);
- _input.updateRemoteValue(widget.controller.plainTextEditingValue);
- }
- if (widget.focusNode != oldWidget.focusNode) {
- oldWidget.focusNode.removeListener(_handleFocusChange);
- widget.focusNode.addListener(_handleFocusChange);
- updateKeepAlive();
- }
- }
-
- void _cancelSubscriptions() {
- _renderContext.removeListener(_handleRenderContextChange);
- widget.controller.removeListener(_handleLocalValueChange);
- _focusNode.removeListener(_handleFocusChange);
- _input.closeConnection();
- _cursorTimer.stop();
- }
-
- // Triggered for both text and selection changes.
- void _handleLocalValueChange() {
- if (widget.mode.canEdit &&
- widget.controller.lastChangeSource == ChangeSource.local) {
- // Only request keyboard for user actions.
- requestKeyboard();
- }
- _input.updateRemoteValue(widget.controller.plainTextEditingValue);
- _cursorTimer.startOrStop(_focusNode, selection);
- setState(() {
- // nothing to update internally.
- });
- }
-
- void _handleFocusChange() {
- _input.openOrCloseConnection(_focusNode,
- widget.controller.plainTextEditingValue, widget.keyboardAppearance, show: !_focusNode.consumeKeyboardToken());
- _cursorTimer.startOrStop(_focusNode, selection);
- updateKeepAlive();
- }
-
- void _handleRemoteValueChange(
- int start, String deleted, String inserted, TextSelection selection) {
- widget.controller
- .replaceText(start, deleted.length, inserted, selection: selection);
- }
-
- void _handleRenderContextChange() {
- setState(() {
- // nothing to update internally.
- });
- }
- }
|