Browse Source

[flutter]: flowy toolbar

appflowy 3 years ago
parent
commit
e4a3355e75

+ 0 - 0
app_flowy/assets/images/editor/multi_select.svg → app_flowy/assets/images/editor/bullet_list.svg


+ 0 - 0
app_flowy/assets/images/editor/embed_link.svg → app_flowy/assets/images/editor/inline_block.svg


+ 4 - 6
app_flowy/lib/workspace/presentation/stack_page/doc/doc_page.dart

@@ -9,6 +9,8 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'package:styled_widget/styled_widget.dart';
 
 
+import 'widget/toolbar/tool_bar.dart';
+
 class DocPage extends StatefulWidget {
 class DocPage extends StatefulWidget {
   final View view;
   final View view;
 
 
@@ -63,7 +65,7 @@ class _DocPageState extends State<DocPage> {
         _renderEditor(controller),
         _renderEditor(controller),
         _renderToolbar(controller),
         _renderToolbar(controller),
       ],
       ],
-    ).padding(horizontal: 80, vertical: 48);
+    ).padding(horizontal: 80, top: 48);
   }
   }
 
 
   Widget _renderEditor(QuillController controller) {
   Widget _renderEditor(QuillController controller) {
@@ -84,14 +86,10 @@ class _DocPageState extends State<DocPage> {
   }
   }
 
 
   Widget _renderToolbar(QuillController controller) {
   Widget _renderToolbar(QuillController controller) {
-    return QuillToolbar.basic(
+    return EditorToolbar.basic(
       controller: controller,
       controller: controller,
     );
     );
   }
   }
-
-  Future<String> _onImageSelection(File file) {
-    throw UnimplementedError();
-  }
 }
 }
 
 
 // import 'package:flowy_editor/flowy_editor.dart';
 // import 'package:flowy_editor/flowy_editor.dart';

+ 93 - 0
app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/check_button.dart

@@ -0,0 +1,93 @@
+import 'package:editor/flutter_quill.dart';
+import 'package:editor/models/documents/style.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class FlowyCheckListButton extends StatefulWidget {
+  const FlowyCheckListButton({
+    required this.controller,
+    required this.attribute,
+    this.iconSize = kDefaultIconSize,
+    this.fillColor,
+    this.childBuilder = defaultToggleStyleButtonBuilder,
+    Key? key,
+  }) : super(key: key);
+
+  final double iconSize;
+
+  final Color? fillColor;
+
+  final QuillController controller;
+
+  final ToggleStyleButtonBuilder childBuilder;
+
+  final Attribute attribute;
+
+  @override
+  _FlowyCheckListButtonState createState() => _FlowyCheckListButtonState();
+}
+
+class _FlowyCheckListButtonState extends State<FlowyCheckListButton> {
+  bool? _isToggled;
+
+  Style get _selectionStyle => widget.controller.getSelectionStyle();
+
+  void _didChangeEditingValue() {
+    setState(() {
+      _isToggled = _getIsToggled(widget.controller.getSelectionStyle().attributes);
+    });
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _isToggled = _getIsToggled(_selectionStyle.attributes);
+    widget.controller.addListener(_didChangeEditingValue);
+  }
+
+  bool _getIsToggled(Map<String, Attribute> attrs) {
+    if (widget.attribute.key == Attribute.list.key) {
+      final attribute = attrs[widget.attribute.key];
+      if (attribute == null) {
+        return false;
+      }
+      return attribute.value == widget.attribute.value || attribute.value == Attribute.checked.value;
+    }
+    return attrs.containsKey(widget.attribute.key);
+  }
+
+  @override
+  void didUpdateWidget(covariant FlowyCheckListButton oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.controller != widget.controller) {
+      oldWidget.controller.removeListener(_didChangeEditingValue);
+      widget.controller.addListener(_didChangeEditingValue);
+      _isToggled = _getIsToggled(_selectionStyle.attributes);
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.controller.removeListener(_didChangeEditingValue);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return FlowyIconButton(
+      onPressed: _toggleAttribute,
+      width: widget.iconSize * kIconButtonFactor,
+      icon: svg('editor/checkbox'),
+      highlightColor: _isToggled == true ? theme.shader5 : theme.shader6,
+      hoverColor: theme.shader5,
+    );
+  }
+
+  void _toggleAttribute() {
+    widget.controller.formatSelection(_isToggled! ? Attribute.clone(Attribute.unchecked, null) : Attribute.unchecked);
+  }
+}

+ 85 - 0
app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/image_button.dart

@@ -0,0 +1,85 @@
+import 'package:editor/flutter_quill.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flutter/material.dart';
+import 'package:image_picker/image_picker.dart';
+
+class FlowyImageButton extends StatelessWidget {
+  const FlowyImageButton({
+    required this.controller,
+    this.iconSize = kDefaultIconSize,
+    this.onImagePickCallback,
+    this.fillColor,
+    this.filePickImpl,
+    this.webImagePickImpl,
+    this.mediaPickSettingSelector,
+    Key? key,
+  }) : super(key: key);
+
+  final double iconSize;
+
+  final Color? fillColor;
+
+  final QuillController controller;
+
+  final OnImagePickCallback? onImagePickCallback;
+
+  final WebImagePickImpl? webImagePickImpl;
+
+  final FilePickImpl? filePickImpl;
+
+  final MediaPickSettingSelector? mediaPickSettingSelector;
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = Theme.of(context);
+
+    return FlowyIconButton(
+      icon: svg('editor/image'),
+      width: iconSize * 1.77,
+      highlightColor: theme.canvasColor,
+      onPressed: () => _onPressedHandler(context),
+    );
+  }
+
+  Future<void> _onPressedHandler(BuildContext context) async {
+    if (onImagePickCallback != null) {
+      final selector = mediaPickSettingSelector ?? ImageVideoUtils.selectMediaPickSetting;
+      final source = await selector(context);
+      if (source != null) {
+        if (source == MediaPickSetting.Gallery) {
+          _pickImage(context);
+        } else {
+          _typeLink(context);
+        }
+      }
+    } else {
+      _typeLink(context);
+    }
+  }
+
+  void _pickImage(BuildContext context) => ImageVideoUtils.handleImageButtonTap(
+        context,
+        controller,
+        ImageSource.gallery,
+        onImagePickCallback!,
+        filePickImpl: filePickImpl,
+        webImagePickImpl: webImagePickImpl,
+      );
+
+  void _typeLink(BuildContext context) {
+    // showDialog<String>(
+    //   context: context,
+    //   builder: (_) => const LinkDialog(),
+    // ).then(_linkSubmitted);
+  }
+
+  void _linkSubmitted(String? value) {
+    if (value != null && value.isNotEmpty) {
+      final index = controller.selection.baseOffset;
+      final length = controller.selection.extentOffset - index;
+
+      controller.replaceText(index, length, BlockEmbed.image(value), null);
+    }
+  }
+}

+ 79 - 0
app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/link_button.dart

@@ -0,0 +1,79 @@
+import 'package:editor/flutter_quill.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class FlowyLinkStyleButton extends StatefulWidget {
+  const FlowyLinkStyleButton({
+    required this.controller,
+    this.iconSize = kDefaultIconSize,
+    Key? key,
+  }) : super(key: key);
+
+  final QuillController controller;
+  final double iconSize;
+
+  @override
+  _FlowyLinkStyleButtonState createState() => _FlowyLinkStyleButtonState();
+}
+
+class _FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
+  void _didChangeSelection() {
+    setState(() {});
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    widget.controller.addListener(_didChangeSelection);
+  }
+
+  @override
+  void didUpdateWidget(covariant FlowyLinkStyleButton oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.controller != widget.controller) {
+      oldWidget.controller.removeListener(_didChangeSelection);
+      widget.controller.addListener(_didChangeSelection);
+    }
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    widget.controller.removeListener(_didChangeSelection);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final isEnabled = !widget.controller.selection.isCollapsed;
+    final pressedHandler = isEnabled ? () => _openLinkDialog(context) : null;
+
+    final theme = context.watch<AppTheme>();
+
+    return FlowyIconButton(
+      onPressed: pressedHandler,
+      icon: svg('editor/share'),
+      highlightColor: isEnabled == true ? theme.shader5 : theme.shader6,
+      hoverColor: theme.shader5,
+      width: widget.iconSize * kIconButtonFactor,
+    );
+  }
+
+  void _openLinkDialog(BuildContext context) {
+    // showDialog<String>(
+    //   context: context,
+    //   builder: (ctx) {
+    //     return const LinkDialog();
+    //   },
+    // ).then(_linkSubmitted);
+  }
+
+  void _linkSubmitted(String? value) {
+    if (value == null || value.isEmpty) {
+      return;
+    }
+    widget.controller.formatSelection(LinkAttribute(value));
+  }
+}

+ 84 - 0
app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/toggle_button.dart

@@ -0,0 +1,84 @@
+import 'package:editor/flutter_quill.dart';
+import 'package:editor/models/documents/style.dart';
+
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class FlowyToggleStyleButton extends StatefulWidget {
+  final Attribute attribute;
+  final Widget icon;
+  final double iconSize;
+  final QuillController controller;
+
+  const FlowyToggleStyleButton({
+    required this.attribute,
+    required this.icon,
+    required this.controller,
+    this.iconSize = kDefaultIconSize,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  _ToggleStyleButtonState createState() => _ToggleStyleButtonState();
+}
+
+class _ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
+  bool? _isToggled;
+  Style get _selectionStyle => widget.controller.getSelectionStyle();
+  @override
+  void initState() {
+    super.initState();
+    _isToggled = _getIsToggled(_selectionStyle.attributes);
+    widget.controller.addListener(_didChangeEditingValue);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+
+    return FlowyIconButton(
+      onPressed: _toggleAttribute,
+      width: widget.iconSize * kIconButtonFactor,
+      icon: widget.icon,
+      highlightColor: _isToggled == true ? theme.shader5 : theme.shader6,
+      hoverColor: theme.shader5,
+    );
+  }
+
+  @override
+  void didUpdateWidget(covariant FlowyToggleStyleButton oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.controller != widget.controller) {
+      oldWidget.controller.removeListener(_didChangeEditingValue);
+      widget.controller.addListener(_didChangeEditingValue);
+      _isToggled = _getIsToggled(_selectionStyle.attributes);
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.controller.removeListener(_didChangeEditingValue);
+    super.dispose();
+  }
+
+  void _didChangeEditingValue() {
+    setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes));
+  }
+
+  bool _getIsToggled(Map<String, Attribute> attrs) {
+    if (widget.attribute.key == Attribute.list.key) {
+      final attribute = attrs[widget.attribute.key];
+      if (attribute == null) {
+        return false;
+      }
+      return attribute.value == widget.attribute.value;
+    }
+    return attrs.containsKey(widget.attribute.key);
+  }
+
+  void _toggleAttribute() {
+    widget.controller.formatSelection(_isToggled! ? Attribute.clone(widget.attribute, null) : widget.attribute);
+  }
+}

+ 282 - 0
app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/tool_bar.dart

@@ -0,0 +1,282 @@
+import 'dart:async';
+import 'dart:math';
+
+import 'package:editor/flutter_quill.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flutter/material.dart';
+import 'check_button.dart';
+import 'image_button.dart';
+import 'link_button.dart';
+import 'toggle_button.dart';
+
+class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
+  final List<Widget> children;
+  final double toolBarHeight;
+  final Color? color;
+
+  const EditorToolbar({
+    required this.children,
+    this.toolBarHeight = 36,
+    this.color,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      color: Theme.of(context).canvasColor,
+      constraints: BoxConstraints.tightFor(height: preferredSize.height),
+      child: ToolbarButtonList(buttons: children),
+    );
+  }
+
+  @override
+  Size get preferredSize => Size.fromHeight(toolBarHeight);
+
+  factory EditorToolbar.basic({
+    required QuillController controller,
+    double toolbarIconSize = kDefaultIconSize,
+    OnImagePickCallback? onImagePickCallback,
+    OnVideoPickCallback? onVideoPickCallback,
+    MediaPickSettingSelector? mediaPickSettingSelector,
+    FilePickImpl? filePickImpl,
+    WebImagePickImpl? webImagePickImpl,
+    WebVideoPickImpl? webVideoPickImpl,
+    Key? key,
+  }) {
+    return EditorToolbar(
+      key: key,
+      toolBarHeight: toolbarIconSize * 2,
+      children: [
+        HistoryButton(
+          icon: Icons.undo_outlined,
+          iconSize: toolbarIconSize,
+          controller: controller,
+          undo: true,
+        ),
+        HistoryButton(
+          icon: Icons.redo_outlined,
+          iconSize: toolbarIconSize,
+          controller: controller,
+          undo: false,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.bold,
+          icon: svg('editor/bold'),
+          iconSize: toolbarIconSize,
+          controller: controller,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.italic,
+          icon: svg("editor/restore"),
+          iconSize: toolbarIconSize,
+          controller: controller,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.underline,
+          icon: svg('editor/underline'),
+          iconSize: toolbarIconSize,
+          controller: controller,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.strikeThrough,
+          icon: svg('editor/strikethrough'),
+          iconSize: toolbarIconSize,
+          controller: controller,
+        ),
+        ColorButton(
+          icon: Icons.format_color_fill,
+          iconSize: toolbarIconSize,
+          controller: controller,
+          background: true,
+        ),
+        FlowyImageButton(
+          iconSize: toolbarIconSize,
+          controller: controller,
+          onImagePickCallback: onImagePickCallback,
+          filePickImpl: filePickImpl,
+          webImagePickImpl: webImagePickImpl,
+          mediaPickSettingSelector: mediaPickSettingSelector,
+        ),
+        SelectHeaderStyleButton(
+          controller: controller,
+          iconSize: toolbarIconSize,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.ol,
+          controller: controller,
+          icon: svg('editor/numbers'),
+          iconSize: toolbarIconSize,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.ul,
+          controller: controller,
+          icon: svg('editor/bullet_list'),
+          iconSize: toolbarIconSize,
+        ),
+        FlowyCheckListButton(
+          attribute: Attribute.unchecked,
+          controller: controller,
+          iconSize: toolbarIconSize,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.inlineCode,
+          controller: controller,
+          icon: svg('editor/inline_block'),
+          iconSize: toolbarIconSize,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.blockQuote,
+          controller: controller,
+          icon: svg('editor/quote'),
+          iconSize: toolbarIconSize,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.blockQuote,
+          controller: controller,
+          icon: svg('editor/quote'),
+          iconSize: toolbarIconSize,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.blockQuote,
+          controller: controller,
+          icon: svg('editor/quote'),
+          iconSize: toolbarIconSize,
+        ),
+        FlowyToggleStyleButton(
+          attribute: Attribute.blockQuote,
+          controller: controller,
+          icon: svg('editor/quote'),
+          iconSize: toolbarIconSize,
+        ),
+        FlowyLinkStyleButton(
+          controller: controller,
+          iconSize: toolbarIconSize,
+        ),
+      ],
+    );
+  }
+}
+
+class ToolbarButtonList extends StatefulWidget {
+  const ToolbarButtonList({required this.buttons, Key? key}) : super(key: key);
+
+  final List<Widget> buttons;
+
+  @override
+  _ToolbarButtonListState createState() => _ToolbarButtonListState();
+}
+
+class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindingObserver {
+  final ScrollController _controller = ScrollController();
+  bool _showLeftArrow = false;
+  bool _showRightArrow = false;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller.addListener(_handleScroll);
+
+    // Listening to the WidgetsBinding instance is necessary so that we can
+    // hide the arrows when the window gets a new size and thus the toolbar
+    // becomes scrollable/unscrollable.
+    WidgetsBinding.instance!.addObserver(this);
+
+    // Workaround to allow the scroll controller attach to our ListView so that
+    // we can detect if overflow arrows need to be shown on init.
+    Timer.run(_handleScroll);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        return SizedBox(
+          width: min(constraints.maxWidth, (widget.buttons.length + 3) * kDefaultIconSize * kIconButtonFactor + 16),
+          child: Row(
+            children: <Widget>[
+              _buildLeftArrow(),
+              _buildScrollableList(constraints),
+              _buildRightColor(),
+            ],
+          ),
+        );
+      },
+    );
+  }
+
+  @override
+  void didChangeMetrics() => _handleScroll();
+
+  @override
+  void dispose() {
+    _controller.dispose();
+    WidgetsBinding.instance!.removeObserver(this);
+    super.dispose();
+  }
+
+  void _handleScroll() {
+    if (!mounted) return;
+    setState(() {
+      _showLeftArrow = _controller.position.minScrollExtent != _controller.position.pixels;
+      _showRightArrow = _controller.position.maxScrollExtent != _controller.position.pixels;
+    });
+  }
+
+  Widget _buildLeftArrow() {
+    return SizedBox(
+      width: 8,
+      child: Transform.translate(
+        // Move the icon a few pixels to center it
+        offset: const Offset(-5, 0),
+        child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null,
+      ),
+    );
+  }
+
+  Widget _buildScrollableList(BoxConstraints constraints) {
+    return ScrollConfiguration(
+      // Remove the glowing effect, as we already have the arrow indicators
+      behavior: _NoGlowBehavior(),
+      // The CustomScrollView is necessary so that the children are not
+      // stretched to the height of the toolbar, https://bit.ly/3uC3bjI
+      child: Expanded(
+        child: CustomScrollView(
+          scrollDirection: Axis.horizontal,
+          controller: _controller,
+          physics: const ClampingScrollPhysics(),
+          slivers: [
+            SliverList(
+              delegate: SliverChildBuilderDelegate(
+                (BuildContext context, int index) {
+                  return widget.buttons[index];
+                },
+                childCount: widget.buttons.length,
+                addAutomaticKeepAlives: false,
+              ),
+            )
+          ],
+        ),
+      ),
+    );
+  }
+
+  Widget _buildRightColor() {
+    return SizedBox(
+      width: 8,
+      child: Transform.translate(
+        // Move the icon a few pixels to center it
+        offset: const Offset(-5, 0),
+        child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null,
+      ),
+    );
+  }
+}
+
+/// ScrollBehavior without the Material glow effect.
+class _NoGlowBehavior extends ScrollBehavior {
+  @override
+  Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) {
+    return child;
+  }
+}

+ 9 - 18
app_flowy/packages/editor/lib/src/widgets/toolbar.dart

@@ -35,16 +35,15 @@ export 'toolbar/select_header_style_button.dart';
 export 'toolbar/toggle_check_list_button.dart';
 export 'toolbar/toggle_check_list_button.dart';
 export 'toolbar/toggle_style_button.dart';
 export 'toolbar/toggle_style_button.dart';
 export 'toolbar/video_button.dart';
 export 'toolbar/video_button.dart';
+export 'toolbar/image_video_utils.dart';
+export 'toolbar/arrow_indicated_button_list.dart';
 
 
 typedef OnImagePickCallback = Future<String?> Function(File file);
 typedef OnImagePickCallback = Future<String?> Function(File file);
 typedef OnVideoPickCallback = Future<String?> Function(File file);
 typedef OnVideoPickCallback = Future<String?> Function(File file);
 typedef FilePickImpl = Future<String?> Function(BuildContext context);
 typedef FilePickImpl = Future<String?> Function(BuildContext context);
-typedef WebImagePickImpl = Future<String?> Function(
-    OnImagePickCallback onImagePickCallback);
-typedef WebVideoPickImpl = Future<String?> Function(
-    OnVideoPickCallback onImagePickCallback);
-typedef MediaPickSettingSelector = Future<MediaPickSetting?> Function(
-    BuildContext context);
+typedef WebImagePickImpl = Future<String?> Function(OnImagePickCallback onImagePickCallback);
+typedef WebVideoPickImpl = Future<String?> Function(OnVideoPickCallback onImagePickCallback);
+typedef MediaPickSettingSelector = Future<MediaPickSetting?> Function(BuildContext context);
 
 
 // The default size of the icon of a button.
 // The default size of the icon of a button.
 const double kDefaultIconSize = 18;
 const double kDefaultIconSize = 18;
@@ -218,8 +217,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
             webVideoPickImpl: webImagePickImpl,
             webVideoPickImpl: webImagePickImpl,
             mediaPickSettingSelector: mediaPickSettingSelector,
             mediaPickSettingSelector: mediaPickSettingSelector,
           ),
           ),
-        if ((onImagePickCallback != null || onVideoPickCallback != null) &&
-            showCameraButton)
+        if ((onImagePickCallback != null || onVideoPickCallback != null) && showCameraButton)
           CameraButton(
           CameraButton(
               icon: Icons.photo_camera,
               icon: Icons.photo_camera,
               iconSize: toolbarIconSize,
               iconSize: toolbarIconSize,
@@ -246,10 +244,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
             iconSize: toolbarIconSize,
             iconSize: toolbarIconSize,
           ),
           ),
         if (isButtonGroupShown[1] &&
         if (isButtonGroupShown[1] &&
-            (isButtonGroupShown[2] ||
-                isButtonGroupShown[3] ||
-                isButtonGroupShown[4] ||
-                isButtonGroupShown[5]))
+            (isButtonGroupShown[2] || isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5]))
           VerticalDivider(
           VerticalDivider(
             indent: 12,
             indent: 12,
             endIndent: 12,
             endIndent: 12,
@@ -260,10 +255,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
             controller: controller,
             controller: controller,
             iconSize: toolbarIconSize,
             iconSize: toolbarIconSize,
           ),
           ),
-        if (isButtonGroupShown[2] &&
-            (isButtonGroupShown[3] ||
-                isButtonGroupShown[4] ||
-                isButtonGroupShown[5]))
+        if (isButtonGroupShown[2] && (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5]))
           VerticalDivider(
           VerticalDivider(
             indent: 12,
             indent: 12,
             endIndent: 12,
             endIndent: 12,
@@ -297,8 +289,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
             icon: Icons.code,
             icon: Icons.code,
             iconSize: toolbarIconSize,
             iconSize: toolbarIconSize,
           ),
           ),
-        if (isButtonGroupShown[3] &&
-            (isButtonGroupShown[4] || isButtonGroupShown[5]))
+        if (isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5]))
           VerticalDivider(
           VerticalDivider(
             indent: 12,
             indent: 12,
             endIndent: 12,
             endIndent: 12,

+ 5 - 10
app_flowy/packages/editor/lib/src/widgets/toolbar/arrow_indicated_button_list.dart

@@ -7,18 +7,15 @@ import 'package:flutter/material.dart';
 /// The arrow indicators are automatically hidden if the list is not
 /// The arrow indicators are automatically hidden if the list is not
 /// scrollable in the direction of the respective arrow.
 /// scrollable in the direction of the respective arrow.
 class ArrowIndicatedButtonList extends StatefulWidget {
 class ArrowIndicatedButtonList extends StatefulWidget {
-  const ArrowIndicatedButtonList({required this.buttons, Key? key})
-      : super(key: key);
+  const ArrowIndicatedButtonList({required this.buttons, Key? key}) : super(key: key);
 
 
   final List<Widget> buttons;
   final List<Widget> buttons;
 
 
   @override
   @override
-  _ArrowIndicatedButtonListState createState() =>
-      _ArrowIndicatedButtonListState();
+  _ArrowIndicatedButtonListState createState() => _ArrowIndicatedButtonListState();
 }
 }
 
 
-class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
-    with WidgetsBindingObserver {
+class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList> with WidgetsBindingObserver {
   final ScrollController _controller = ScrollController();
   final ScrollController _controller = ScrollController();
   bool _showLeftArrow = false;
   bool _showLeftArrow = false;
   bool _showRightArrow = false;
   bool _showRightArrow = false;
@@ -63,10 +60,8 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
     if (!mounted) return;
     if (!mounted) return;
 
 
     setState(() {
     setState(() {
-      _showLeftArrow =
-          _controller.position.minScrollExtent != _controller.position.pixels;
-      _showRightArrow =
-          _controller.position.maxScrollExtent != _controller.position.pixels;
+      _showLeftArrow = _controller.position.minScrollExtent != _controller.position.pixels;
+      _showRightArrow = _controller.position.maxScrollExtent != _controller.position.pixels;
     });
     });
   }
   }
 
 

+ 8 - 21
app_flowy/packages/editor/lib/src/widgets/toolbar/select_header_style_button.dart

@@ -17,8 +17,7 @@ class SelectHeaderStyleButton extends StatefulWidget {
   final double iconSize;
   final double iconSize;
 
 
   @override
   @override
-  _SelectHeaderStyleButtonState createState() =>
-      _SelectHeaderStyleButtonState();
+  _SelectHeaderStyleButtonState createState() => _SelectHeaderStyleButtonState();
 }
 }
 
 
 class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
 class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
@@ -30,8 +29,7 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
   void initState() {
   void initState() {
     super.initState();
     super.initState();
     setState(() {
     setState(() {
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
+      _value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
     });
     });
     widget.controller.addListener(_didChangeEditingValue);
     widget.controller.addListener(_didChangeEditingValue);
   }
   }
@@ -45,12 +43,7 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
       Attribute.h3: 'H3',
       Attribute.h3: 'H3',
     };
     };
 
 
-    final _valueAttribute = <Attribute>[
-      Attribute.header,
-      Attribute.h1,
-      Attribute.h2,
-      Attribute.h3
-    ];
+    final _valueAttribute = <Attribute>[Attribute.header, Attribute.h1, Attribute.h2, Attribute.h3];
     final _valueString = <String>['N', 'H1', 'H2', 'H3'];
     final _valueString = <String>['N', 'H1', 'H2', 'H3'];
 
 
     final theme = Theme.of(context);
     final theme = Theme.of(context);
@@ -74,13 +67,9 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
               highlightElevation: 0,
               highlightElevation: 0,
               elevation: 0,
               elevation: 0,
               visualDensity: VisualDensity.compact,
               visualDensity: VisualDensity.compact,
-              shape: RoundedRectangleBorder(
-                  borderRadius: BorderRadius.circular(2)),
-              fillColor: _valueToText[_value] == _valueString[index]
-                  ? theme.toggleableActiveColor
-                  : theme.canvasColor,
-              onPressed: () =>
-                  widget.controller.formatSelection(_valueAttribute[index]),
+              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2)),
+              fillColor: _valueToText[_value] == _valueString[index] ? theme.toggleableActiveColor : theme.canvasColor,
+              onPressed: () => widget.controller.formatSelection(_valueAttribute[index]),
               child: Text(
               child: Text(
                 _valueString[index],
                 _valueString[index],
                 style: style.copyWith(
                 style: style.copyWith(
@@ -98,8 +87,7 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
 
 
   void _didChangeEditingValue() {
   void _didChangeEditingValue() {
     setState(() {
     setState(() {
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
+      _value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
     });
     });
   }
   }
 
 
@@ -109,8 +97,7 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
     if (oldWidget.controller != widget.controller) {
     if (oldWidget.controller != widget.controller) {
       oldWidget.controller.removeListener(_didChangeEditingValue);
       oldWidget.controller.removeListener(_didChangeEditingValue);
       widget.controller.addListener(_didChangeEditingValue);
       widget.controller.addListener(_didChangeEditingValue);
-      _value =
-          _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
+      _value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
     }
     }
   }
   }
 
 

+ 2 - 6
app_flowy/packages/editor/lib/src/widgets/toolbar/toggle_style_button.dart

@@ -99,9 +99,7 @@ class _ToggleStyleButtonState extends State<ToggleStyleButton> {
   }
   }
 
 
   void _toggleAttribute() {
   void _toggleAttribute() {
-    widget.controller.formatSelection(_isToggled!
-        ? Attribute.clone(widget.attribute, null)
-        : widget.attribute);
+    widget.controller.formatSelection(_isToggled! ? Attribute.clone(widget.attribute, null) : widget.attribute);
   }
   }
 }
 }
 
 
@@ -121,9 +119,7 @@ Widget defaultToggleStyleButtonBuilder(
           ? theme.primaryIconTheme.color
           ? theme.primaryIconTheme.color
           : theme.iconTheme.color
           : theme.iconTheme.color
       : theme.disabledColor;
       : theme.disabledColor;
-  final fill = isToggled == true
-      ? theme.toggleableActiveColor
-      : fillColor ?? theme.canvasColor;
+  final fill = isToggled == true ? theme.toggleableActiveColor : fillColor ?? theme.canvasColor;
   return QuillIconButton(
   return QuillIconButton(
     highlightElevation: 0,
     highlightElevation: 0,
     hoverElevation: 0,
     hoverElevation: 0,

+ 6 - 3
app_flowy/packages/flowy_infra/lib/theme.dart

@@ -117,7 +117,11 @@ class AppTheme {
   ThemeData get themeData {
   ThemeData get themeData {
     var t = ThemeData(
     var t = ThemeData(
       textTheme: (isDark ? ThemeData.dark() : ThemeData.light()).textTheme,
       textTheme: (isDark ? ThemeData.dark() : ThemeData.light()).textTheme,
-      textSelectionTheme: TextSelectionThemeData(cursorColor: main1),
+      textSelectionTheme: TextSelectionThemeData(cursorColor: main2),
+      primaryIconTheme: IconThemeData(color: hover),
+      iconTheme: IconThemeData(color: shader1),
+      canvasColor: shader6,
+      // hoverColor: hover,
       colorScheme: ColorScheme(
       colorScheme: ColorScheme(
           brightness: isDark ? Brightness.dark : Brightness.light,
           brightness: isDark ? Brightness.dark : Brightness.light,
           primary: main1,
           primary: main1,
@@ -141,6 +145,5 @@ class AppTheme {
         toggleableActiveColor: main1);
         toggleableActiveColor: main1);
   }
   }
 
 
-  Color shift(Color c, double d) =>
-      ColorUtils.shiftHsl(c, d * (isDark ? -1 : 1));
+  Color shift(Color c, double d) => ColorUtils.shiftHsl(c, d * (isDark ? -1 : 1));
 }
 }

+ 23 - 8
app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart

@@ -6,26 +6,41 @@ class FlowyIconButton extends StatelessWidget {
   final double? height;
   final double? height;
   final Widget icon;
   final Widget icon;
   final VoidCallback? onPressed;
   final VoidCallback? onPressed;
+  final Color? highlightColor;
+  final Color? hoverColor;
+  final EdgeInsets iconPadding;
 
 
   const FlowyIconButton({
   const FlowyIconButton({
     Key? key,
     Key? key,
     this.height,
     this.height,
     this.onPressed,
     this.onPressed,
     this.width = 30,
     this.width = 30,
+    this.highlightColor = Colors.transparent,
+    this.hoverColor = Colors.transparent,
+    this.iconPadding = const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
     required this.icon,
     required this.icon,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return SizedBox(
-      width: width,
-      height: height ?? width,
-      child: IconButton(
-        icon: icon,
-        padding: EdgeInsets.zero,
-        iconSize: width,
-        alignment: Alignment.center,
+    return ConstrainedBox(
+      constraints: BoxConstraints.tightFor(width: width, height: width),
+      child: RawMaterialButton(
+        visualDensity: VisualDensity.compact,
+        hoverElevation: 0,
+        highlightElevation: 0,
+        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2)),
+        fillColor: highlightColor,
+        hoverColor: hoverColor,
+        focusColor: Colors.transparent,
+        splashColor: Colors.transparent,
+        highlightColor: Colors.transparent,
+        elevation: 0,
         onPressed: onPressed,
         onPressed: onPressed,
+        child: Padding(
+          padding: iconPadding,
+          child: SizedBox.fromSize(child: icon, size: Size(width, width)),
+        ),
       ),
       ),
     );
     );
   }
   }