123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- // 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 'dart:async';
- import 'dart:ui' as ui;
-
- import 'package:flutter/material.dart';
- import 'package:notus/notus.dart';
-
- import 'iconfont.dart';
- import 'buttons.dart';
- import 'scope.dart';
- import 'theme.dart';
-
- /// List of all button actions supported by [ZefyrToolbar] buttons.
- enum ZefyrToolbarAction {
- bold,
- italic,
- underline,
- deleteline,
- link,
- unlink,
- emoji,
- clipboardCopy,
- openInBrowser,
- heading,
- text,
- headingLevel1,
- headingLevel2,
- headingLevel3,
- headingLevel4,
- headingLevel5,
- headingLevel6,
- bulletList,
- numberList,
- code,
- quote,
- horizontalRule,
- image,
- cameraImage,
- galleryImage,
- hideKeyboard,
- close,
- confirm,
- }
-
- final kZefyrToolbarAttributeActions = <ZefyrToolbarAction, NotusAttributeKey>{
- ZefyrToolbarAction.bold: NotusAttribute.bold,
- ZefyrToolbarAction.italic: NotusAttribute.italic,
- ZefyrToolbarAction.link: NotusAttribute.link,
- ZefyrToolbarAction.heading: NotusAttribute.heading,
- ZefyrToolbarAction.headingLevel1: NotusAttribute.heading.level1,
- ZefyrToolbarAction.headingLevel2: NotusAttribute.heading.level2,
- ZefyrToolbarAction.headingLevel3: NotusAttribute.heading.level3,
- ZefyrToolbarAction.bulletList: NotusAttribute.block.bulletList,
- ZefyrToolbarAction.numberList: NotusAttribute.block.numberList,
- ZefyrToolbarAction.code: NotusAttribute.block.code,
- ZefyrToolbarAction.quote: NotusAttribute.block.quote,
- ZefyrToolbarAction.horizontalRule: NotusAttribute.embed.horizontalRule,
- };
-
- const kDefaultButtonIcons = {
- ZefyrToolbarAction.bold: IconFont.note_editor_text_bold,
- ZefyrToolbarAction.italic: IconFont.note_editor_text_in,
- ZefyrToolbarAction.underline: IconFont.note_editor_text_underline,
- ZefyrToolbarAction.deleteline: IconFont.note_editor_text_deleteline,
- ZefyrToolbarAction.quote: IconFont.note_editor_text_quote,
- ZefyrToolbarAction.numberList: IconFont.note_editor_text_ol,
- ZefyrToolbarAction.bulletList: IconFont.note_editor_text_ul,
- ZefyrToolbarAction.link: IconFont.note_editor_link,
- ZefyrToolbarAction.unlink: Icons.link_off,
- ZefyrToolbarAction.emoji: IconFont.im_dialogebar_sendemoji,
- ZefyrToolbarAction.clipboardCopy: Icons.content_copy,
- ZefyrToolbarAction.openInBrowser: Icons.open_in_new,
- ZefyrToolbarAction.heading: IconFont.note_editor_text,
- ZefyrToolbarAction.headingLevel1: IconFont.note_editor_text_h,
- ZefyrToolbarAction.headingLevel2: IconFont.note_editor_text_h1,
- ZefyrToolbarAction.headingLevel3: IconFont.note_editor_text_h2,
- ZefyrToolbarAction.headingLevel4: IconFont.note_editor_text_h3,
- ZefyrToolbarAction.headingLevel5: IconFont.note_editor_text_h4,
- ZefyrToolbarAction.headingLevel6: IconFont.note_editor_text_h5,
- ZefyrToolbarAction.text: IconFont.note_editor_text,
- ZefyrToolbarAction.code: IconFont.note_editor_text_code,
- ZefyrToolbarAction.horizontalRule: IconFont.note_editor_text_hr,
- ZefyrToolbarAction.image: IconFont.note_editor_image,
- ZefyrToolbarAction.cameraImage: IconFont.im_dialoge_more_takephoto,
- ZefyrToolbarAction.galleryImage: IconFont.im_dialoge_more_album,
- ZefyrToolbarAction.hideKeyboard: IconFont.note_editor_keyboard,
- ZefyrToolbarAction.close: Icons.close,
- ZefyrToolbarAction.confirm: Icons.check,
- };
-
- const kSpecialIconSizes = {
- ZefyrToolbarAction.unlink: 20.0,
- ZefyrToolbarAction.clipboardCopy: 20.0,
- ZefyrToolbarAction.openInBrowser: 20.0,
- ZefyrToolbarAction.close: 20.0,
- ZefyrToolbarAction.confirm: 20.0,
- };
-
- const kDefaultButtonTexts = {};
-
- /// Allows customizing appearance of [ZefyrToolbar].
- abstract class ZefyrToolbarDelegate {
- /// Builds toolbar button for specified [action].
- ///
- /// Returned widget is usually an instance of [ZefyrButton].
- Widget buildButton(BuildContext context, ZefyrToolbarAction action,
- {VoidCallback onPressed});
- }
-
- /// Scaffold for [ZefyrToolbar].
- class ZefyrToolbarScaffold extends StatelessWidget {
- const ZefyrToolbarScaffold({
- Key key,
- @required this.body,
- this.trailing,
- this.autoImplyTrailing = true,
- }) : super(key: key);
-
- final Widget body;
- final Widget trailing;
- final bool autoImplyTrailing;
-
- @override
- Widget build(BuildContext context) {
- final theme = ZefyrTheme.of(context).toolbarTheme;
- final toolbar = ZefyrToolbar.of(context);
- final constraints =
- BoxConstraints.tightFor(height: ZefyrToolbar.kToolbarHeight);
- final children = <Widget>[
- if (trailing != null)
- trailing
- else if (autoImplyTrailing)
- toolbar.buildButton(context, ZefyrToolbarAction.close)
- ];
-
- children.add(Expanded(child: body));
-
- return Container(
- constraints: constraints,
- decoration: BoxDecoration(
- border: Border(
- top: BorderSide(color: theme.dividerColor, width: 1)
- ),
- ),
- child: Material(color: theme.color, child: Row(children: children)),
- );
- }
- }
-
- /// Toolbar for [ZefyrEditor].
- class ZefyrToolbar extends StatefulWidget implements PreferredSizeWidget {
- static const kToolbarHeight = 50.0;
-
- const ZefyrToolbar({
- Key key,
- @required this.editor,
- this.autoHide = true,
- this.delegate,
- }) : super(key: key);
-
- final ZefyrToolbarDelegate delegate;
- final ZefyrScope editor;
-
- /// Whether to automatically hide this toolbar when editor loses focus.
- final bool autoHide;
-
- static ZefyrToolbarState of(BuildContext context) {
- final _ZefyrToolbarScope scope =
- context.dependOnInheritedWidgetOfExactType<_ZefyrToolbarScope>();
- return scope?.toolbar;
- }
-
- @override
- ZefyrToolbarState createState() => ZefyrToolbarState();
-
- @override
- ui.Size get preferredSize => Size.fromHeight(ZefyrToolbar.kToolbarHeight);
- }
-
- class _ZefyrToolbarScope extends InheritedWidget {
- _ZefyrToolbarScope({Key key, @required Widget child, @required this.toolbar})
- : super(key: key, child: child);
-
- final ZefyrToolbarState toolbar;
-
- @override
- bool updateShouldNotify(_ZefyrToolbarScope oldWidget) {
- return toolbar != oldWidget.toolbar;
- }
- }
-
- class ZefyrToolbarState extends State<ZefyrToolbar>
- with SingleTickerProviderStateMixin {
- final Key _toolbarKey = UniqueKey();
- final Key _overlayKey = UniqueKey();
-
- ZefyrToolbarDelegate _delegate;
- AnimationController _overlayAnimation;
- WidgetBuilder _overlayBuilder;
- Completer<void> _overlayCompleter;
-
- TextSelection _selection;
-
- void markNeedsRebuild() {
- setState(() {
- if (_selection != editor.selection) {
- _selection = editor.selection;
- // closeOverlay();
- }
- });
- }
-
- Widget buildButton(BuildContext context, ZefyrToolbarAction action,
- {VoidCallback onPressed}) {
- return _delegate.buildButton(context, action, onPressed: onPressed);
- }
-
- Future<void> showOverlay(WidgetBuilder builder) async {
- if (hasOverlay) {
- closeOverlay();
- }
- final completer = Completer<void>();
- setState(() {
- _overlayBuilder = builder;
- _overlayCompleter = completer;
- _overlayAnimation.forward();
- });
- return completer.future;
- }
-
- void closeOverlay() {
- if (!hasOverlay) return;
- _overlayAnimation.reverse().whenComplete(() {
- setState(() {
- _overlayBuilder = null;
- _overlayCompleter?.complete();
- _overlayCompleter = null;
- });
- });
- }
-
- bool get hasOverlay => _overlayBuilder != null;
-
- ZefyrScope get editor => widget.editor;
-
- @override
- void initState() {
- super.initState();
- _delegate = widget.delegate ?? _DefaultZefyrToolbarDelegate();
- _overlayAnimation =
- AnimationController(vsync: this, duration: Duration(milliseconds: 100));
- _selection = editor.selection;
- }
-
- @override
- void didUpdateWidget(ZefyrToolbar oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (widget.delegate != oldWidget.delegate) {
- _delegate = widget.delegate ?? _DefaultZefyrToolbarDelegate();
- }
- }
-
- @override
- void dispose() {
- _overlayAnimation.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- final layers = <Widget>[];
-
- // Must set unique key for the toolbar to prevent it from reconstructing
- // new state each time we toggle overlay.
-
- final toolbar = ZefyrToolbarScaffold(
- key: _toolbarKey,
- body: ZefyrButtonList(buttons: _buildButtons(context)),
- trailing: buildButton(context, ZefyrToolbarAction.hideKeyboard),
- );
-
- layers.add(toolbar);
-
- if (hasOverlay) {
- Widget widget = Builder(builder: _overlayBuilder);
- assert(widget != null);
- final overlay = FadeTransition(
- key: _overlayKey,
- opacity: _overlayAnimation,
- child: widget,
- );
- layers.add(overlay);
- }
- return _ZefyrToolbarScope(
- toolbar: this,
- child: Column(
- children: layers
- ),
- );
- }
-
- List<Widget> _buildButtons(BuildContext context) {
- final buttons = <Widget>[
- HeadingButton(),
- buildButton(context, ZefyrToolbarAction.emoji),
- if (editor.imageDelegate != null) ImageButton(),
- LinkButton(),
-
- // buildButton(context, ZefyrToolbarAction.bulletList),
- // buildButton(context, ZefyrToolbarAction.numberList),
- // buildButton(context, ZefyrToolbarAction.quote),
- // buildButton(context, ZefyrToolbarAction.code),
- // buildButton(context, ZefyrToolbarAction.horizontalRule),
- ];
- return buttons;
- }
- }
-
- /// Scrollable list of toolbar buttons.
- class ZefyrButtonList extends StatefulWidget {
- const ZefyrButtonList({Key key, @required this.buttons}) : super(key: key);
- final List<Widget> buttons;
-
- @override
- _ZefyrButtonListState createState() => _ZefyrButtonListState();
- }
-
- class _ZefyrButtonListState extends State<ZefyrButtonList> {
- final ScrollController _controller = ScrollController();
- bool _showLeftArrow = false;
- bool _showRightArrow = false;
-
- @override
- void initState() {
- super.initState();
- _controller.addListener(_handleScroll);
- // Workaround to allow scroll controller attach to our ListView so that
- // we can detect if overflow arrows need to be shown on init.
- // TODO: find a better way to detect overflow
- Timer.run(_handleScroll);
- }
-
- @override
- Widget build(BuildContext context) {
- final theme = ZefyrTheme.of(context).toolbarTheme;
- final color = theme.iconColor;
- final list = ListView(
- scrollDirection: Axis.horizontal,
- controller: _controller,
- children: widget.buttons,
- physics: ClampingScrollPhysics(),
- );
-
- final leftArrow = _showLeftArrow
- ? Icon(Icons.arrow_left, size: 18.0, color: color)
- : null;
- final rightArrow = _showRightArrow
- ? Icon(Icons.arrow_right, size: 18.0, color: color)
- : null;
- return ClipRect(child: list);
-
- // Row(
- // children: <Widget>[
- // SizedBox(
- // width: 12.0,
- // height: ZefyrToolbar.kToolbarHeight,
- // child: Container(child: leftArrow, color: theme.color),
- // ),
- // Expanded(child: ClipRect(child: list)),
- // SizedBox(
- // width: 12.0,
- // height: ZefyrToolbar.kToolbarHeight,
- // child: Container(child: rightArrow, color: theme.color),
- // ),
- // ],
- // );
- }
-
- void _handleScroll() {
- setState(() {
- _showLeftArrow =
- _controller.position.minScrollExtent != _controller.position.pixels;
- _showRightArrow =
- _controller.position.maxScrollExtent != _controller.position.pixels;
- });
- }
- }
-
- class _DefaultZefyrToolbarDelegate implements ZefyrToolbarDelegate {
-
-
- @override
- Widget buildButton(BuildContext context, ZefyrToolbarAction action,
- {VoidCallback onPressed}) {
- final theme = Theme.of(context);
- if (kDefaultButtonIcons.containsKey(action)) {
- final icon = kDefaultButtonIcons[action];
- final size = kSpecialIconSizes[action];
- return ZefyrButton.icon(
- action: action,
- icon: icon,
- iconSize: size,
- onPressed: onPressed,
- );
- } else {
- final text = kDefaultButtonTexts[action];
- assert(text != null);
- final style = theme.textTheme.caption.copyWith(fontSize: 14.0);
- return ZefyrButton.text(
- action: action,
- text: text,
- style: style,
- onPressed: onPressed,
- );
- }
- }
- }
|