[flutter]: flowy toolbar

This commit is contained in:
appflowy 2021-10-22 23:49:56 +08:00
parent 8e5cbfb84e
commit e4a3355e75
14 changed files with 680 additions and 72 deletions

View File

Before

Width:  |  Height:  |  Size: 512 B

After

Width:  |  Height:  |  Size: 512 B

View File

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 286 B

View File

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

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -35,16 +35,15 @@ export 'toolbar/select_header_style_button.dart';
export 'toolbar/toggle_check_list_button.dart';
export 'toolbar/toggle_style_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 OnVideoPickCallback = Future<String?> Function(File file);
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.
const double kDefaultIconSize = 18;
@ -218,8 +217,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
webVideoPickImpl: webImagePickImpl,
mediaPickSettingSelector: mediaPickSettingSelector,
),
if ((onImagePickCallback != null || onVideoPickCallback != null) &&
showCameraButton)
if ((onImagePickCallback != null || onVideoPickCallback != null) && showCameraButton)
CameraButton(
icon: Icons.photo_camera,
iconSize: toolbarIconSize,
@ -246,10 +244,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
iconSize: toolbarIconSize,
),
if (isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
(isButtonGroupShown[2] || isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
@ -260,10 +255,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
controller: controller,
iconSize: toolbarIconSize,
),
if (isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
if (isButtonGroupShown[2] && (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,
@ -297,8 +289,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
icon: Icons.code,
iconSize: toolbarIconSize,
),
if (isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5]))
if (isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5]))
VerticalDivider(
indent: 12,
endIndent: 12,

View File

@ -7,18 +7,15 @@ import 'package:flutter/material.dart';
/// The arrow indicators are automatically hidden if the list is not
/// scrollable in the direction of the respective arrow.
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;
@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();
bool _showLeftArrow = false;
bool _showRightArrow = false;
@ -63,10 +60,8 @@ class _ArrowIndicatedButtonListState extends State<ArrowIndicatedButtonList>
if (!mounted) return;
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;
});
}

View File

@ -17,8 +17,7 @@ class SelectHeaderStyleButton extends StatefulWidget {
final double iconSize;
@override
_SelectHeaderStyleButtonState createState() =>
_SelectHeaderStyleButtonState();
_SelectHeaderStyleButtonState createState() => _SelectHeaderStyleButtonState();
}
class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
@ -30,8 +29,7 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
void initState() {
super.initState();
setState(() {
_value =
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
});
widget.controller.addListener(_didChangeEditingValue);
}
@ -45,12 +43,7 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
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 theme = Theme.of(context);
@ -74,13 +67,9 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
highlightElevation: 0,
elevation: 0,
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(
_valueString[index],
style: style.copyWith(
@ -98,8 +87,7 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
void _didChangeEditingValue() {
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) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
_value =
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
}
}

View File

@ -99,9 +99,7 @@ class _ToggleStyleButtonState extends State<ToggleStyleButton> {
}
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.iconTheme.color
: theme.disabledColor;
final fill = isToggled == true
? theme.toggleableActiveColor
: fillColor ?? theme.canvasColor;
final fill = isToggled == true ? theme.toggleableActiveColor : fillColor ?? theme.canvasColor;
return QuillIconButton(
highlightElevation: 0,
hoverElevation: 0,

View File

@ -117,7 +117,11 @@ class AppTheme {
ThemeData get themeData {
var t = ThemeData(
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(
brightness: isDark ? Brightness.dark : Brightness.light,
primary: main1,
@ -141,6 +145,5 @@ class AppTheme {
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));
}

View File

@ -6,26 +6,41 @@ class FlowyIconButton extends StatelessWidget {
final double? height;
final Widget icon;
final VoidCallback? onPressed;
final Color? highlightColor;
final Color? hoverColor;
final EdgeInsets iconPadding;
const FlowyIconButton({
Key? key,
this.height,
this.onPressed,
this.width = 30,
this.highlightColor = Colors.transparent,
this.hoverColor = Colors.transparent,
this.iconPadding = const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
required this.icon,
}) : super(key: key);
@override
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,
child: Padding(
padding: iconPadding,
child: SizedBox.fromSize(child: icon, size: Size(width, width)),
),
),
);
}