[flutter]: config toolbar ui

This commit is contained in:
appflowy 2021-10-23 20:32:39 +08:00
parent e4a3355e75
commit cf98c39b98
12 changed files with 246 additions and 118 deletions

View File

@ -1,10 +1,8 @@
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';
import 'toolbar_icon_button.dart';
class FlowyCheckListButton extends StatefulWidget {
const FlowyCheckListButton({
@ -77,13 +75,11 @@ class _FlowyCheckListButtonState extends State<FlowyCheckListButton> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyIconButton(
return ToolbarIconButton(
onPressed: _toggleAttribute,
width: widget.iconSize * kIconButtonFactor,
icon: svg('editor/checkbox'),
highlightColor: _isToggled == true ? theme.shader5 : theme.shader6,
hoverColor: theme.shader5,
iconName: 'editor/checkbox',
isToggled: _isToggled ?? false,
);
}

View File

@ -0,0 +1,96 @@
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/foundation.dart';
import 'package:editor/models/documents/style.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'toolbar_icon_button.dart';
class FlowyHeaderStyleButton extends StatefulWidget {
const FlowyHeaderStyleButton({
required this.controller,
this.iconSize = kDefaultIconSize,
Key? key,
}) : super(key: key);
final QuillController controller;
final double iconSize;
@override
_FlowyHeaderStyleButtonState createState() => _FlowyHeaderStyleButtonState();
}
class _FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
Attribute? _value;
Style get _selectionStyle => widget.controller.getSelectionStyle();
@override
void initState() {
super.initState();
setState(() {
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
});
widget.controller.addListener(_didChangeEditingValue);
}
@override
Widget build(BuildContext context) {
final _valueToText = <Attribute, String>{
Attribute.h1: 'H1',
Attribute.h2: 'H2',
Attribute.h3: 'H3',
};
final _valueAttribute = <Attribute>[Attribute.h1, Attribute.h2, Attribute.h3];
final _valueString = <String>['H1', 'H2', 'H3'];
final _attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(3, (index) {
// final child =
// _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1');
final _isToggled = _valueToText[_value] == _valueString[index];
return ToolbarIconButton(
onPressed: () {
if (_isToggled) {
widget.controller.formatSelection(Attribute.header);
} else {
widget.controller.formatSelection(_valueAttribute[index]);
}
},
width: widget.iconSize * kIconButtonFactor,
iconName: _attributeImageName[index],
isToggled: _isToggled,
);
}),
);
}
void _didChangeEditingValue() {
setState(() {
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
});
}
@override
void didUpdateWidget(covariant FlowyHeaderStyleButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
_value = _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
}
}
@override
void dispose() {
widget.controller.removeListener(_didChangeEditingValue);
super.dispose();
}
}

View File

@ -1,9 +1,9 @@
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';
import 'toolbar_icon_button.dart';
class FlowyImageButton extends StatelessWidget {
const FlowyImageButton({
required this.controller,
@ -32,13 +32,11 @@ class FlowyImageButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return FlowyIconButton(
icon: svg('editor/image'),
return ToolbarIconButton(
iconName: 'editor/image',
width: iconSize * 1.77,
highlightColor: theme.canvasColor,
onPressed: () => _onPressedHandler(context),
isToggled: false,
);
}

View File

@ -1,3 +1,4 @@
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:editor/flutter_quill.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -47,33 +48,39 @@ class _FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final isEnabled = !widget.controller.selection.isCollapsed;
final pressedHandler = isEnabled ? () => _openLinkDialog(context) : null;
final theme = context.watch<AppTheme>();
final icon = isEnabled ? svg('editor/share') : svg('editor/share', color: theme.shader4);
return FlowyIconButton(
onPressed: pressedHandler,
icon: svg('editor/share'),
highlightColor: isEnabled == true ? theme.shader5 : theme.shader6,
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
icon: icon,
fillColor: 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;
final style = widget.controller.getSelectionStyle();
final values = style.values.where((v) => v.key == Attribute.link.key).map((v) => v.value);
String value = "";
if (values.isNotEmpty) {
assert(values.length == 1);
value = values.first;
}
widget.controller.formatSelection(LinkAttribute(value));
TextFieldDialog(
title: 'URL',
value: value,
confirm: (newValue) {
if (newValue.isEmpty) {
return;
}
widget.controller.formatSelection(LinkAttribute(newValue));
},
).show(context);
}
}

View File

@ -1,20 +1,18 @@
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';
import 'toolbar_icon_button.dart';
class FlowyToggleStyleButton extends StatefulWidget {
final Attribute attribute;
final Widget icon;
final String normalIcon;
final double iconSize;
final QuillController controller;
const FlowyToggleStyleButton({
required this.attribute,
required this.icon,
required this.normalIcon,
required this.controller,
this.iconSize = kDefaultIconSize,
Key? key,
@ -36,14 +34,11 @@ class _ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyIconButton(
return ToolbarIconButton(
onPressed: _toggleAttribute,
width: widget.iconSize * kIconButtonFactor,
icon: widget.icon,
highlightColor: _isToggled == true ? theme.shader5 : theme.shader6,
hoverColor: theme.shader5,
isToggled: _isToggled ?? false,
iconName: widget.normalIcon,
);
}

View File

@ -2,9 +2,10 @@ import 'dart:async';
import 'dart:math';
import 'package:editor/flutter_quill.dart';
import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart';
import 'package:styled_widget/styled_widget.dart';
import 'check_button.dart';
import 'header_button.dart';
import 'image_button.dart';
import 'link_button.dart';
import 'toggle_button.dart';
@ -16,7 +17,7 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
const EditorToolbar({
required this.children,
this.toolBarHeight = 36,
this.toolBarHeight = 46,
this.color,
Key? key,
}) : super(key: key);
@ -26,7 +27,7 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
return Container(
color: Theme.of(context).canvasColor,
constraints: BoxConstraints.tightFor(height: preferredSize.height),
child: ToolbarButtonList(buttons: children),
child: ToolbarButtonList(buttons: children).padding(horizontal: 4, vertical: 4),
);
}
@ -62,25 +63,25 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
),
FlowyToggleStyleButton(
attribute: Attribute.bold,
icon: svg('editor/bold'),
normalIcon: 'editor/bold',
iconSize: toolbarIconSize,
controller: controller,
),
FlowyToggleStyleButton(
attribute: Attribute.italic,
icon: svg("editor/restore"),
normalIcon: 'editor/restore',
iconSize: toolbarIconSize,
controller: controller,
),
FlowyToggleStyleButton(
attribute: Attribute.underline,
icon: svg('editor/underline'),
normalIcon: 'editor/underline',
iconSize: toolbarIconSize,
controller: controller,
),
FlowyToggleStyleButton(
attribute: Attribute.strikeThrough,
icon: svg('editor/strikethrough'),
normalIcon: 'editor/strikethrough',
iconSize: toolbarIconSize,
controller: controller,
),
@ -98,20 +99,20 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
webImagePickImpl: webImagePickImpl,
mediaPickSettingSelector: mediaPickSettingSelector,
),
SelectHeaderStyleButton(
FlowyHeaderStyleButton(
controller: controller,
iconSize: toolbarIconSize,
),
FlowyToggleStyleButton(
attribute: Attribute.ol,
controller: controller,
icon: svg('editor/numbers'),
normalIcon: 'editor/numbers',
iconSize: toolbarIconSize,
),
FlowyToggleStyleButton(
attribute: Attribute.ul,
controller: controller,
icon: svg('editor/bullet_list'),
normalIcon: 'editor/bullet_list',
iconSize: toolbarIconSize,
),
FlowyCheckListButton(
@ -122,31 +123,13 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
FlowyToggleStyleButton(
attribute: Attribute.inlineCode,
controller: controller,
icon: svg('editor/inline_block'),
normalIcon: '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'),
normalIcon: 'editor/quote',
iconSize: toolbarIconSize,
),
FlowyLinkStyleButton(
@ -191,14 +174,25 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
List<Widget> children = [];
double width = (widget.buttons.length + 2) * kDefaultIconSize * kIconButtonFactor;
final isFit = constraints.maxWidth > width;
if (!isFit) {
children.add(_buildLeftArrow());
width = width + 18;
}
children.add(_buildScrollableList(constraints));
if (!isFit) {
children.add(_buildRightArrow());
width = width + 18;
}
return SizedBox(
width: min(constraints.maxWidth, (widget.buttons.length + 3) * kDefaultIconSize * kIconButtonFactor + 16),
width: min(constraints.maxWidth, width),
child: Row(
children: <Widget>[
_buildLeftArrow(),
_buildScrollableList(constraints),
_buildRightColor(),
],
children: children,
),
);
},
@ -261,7 +255,7 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
);
}
Widget _buildRightColor() {
Widget _buildRightArrow() {
return SizedBox(
width: 8,
child: Transform.translate(
@ -273,7 +267,6 @@ class _ToolbarButtonListState extends State<ToolbarButtonList> with WidgetsBindi
}
}
/// ScrollBehavior without the Material glow effect.
class _NoGlowBehavior extends ScrollBehavior {
@override
Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) {

View File

@ -0,0 +1,29 @@
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 ToolbarIconButton extends StatelessWidget {
final double width;
final VoidCallback? onPressed;
final bool isToggled;
final String iconName;
const ToolbarIconButton(
{Key? key, required this.onPressed, required this.isToggled, required this.width, required this.iconName})
: super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyIconButton(
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
onPressed: onPressed,
width: width,
icon: isToggled == true ? svg(iconName, color: Colors.white) : svg(iconName),
fillColor: isToggled == true ? theme.main1 : theme.shader6,
hoverColor: isToggled == true ? theme.main1 : theme.shader5,
);
}
}

View File

@ -14,30 +14,30 @@ import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart';
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
class RenameDialog extends StatefulWidget {
final String name;
class TextFieldDialog extends StatefulWidget {
final String value;
final String title;
final void Function()? cancel;
final void Function(String) confirm;
const RenameDialog({
const TextFieldDialog({
required this.title,
required this.name,
required this.value,
required this.confirm,
this.cancel,
Key? key,
}) : super(key: key);
@override
State<RenameDialog> createState() => _CreateRenameDialog();
State<TextFieldDialog> createState() => _CreateTextFieldDialog();
}
class _CreateRenameDialog extends State<RenameDialog> {
String newViewName = "";
class _CreateTextFieldDialog extends State<TextFieldDialog> {
String newValue = "";
@override
void initState() {
newViewName = widget.name;
newValue = widget.value;
super.initState();
}
@ -53,9 +53,10 @@ class _CreateRenameDialog extends State<RenameDialog> {
VSpace(Insets.sm * 1.5),
],
FlowyFormTextInput(
hintText: widget.name,
hintText: widget.value,
autoFocus: true,
onChanged: (text) {
newViewName = text;
newValue = text;
},
),
SizedBox(height: Insets.l),
@ -63,7 +64,7 @@ class _CreateRenameDialog extends State<RenameDialog> {
height: 40,
child: OkCancelButton(
onOkPressed: () {
widget.confirm(newViewName);
widget.confirm(newValue);
},
onCancelPressed: () {
if (widget.cancel != null) {

View File

@ -80,14 +80,14 @@ class AddButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowyIconButton(
width: 16,
width: 22,
onPressed: () {
ActionList(
anchorContext: context,
onSelected: onSelected,
).show(context);
},
icon: svg("home/add"),
icon: svg("home/add").padding(horizontal: 3, vertical: 3),
);
}
}

View File

@ -63,23 +63,25 @@ class ViewSectionItem extends StatelessWidget {
Widget _render(BuildContext context, bool onHover, ViewState state) {
List<Widget> children = [
SizedBox(width: 16, height: 16, child: state.view.thumbnail()),
const HSpace(6),
const HSpace(2),
FlowyText.regular(state.view.name, fontSize: 12),
];
if (onHover || state.isEditing) {
children.add(const Spacer());
children.add(ViewDisclosureButton(
onTap: () => context.read<ViewBloc>().add(const ViewEvent.setIsEditing(true)),
onSelected: (action) {
context.read<ViewBloc>().add(const ViewEvent.setIsEditing(false));
_handleAction(context, action);
},
));
children.add(
ViewDisclosureButton(
onTap: () => context.read<ViewBloc>().add(const ViewEvent.setIsEditing(true)),
onSelected: (action) {
context.read<ViewBloc>().add(const ViewEvent.setIsEditing(false));
_handleAction(context, action);
},
),
);
}
return SizedBox(
height: 24,
height: 26,
child: Row(children: children).padding(
left: MenuAppSizes.expandedPadding,
right: MenuAppSizes.expandedIconPadding,
@ -91,11 +93,11 @@ class ViewSectionItem extends StatelessWidget {
action.foldRight({}, (action, previous) {
switch (action) {
case ViewAction.rename:
RenameDialog(
TextFieldDialog(
title: 'Rename',
name: context.read<ViewBloc>().state.view.name,
confirm: (newName) {
context.read<ViewBloc>().add(ViewEvent.rename(newName));
value: context.read<ViewBloc>().state.view.name,
confirm: (newValue) {
context.read<ViewBloc>().add(ViewEvent.rename(newValue));
},
).show(context);
@ -126,7 +128,8 @@ class ViewDisclosureButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowyIconButton(
width: 16,
iconPadding: const EdgeInsets.all(5),
width: 26,
onPressed: () {
onTap();
ViewActionList(

View File

@ -1,10 +1,8 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
Widget svg(String name) {
final Widget svg = SvgPicture.asset(
'assets/images/$name.svg',
);
Widget svg(String name, {Color? color}) {
final Widget svg = SvgPicture.asset('assets/images/$name.svg', color: color);
return svg;
}

View File

@ -6,7 +6,7 @@ class FlowyIconButton extends StatelessWidget {
final double? height;
final Widget icon;
final VoidCallback? onPressed;
final Color? highlightColor;
final Color? fillColor;
final Color? hoverColor;
final EdgeInsets iconPadding;
@ -15,14 +15,26 @@ class FlowyIconButton extends StatelessWidget {
this.height,
this.onPressed,
this.width = 30,
this.highlightColor = Colors.transparent,
this.fillColor = Colors.transparent,
this.hoverColor = Colors.transparent,
this.iconPadding = const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
this.iconPadding = EdgeInsets.zero,
required this.icon,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget child = icon;
// if (onPressed == null) {
// child = ColorFiltered(
// colorFilter: ColorFilter.mode(
// Colors.grey,
// BlendMode.saturation,
// ),
// child: child,
// );
// }
return ConstrainedBox(
constraints: BoxConstraints.tightFor(width: width, height: width),
child: RawMaterialButton(
@ -30,7 +42,7 @@ class FlowyIconButton extends StatelessWidget {
hoverElevation: 0,
highlightElevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2)),
fillColor: highlightColor,
fillColor: fillColor,
hoverColor: hoverColor,
focusColor: Colors.transparent,
splashColor: Colors.transparent,
@ -39,7 +51,7 @@ class FlowyIconButton extends StatelessWidget {
onPressed: onPressed,
child: Padding(
padding: iconPadding,
child: SizedBox.fromSize(child: icon, size: Size(width, width)),
child: SizedBox.fromSize(child: child, size: Size(width, width)),
),
),
);