feat: refactor theme plugin, use themedata extension

This commit is contained in:
Lucas.Xu 2022-10-25 21:31:19 +08:00
parent cdee706f46
commit dde50d32d6
18 changed files with 176 additions and 474 deletions

View File

@ -10,7 +10,6 @@ import 'package:flutter/services.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:path_provider/path_provider.dart';
import 'package:universal_html/html.dart' as html;
@ -57,7 +56,6 @@ class _MyHomePageState extends State<MyHomePage> {
int _pageIndex = 0;
EditorState? _editorState;
bool darkMode = false;
EditorStyle _editorStyle = EditorStyle.defaultStyle();
Future<String>? _jsonString;
@override
@ -132,7 +130,8 @@ class _MyHomePageState extends State<MyHomePage> {
CheckboxPluginStyle.dark,
NumberListPluginStyle.dark,
QuotedTextPluginStyle.dark,
BulletedListPluginStyle.dark
BulletedListPluginStyle.dark,
EditorStyle.dark,
])
: ThemeData.light().copyWith(
extensions: [
@ -140,34 +139,32 @@ class _MyHomePageState extends State<MyHomePage> {
CheckboxPluginStyle.light,
NumberListPluginStyle.light,
QuotedTextPluginStyle.light,
BulletedListPluginStyle.light
BulletedListPluginStyle.light,
EditorStyle.light,
],
);
return Theme(
data: themeData,
child: Container(
color: darkMode ? Colors.black : Colors.white,
width: MediaQuery.of(context).size.width,
child: AppFlowyEditor(
editorState: _editorState!,
editorStyle: _editorStyle,
editable: true,
customBuilders: {
'text/code_block': CodeBlockNodeWidgetBuilder(),
'tex': TeXBlockNodeWidgetBuidler(),
'horizontal_rule': HorizontalRuleWidgetBuilder(),
},
shortcutEvents: [
enterInCodeBlock,
ignoreKeysInCodeBlock,
insertHorizontalRule,
],
selectionMenuItems: [
codeBlockMenuItem,
teXBlockMenuItem,
horizontalRuleMenuItem,
],
),
return Container(
color: darkMode ? Colors.black : Colors.white,
width: MediaQuery.of(context).size.width,
child: AppFlowyEditor(
editorState: _editorState!,
themeData: themeData,
editable: true,
customBuilders: {
'text/code_block': CodeBlockNodeWidgetBuilder(),
'tex': TeXBlockNodeWidgetBuidler(),
'horizontal_rule': HorizontalRuleWidgetBuilder(),
},
shortcutEvents: [
enterInCodeBlock,
ignoreKeysInCodeBlock,
insertHorizontalRule,
],
selectionMenuItems: [
codeBlockMenuItem,
teXBlockMenuItem,
horizontalRuleMenuItem,
],
),
);
} else {
@ -207,8 +204,6 @@ class _MyHomePageState extends State<MyHomePage> {
icon: const Icon(Icons.color_lens),
onPressed: () {
setState(() {
_editorStyle =
darkMode ? EditorStyle.defaultStyle() : _customizedStyle();
darkMode = !darkMode;
});
},
@ -277,44 +272,4 @@ class _MyHomePageState extends State<MyHomePage> {
});
}
}
EditorStyle _customizedStyle() {
final editorStyle = EditorStyle.defaultStyle();
return editorStyle.copyWith(
cursorColor: Colors.white,
selectionColor: Colors.blue.withOpacity(0.3),
textStyle: editorStyle.textStyle.copyWith(
defaultTextStyle: GoogleFonts.poppins().copyWith(
color: Colors.white,
fontSize: 14.0,
),
defaultPlaceholderTextStyle: GoogleFonts.poppins().copyWith(
color: Colors.white.withOpacity(0.5),
fontSize: 14.0,
),
bold: const TextStyle(fontWeight: FontWeight.w900),
code: TextStyle(
fontStyle: FontStyle.italic,
color: Colors.red[300],
backgroundColor: Colors.grey.withOpacity(0.3),
),
highlightColorHex: '0x6FFFEB3B',
),
pluginStyles: {
'text/quote': builtInPluginStyle
..update(
'textStyle',
(_) {
return (EditorState editorState, Node node) {
return TextStyle(
color: Colors.blue[200],
fontStyle: FontStyle.italic,
fontSize: 12.0,
);
};
},
),
},
);
}
}

View File

@ -167,7 +167,7 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge>
textNode: widget.textNode,
editorState: widget.editorState,
textSpanDecorator: (textSpan) => TextSpan(
style: widget.editorState.editorStyle.textStyle.defaultTextStyle,
style: widget.editorState.editorStyle.textStyle,
children: codeTextSpan,
),
),

View File

@ -59,13 +59,14 @@ class EditorState {
/// Stores the selection menu items.
List<SelectionMenuItem> selectionMenuItems = [];
/// Stores the editor style.
EditorStyle editorStyle = EditorStyle.defaultStyle();
/// Operation stream.
Stream<Transaction> get transactionStream => _observer.stream;
final StreamController<Transaction> _observer = StreamController.broadcast();
late ThemeData themeData;
EditorStyle get editorStyle =>
themeData.extension<EditorStyle>() ?? EditorStyle.light;
final UndoManager undoManager = UndoManager();
Selection? _cursorSelection;

View File

@ -10,56 +10,6 @@ abstract class BuiltInTextWidget extends StatefulWidget {
TextNode get textNode;
}
mixin BuiltInStyleMixin<T extends BuiltInTextWidget> on State<T> {
EdgeInsets get padding {
final padding = widget.editorState.editorStyle.style(
widget.editorState,
widget.textNode,
'padding',
);
if (padding is EdgeInsets) {
return padding;
}
return const EdgeInsets.all(0);
}
TextStyle get textStyle {
final textStyle = widget.editorState.editorStyle.style(
widget.editorState,
widget.textNode,
'textStyle',
);
if (textStyle is TextStyle) {
return textStyle;
}
return const TextStyle();
}
Size? get iconSize {
final iconSize = widget.editorState.editorStyle.style(
widget.editorState,
widget.textNode,
'iconSize',
);
if (iconSize is Size) {
return iconSize;
}
return const Size.square(18.0);
}
EdgeInsets? get iconPadding {
final iconPadding = widget.editorState.editorStyle.style(
widget.editorState,
widget.textNode,
'iconPadding',
);
if (iconPadding is EdgeInsets) {
return iconPadding;
}
return const EdgeInsets.all(0);
}
}
mixin BuiltInTextWidgetMixin<T extends BuiltInTextWidget> on State<T>
implements DefaultSelectable {
@override

View File

@ -61,7 +61,8 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
}
BulletedListPluginStyle get style =>
Theme.of(context).extension<BulletedListPluginStyle>()!;
Theme.of(context).extension<BulletedListPluginStyle>() ??
BulletedListPluginStyle.light;
EdgeInsets get padding => style.padding(
widget.editorState,
@ -97,7 +98,7 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
textSpan.updateTextStyle(textStyle),
placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(textStyle),
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
lineHeight: widget.editorState.editorStyle.lineHeight,
textNode: widget.textNode,
editorState: widget.editorState,
),

View File

@ -54,7 +54,8 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
}
CheckboxPluginStyle get style =>
Theme.of(context).extension<CheckboxPluginStyle>()!;
Theme.of(context).extension<CheckboxPluginStyle>() ??
CheckboxPluginStyle.light;
EdgeInsets get padding => style.padding(
widget.editorState,
@ -94,7 +95,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
child: FlowyRichText(
key: _richTextKey,
placeholderText: 'To-do',
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
lineHeight: widget.editorState.editorStyle.lineHeight,
textNode: widget.textNode,
textSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(textStyle),

View File

@ -202,12 +202,13 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
}
TextSpan get _placeholderTextSpan {
final style = widget.editorState.editorStyle.textStyle;
final placeholderTextStyle =
widget.editorState.editorStyle.placeholderTextStyle;
return TextSpan(
children: [
TextSpan(
text: widget.placeholderText,
style: style.defaultPlaceholderTextStyle,
style: placeholderTextStyle,
),
],
);
@ -216,10 +217,10 @@ class _FlowyRichTextState extends State<FlowyRichText> with SelectableMixin {
TextSpan get _textSpan {
var offset = 0;
List<TextSpan> textSpans = [];
final style = widget.editorState.editorStyle.textStyle;
final style = widget.editorState.editorStyle;
final textInserts = widget.textNode.delta.whereType<TextInsert>();
for (final textInsert in textInserts) {
var textStyle = style.defaultTextStyle;
var textStyle = style.textStyle!;
GestureRecognizer? recognizer;
final attributes = textInsert.attributes;
if (attributes != null) {

View File

@ -60,7 +60,8 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
}
HeadingPluginStyle get style =>
Theme.of(context).extension<HeadingPluginStyle>()!;
Theme.of(context).extension<HeadingPluginStyle>() ??
HeadingPluginStyle.light;
EdgeInsets get padding => style.padding(
widget.editorState,
@ -82,7 +83,7 @@ class _HeadingTextNodeWidgetState extends State<HeadingTextNodeWidget>
placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(textStyle),
textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle),
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
lineHeight: widget.editorState.editorStyle.lineHeight,
textNode: widget.textNode,
editorState: widget.editorState,
),

View File

@ -4,7 +4,7 @@ import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
import 'package:appflowy_editor/src/render/selection/selectable.dart';
import 'package:appflowy_editor/src/render/style/plugin_style.dart';
import 'package:appflowy_editor/src/render/style/built_in_plugin_styles.dart';
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
import 'package:flutter/material.dart';
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
@ -59,20 +59,9 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
return super.baseOffset.translate(0, padding.top);
}
Color get numberColor {
final numberColor = widget.editorState.editorStyle.style(
widget.editorState,
widget.textNode,
'numberColor',
);
if (numberColor is Color) {
return numberColor;
}
return Colors.black;
}
NumberListPluginStyle get style =>
Theme.of(context).extension<NumberListPluginStyle>()!;
Theme.of(context).extension<NumberListPluginStyle>() ??
NumberListPluginStyle.light;
EdgeInsets get padding => style.padding(
widget.editorState,
@ -106,7 +95,7 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
placeholderText: 'List',
textNode: widget.textNode,
editorState: widget.editorState,
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
lineHeight: widget.editorState.editorStyle.lineHeight,
placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(textStyle),
textSpanDecorator: (textSpan) =>

View File

@ -60,7 +60,8 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
}
QuotedTextPluginStyle get style =>
Theme.of(context).extension<QuotedTextPluginStyle>()!;
Theme.of(context).extension<QuotedTextPluginStyle>() ??
QuotedTextPluginStyle.light;
EdgeInsets get padding => style.padding(
widget.editorState,
@ -98,7 +99,7 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
textSpan.updateTextStyle(textStyle),
placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(textStyle),
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
lineHeight: widget.editorState.editorStyle.lineHeight,
editorState: widget.editorState,
),
),

View File

@ -4,6 +4,7 @@ import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
import 'package:appflowy_editor/src/render/selection/selectable.dart';
import 'package:appflowy_editor/src/render/style/editor_style.dart';
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
import 'package:flutter/material.dart';
import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
@ -43,11 +44,7 @@ class RichTextNodeWidget extends BuiltInTextWidget {
// customize
class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
with
SelectableMixin,
DefaultSelectable,
BuiltInStyleMixin,
BuiltInTextWidgetMixin {
with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin {
@override
GlobalKey? get iconKey => null;
@ -59,20 +56,26 @@ class _RichTextNodeWidgetState extends State<RichTextNodeWidget>
@override
Offset get baseOffset {
return padding.topLeft;
return textPadding.topLeft;
}
EditorStyle get style => widget.editorState.editorStyle;
EdgeInsets get textPadding => style.textPadding!;
TextStyle get textStyle => style.textStyle!;
@override
Widget buildWithSingle(BuildContext context) {
return Padding(
padding: padding,
padding: textPadding,
child: FlowyRichText(
key: _richTextKey,
textNode: widget.textNode,
textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle),
placeholderTextSpanDecorator: (textSpan) =>
textSpan.updateTextStyle(textStyle),
lineHeight: widget.editorState.editorStyle.textStyle.lineHeight,
lineHeight: widget.editorState.editorStyle.lineHeight,
editorState: widget.editorState,
),
);

View File

@ -2,6 +2,21 @@ import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
import 'package:flutter/material.dart';
Iterable<ThemeExtension<dynamic>> get lightPlguinStyleExtension => [
HeadingPluginStyle.light,
CheckboxPluginStyle.light,
NumberListPluginStyle.light,
QuotedTextPluginStyle.light,
];
Iterable<ThemeExtension<dynamic>> get darkPlguinStyleExtension => [
HeadingPluginStyle.dark,
CheckboxPluginStyle.dark,
NumberListPluginStyle.dark,
QuotedTextPluginStyle.dark,
BulletedListPluginStyle.dark,
];
typedef TextStyleCustomizer = TextStyle Function(
EditorState editorState, TextNode textNode);
typedef PaddingCustomizer = EdgeInsets Function(

View File

@ -1,16 +1,21 @@
import 'package:flutter/material.dart';
import 'package:appflowy_editor/src/core/document/node.dart';
import 'package:appflowy_editor/src/editor_state.dart';
import 'package:appflowy_editor/src/extensions/attributes_extension.dart';
Iterable<ThemeExtension<dynamic>> get lightEditorStyleExtension => [
EditorStyle.light,
];
class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
Iterable<ThemeExtension<dynamic>> get dartEditorStyleExtension => [
EditorStyle.dark,
];
class EditorStyle extends ThemeExtension<EditorStyle> {
// Editor styles
final EdgeInsets? padding;
final Color? cursorColor;
final Color? selectionColor;
// Text styles
final EdgeInsets? textPadding;
final TextStyle? textStyle;
final TextStyle? placeholderTextStyle;
final double lineHeight;
@ -24,10 +29,11 @@ class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
final TextStyle? code;
final String? highlightColorHex;
EditorStyleV2({
EditorStyle({
required this.padding,
required this.cursorColor,
required this.selectionColor,
required this.textPadding,
required this.textStyle,
required this.placeholderTextStyle,
required this.bold,
@ -41,7 +47,7 @@ class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
});
@override
EditorStyleV2 copyWith({
EditorStyle copyWith({
EdgeInsets? padding,
Color? cursorColor,
Color? selectionColor,
@ -56,10 +62,11 @@ class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
String? highlightColorHex,
double? lineHeight,
}) {
return EditorStyleV2(
return EditorStyle(
padding: padding ?? this.padding,
cursorColor: cursorColor ?? this.cursorColor,
selectionColor: selectionColor ?? this.selectionColor,
textPadding: textPadding ?? textPadding,
textStyle: textStyle ?? this.textStyle,
placeholderTextStyle: placeholderTextStyle ?? this.placeholderTextStyle,
bold: bold ?? this.bold,
@ -74,14 +81,15 @@ class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
}
@override
ThemeExtension<EditorStyleV2> lerp(
ThemeExtension<EditorStyleV2>? other, double t) {
if (other == null || other is! EditorStyleV2) {
ThemeExtension<EditorStyle> lerp(
ThemeExtension<EditorStyle>? other, double t) {
if (other == null || other is! EditorStyle) {
return this;
}
return EditorStyleV2(
return EditorStyle(
padding: EdgeInsets.lerp(padding, other.padding, t),
cursorColor: Color.lerp(cursorColor, other.cursorColor, t),
textPadding: EdgeInsets.lerp(textPadding, other.textPadding, t),
selectionColor: Color.lerp(selectionColor, other.selectionColor, t),
textStyle: TextStyle.lerp(textStyle, other.textStyle, t),
placeholderTextStyle:
@ -96,262 +104,36 @@ class EditorStyleV2 extends ThemeExtension<EditorStyleV2> {
lineHeight: lineHeight,
);
}
}
typedef PluginStyler = Object Function(EditorState editorState, Node node);
typedef PluginStyle = Map<String, PluginStyler>;
/// Editor style configuration
class EditorStyle {
EditorStyle({
required this.padding,
required this.textStyle,
required this.cursorColor,
required this.selectionColor,
Map<String, PluginStyle> pluginStyles = const {},
}) {
_pluginStyles.addAll(pluginStyles);
}
EditorStyle.defaultStyle()
: padding = const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0),
textStyle = BuiltInTextStyle.builtIn(),
cursorColor = const Color(0xFF00BCF0),
selectionColor = const Color.fromARGB(53, 111, 201, 231);
/// The margin of the document context from the editor.
final EdgeInsets padding;
final BuiltInTextStyle textStyle;
final Color cursorColor;
final Color selectionColor;
final Map<String, PluginStyle> _pluginStyles = Map.from(builtInTextStylers);
Object? style(EditorState editorState, Node node, String key) {
final styler = _pluginStyles[node.id]?[key];
if (styler != null) {
return styler(editorState, node);
}
return null;
}
@override
EditorStyle copyWith({
EdgeInsets? padding,
BuiltInTextStyle? textStyle,
Color? cursorColor,
Color? selectionColor,
Map<String, PluginStyle>? pluginStyles,
}) {
return EditorStyle(
padding: padding ?? this.padding,
textStyle: textStyle ?? this.textStyle,
cursorColor: cursorColor ?? this.cursorColor,
selectionColor: selectionColor ?? this.selectionColor,
pluginStyles: pluginStyles ?? {},
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is EditorStyle &&
other.padding == padding &&
other.textStyle == textStyle &&
other.cursorColor == cursorColor &&
other.selectionColor == selectionColor;
}
@override
int get hashCode {
return padding.hashCode ^
textStyle.hashCode ^
cursorColor.hashCode ^
selectionColor.hashCode;
}
}
PluginStyle get builtInPluginStyle => Map.from({
'padding': (_, __) => const EdgeInsets.symmetric(vertical: 8.0),
'textStyle': (_, __) => const TextStyle(),
'iconSize': (_, __) => const Size.square(20.0),
'iconPadding': (_, __) => const EdgeInsets.only(right: 5.0),
});
Map<String, PluginStyle> builtInTextStylers = {
'text': builtInPluginStyle,
'text/checkbox': builtInPluginStyle
..update(
'textStyle',
(_) => (EditorState editorState, Node node) {
if (node is TextNode && node.attributes.check == true) {
return const TextStyle(
color: Colors.grey,
decoration: TextDecoration.lineThrough,
);
}
return const TextStyle();
},
static final light = EditorStyle(
padding: const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0),
cursorColor: const Color(0xFF00BCF0),
selectionColor: const Color.fromARGB(53, 111, 201, 231),
textPadding: const EdgeInsets.symmetric(vertical: 8.0),
textStyle: const TextStyle(fontSize: 16.0, color: Colors.black),
placeholderTextStyle: const TextStyle(fontSize: 16.0, color: Colors.grey),
bold: const TextStyle(fontWeight: FontWeight.bold),
italic: const TextStyle(fontStyle: FontStyle.italic),
underline: const TextStyle(decoration: TextDecoration.underline),
strikethrough: const TextStyle(decoration: TextDecoration.lineThrough),
href: const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
'text/heading': builtInPluginStyle
..update(
'textStyle',
(_) => (EditorState editorState, Node node) {
final headingToFontSize = {
'h1': 32.0,
'h2': 28.0,
'h3': 24.0,
'h4': 18.0,
'h5': 18.0,
'h6': 18.0,
};
final fontSize = headingToFontSize[node.attributes.heading] ?? 18.0;
return TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold);
},
code: const TextStyle(
fontFamily: 'monospace',
color: Color(0xFF00BCF0),
backgroundColor: Color(0xFFE0F8FF),
),
'text/bulleted-list': builtInPluginStyle,
'text/number-list': builtInPluginStyle
..addAll({
'numberColor': (EditorState editorState, Node node) {
return Colors.black;
},
'iconPadding': (EditorState editorState, Node node) {
return const EdgeInsets.only(left: 5.0, right: 5.0);
},
}),
'text/bulleted-list': builtInPluginStyle
..addAll({
'bulletColor': (EditorState editorState, Node node) {
return Colors.black;
},
}),
'text/quote': builtInPluginStyle,
'image': builtInPluginStyle,
};
highlightColorHex: '0x6000BCF0',
lineHeight: 1.5,
);
class BuiltInTextStyle {
const BuiltInTextStyle({
required this.defaultTextStyle,
required this.defaultPlaceholderTextStyle,
required this.bold,
required this.italic,
required this.underline,
required this.strikethrough,
required this.href,
required this.code,
this.highlightColorHex = '0x6000BCF0',
this.lineHeight = 1.5,
});
final TextStyle defaultTextStyle;
final TextStyle defaultPlaceholderTextStyle;
final TextStyle bold;
final TextStyle italic;
final TextStyle underline;
final TextStyle strikethrough;
final TextStyle href;
final TextStyle code;
final String highlightColorHex;
final double lineHeight;
BuiltInTextStyle.builtIn()
: defaultTextStyle = const TextStyle(fontSize: 16.0, color: Colors.black),
defaultPlaceholderTextStyle =
const TextStyle(fontSize: 16.0, color: Colors.grey),
bold = const TextStyle(fontWeight: FontWeight.bold),
italic = const TextStyle(fontStyle: FontStyle.italic),
underline = const TextStyle(decoration: TextDecoration.underline),
strikethrough = const TextStyle(decoration: TextDecoration.lineThrough),
href = const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
code = const TextStyle(
fontFamily: 'monospace',
color: Color(0xFF00BCF0),
backgroundColor: Color(0xFFE0F8FF),
),
highlightColorHex = '0x6000BCF0',
lineHeight = 1.5;
BuiltInTextStyle.builtInDarkMode()
: defaultTextStyle = const TextStyle(fontSize: 16.0, color: Colors.white),
defaultPlaceholderTextStyle = TextStyle(
fontSize: 16.0,
color: Colors.white.withOpacity(0.3),
),
bold = const TextStyle(fontWeight: FontWeight.bold),
italic = const TextStyle(fontStyle: FontStyle.italic),
underline = const TextStyle(decoration: TextDecoration.underline),
strikethrough = const TextStyle(decoration: TextDecoration.lineThrough),
href = const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
code = const TextStyle(
fontFamily: 'monospace',
color: Color(0xFF00BCF0),
backgroundColor: Color(0xFFE0F8FF),
),
highlightColorHex = '0x6000BCF0',
lineHeight = 1.5;
BuiltInTextStyle copyWith({
TextStyle? defaultTextStyle,
TextStyle? defaultPlaceholderTextStyle,
TextStyle? bold,
TextStyle? italic,
TextStyle? underline,
TextStyle? strikethrough,
TextStyle? href,
TextStyle? code,
String? highlightColorHex,
double? lineHeight,
}) {
return BuiltInTextStyle(
defaultTextStyle: defaultTextStyle ?? this.defaultTextStyle,
defaultPlaceholderTextStyle:
defaultPlaceholderTextStyle ?? this.defaultPlaceholderTextStyle,
bold: bold ?? this.bold,
italic: italic ?? this.italic,
underline: underline ?? this.underline,
strikethrough: strikethrough ?? this.strikethrough,
href: href ?? this.href,
code: code ?? this.code,
highlightColorHex: highlightColorHex ?? this.highlightColorHex,
lineHeight: lineHeight ?? this.lineHeight,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is BuiltInTextStyle &&
other.defaultTextStyle == defaultTextStyle &&
other.defaultPlaceholderTextStyle == defaultPlaceholderTextStyle &&
other.bold == bold &&
other.italic == italic &&
other.underline == underline &&
other.strikethrough == strikethrough &&
other.href == href &&
other.code == code &&
other.highlightColorHex == highlightColorHex &&
other.lineHeight == lineHeight;
}
@override
int get hashCode {
return defaultTextStyle.hashCode ^
defaultPlaceholderTextStyle.hashCode ^
bold.hashCode ^
italic.hashCode ^
underline.hashCode ^
strikethrough.hashCode ^
href.hashCode ^
code.hashCode ^
highlightColorHex.hashCode ^
lineHeight.hashCode;
}
static final dark = light.copyWith(
textStyle: const TextStyle(fontSize: 16.0, color: Colors.white),
placeholderTextStyle: TextStyle(
fontSize: 16.0,
color: Colors.white.withOpacity(0.3),
),
);
}

View File

@ -259,7 +259,7 @@ List<ToolbarItem> defaultToolbarItems = [
),
handler: (editorState, context) => formatHighlight(
editorState,
editorState.editorStyle.textStyle.highlightColorHex,
editorState.editorStyle.highlightColorHex!,
),
),
];

View File

@ -1,12 +1,9 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/flutter/overlay.dart';
import 'package:appflowy_editor/src/render/image/image_node_builder.dart';
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
import 'package:appflowy_editor/src/render/style/editor_style.dart';
import 'package:appflowy_editor/src/service/shortcut_event/built_in_shortcut_events.dart';
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart';
import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
import 'package:appflowy_editor/src/editor_state.dart';
import 'package:appflowy_editor/src/render/editor/editor_entry.dart';
import 'package:appflowy_editor/src/render/rich_text/bulleted_list_text.dart';
import 'package:appflowy_editor/src/render/rich_text/checkbox_text.dart';
@ -14,12 +11,6 @@ import 'package:appflowy_editor/src/render/rich_text/heading_text.dart';
import 'package:appflowy_editor/src/render/rich_text/number_list_text.dart';
import 'package:appflowy_editor/src/render/rich_text/quoted_text.dart';
import 'package:appflowy_editor/src/render/rich_text/rich_text.dart';
import 'package:appflowy_editor/src/service/input_service.dart';
import 'package:appflowy_editor/src/service/keyboard_service.dart';
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
import 'package:appflowy_editor/src/service/scroll_service.dart';
import 'package:appflowy_editor/src/service/selection_service.dart';
import 'package:appflowy_editor/src/service/toolbar_service.dart';
NodeWidgetBuilders defaultBuilders = {
'editor': EditorEntryWidgetBuilder(),
@ -33,15 +24,21 @@ NodeWidgetBuilders defaultBuilders = {
};
class AppFlowyEditor extends StatefulWidget {
const AppFlowyEditor({
AppFlowyEditor({
Key? key,
required this.editorState,
this.customBuilders = const {},
this.shortcutEvents = const [],
this.selectionMenuItems = const [],
this.editable = true,
required this.editorStyle,
}) : super(key: key);
ThemeData? themeData,
}) : super(key: key) {
this.themeData = themeData ??
ThemeData.light().copyWith(extensions: [
...lightEditorStyleExtension,
...lightPlguinStyleExtension,
]);
}
final EditorState editorState;
@ -53,7 +50,7 @@ class AppFlowyEditor extends StatefulWidget {
final List<SelectionMenuItem> selectionMenuItems;
final EditorStyle editorStyle;
late final ThemeData themeData;
final bool editable;
@ -65,13 +62,15 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
Widget? services;
EditorState get editorState => widget.editorState;
EditorStyle get editorStyle =>
editorState.themeData.extension<EditorStyle>() ?? EditorStyle.light;
@override
void initState() {
super.initState();
editorState.selectionMenuItems = widget.selectionMenuItems;
editorState.editorStyle = widget.editorStyle;
editorState.themeData = widget.themeData;
editorState.service.renderPluginService = _createRenderPlugin();
editorState.editable = widget.editable;
}
@ -85,7 +84,7 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
editorState.service.renderPluginService = _createRenderPlugin();
}
editorState.editorStyle = widget.editorStyle;
editorState.themeData = widget.themeData;
editorState.editable = widget.editable;
services = null;
}
@ -102,38 +101,41 @@ class _AppFlowyEditorState extends State<AppFlowyEditor> {
);
}
AppFlowyScroll _buildServices(BuildContext context) {
return AppFlowyScroll(
key: editorState.service.scrollServiceKey,
child: Padding(
padding: widget.editorStyle.padding,
child: AppFlowySelection(
key: editorState.service.selectionServiceKey,
cursorColor: widget.editorStyle.cursorColor,
selectionColor: widget.editorStyle.selectionColor,
editorState: editorState,
editable: widget.editable,
child: AppFlowyInput(
key: editorState.service.inputServiceKey,
Widget _buildServices(BuildContext context) {
return Theme(
data: widget.themeData,
child: AppFlowyScroll(
key: editorState.service.scrollServiceKey,
child: Padding(
padding: editorStyle.padding!,
child: AppFlowySelection(
key: editorState.service.selectionServiceKey,
cursorColor: editorStyle.cursorColor!,
selectionColor: editorStyle.selectionColor!,
editorState: editorState,
editable: widget.editable,
child: AppFlowyKeyboard(
key: editorState.service.keyboardServiceKey,
editable: widget.editable,
shortcutEvents: [
...widget.shortcutEvents,
...builtInShortcutEvents,
],
child: AppFlowyInput(
key: editorState.service.inputServiceKey,
editorState: editorState,
child: FlowyToolbar(
key: editorState.service.toolbarServiceKey,
editable: widget.editable,
child: AppFlowyKeyboard(
key: editorState.service.keyboardServiceKey,
editable: widget.editable,
shortcutEvents: [
...widget.shortcutEvents,
...builtInShortcutEvents,
],
editorState: editorState,
child:
editorState.service.renderPluginService.buildPluginWidget(
NodeWidgetContext(
context: context,
node: editorState.document.root,
editorState: editorState,
child: FlowyToolbar(
key: editorState.service.toolbarServiceKey,
editorState: editorState,
child:
editorState.service.renderPluginService.buildPluginWidget(
NodeWidgetContext(
context: context,
node: editorState.document.root,
editorState: editorState,
),
),
),
),

View File

@ -57,7 +57,7 @@ ShortcutEventHandler formatHighlightEventHandler = (editorState, event) {
}
formatHighlight(
editorState,
editorState.editorStyle.textStyle.highlightColorHex,
editorState.editorStyle.highlightColorHex!,
);
return KeyEventResult.handled;
};

View File

@ -39,7 +39,6 @@ class EditorWidgetTester {
home: Scaffold(
body: AppFlowyEditor(
editorState: _editorState,
editorStyle: EditorStyle.defaultStyle(),
),
),
);

View File

@ -49,10 +49,11 @@ void main() async {
final editorRect = tester.getRect(editorFinder);
final leftImageRect = tester.getRect(imageFinder.at(0));
expect(leftImageRect.left, editor.editorState.editorStyle.padding.left);
expect(
leftImageRect.left, editor.editorState.editorStyle.padding!.left);
final rightImageRect = tester.getRect(imageFinder.at(2));
expect(rightImageRect.right,
editorRect.right - editor.editorState.editorStyle.padding.right);
editorRect.right - editor.editorState.editorStyle.padding!.right);
final centerImageRect = tester.getRect(imageFinder.at(1));
expect(centerImageRect.left,
(leftImageRect.left + rightImageRect.left) / 2.0);