feat: support customizing mobile page style (#769)
* feat: customize the list icons * test: fix tests
This commit is contained in:
parent
a9ef209c55
commit
5d4f2367a3
6 changed files with 72 additions and 43 deletions
|
|
@ -102,7 +102,7 @@ class _CustomizeThemeForEditorState extends State<CustomizeThemeForEditor> {
|
|||
// todo-list block
|
||||
TodoListBlockKeys.type: TodoListBlockComponentBuilder(
|
||||
configuration: configuration,
|
||||
iconBuilder: (context, node) {
|
||||
iconBuilder: (context, node, ___) {
|
||||
final checked = node.attributes[TodoListBlockKeys.checked] as bool;
|
||||
return GestureDetector(
|
||||
onTap: () => editorState.apply(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ class TextStyleConfiguration {
|
|||
this.autoComplete = const TextStyle(
|
||||
color: Colors.grey,
|
||||
),
|
||||
this.applyHeightToFirstAscent = false,
|
||||
this.applyHeightToLastDescent = false,
|
||||
});
|
||||
|
||||
/// default text style
|
||||
|
|
@ -49,6 +51,10 @@ class TextStyleConfiguration {
|
|||
/// auto complete text style
|
||||
final TextStyle autoComplete;
|
||||
|
||||
/// apply line height to the first or the last ascent
|
||||
final bool applyHeightToFirstAscent;
|
||||
final bool applyHeightToLastDescent;
|
||||
|
||||
TextStyleConfiguration copyWith({
|
||||
TextStyle? text,
|
||||
TextStyle? bold,
|
||||
|
|
@ -58,6 +64,8 @@ class TextStyleConfiguration {
|
|||
TextStyle? href,
|
||||
TextStyle? code,
|
||||
TextStyle? autoComplete,
|
||||
bool? applyHeightToFirstAscent,
|
||||
bool? applyHeightToLastDescent,
|
||||
}) {
|
||||
return TextStyleConfiguration(
|
||||
text: text ?? this.text,
|
||||
|
|
@ -68,6 +76,10 @@ class TextStyleConfiguration {
|
|||
href: href ?? this.href,
|
||||
code: code ?? this.code,
|
||||
autoComplete: autoComplete ?? this.autoComplete,
|
||||
applyHeightToFirstAscent:
|
||||
applyHeightToFirstAscent ?? this.applyHeightToFirstAscent,
|
||||
applyHeightToLastDescent:
|
||||
applyHeightToLastDescent ?? this.applyHeightToLastDescent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/editor/block_component/base_component/block_icon_builder.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:numerus/roman/roman.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
@ -40,13 +39,19 @@ Node numberedListNode({
|
|||
);
|
||||
}
|
||||
|
||||
typedef NumberedListIconBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
Node node,
|
||||
TextDirection direction,
|
||||
);
|
||||
|
||||
class NumberedListBlockComponentBuilder extends BlockComponentBuilder {
|
||||
NumberedListBlockComponentBuilder({
|
||||
super.configuration,
|
||||
this.iconBuilder,
|
||||
});
|
||||
|
||||
final BlockIconBuilder? iconBuilder;
|
||||
final NumberedListIconBuilder? iconBuilder;
|
||||
|
||||
@override
|
||||
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
||||
|
|
@ -78,7 +83,7 @@ class NumberedListBlockComponentWidget extends BlockComponentStatefulWidget {
|
|||
this.iconBuilder,
|
||||
});
|
||||
|
||||
final BlockIconBuilder? iconBuilder;
|
||||
final NumberedListIconBuilder? iconBuilder;
|
||||
|
||||
@override
|
||||
State<NumberedListBlockComponentWidget> createState() =>
|
||||
|
|
@ -131,7 +136,11 @@ class _NumberedListBlockComponentWidgetState
|
|||
textDirection: textDirection,
|
||||
children: [
|
||||
widget.iconBuilder != null
|
||||
? widget.iconBuilder!(context, node)
|
||||
? widget.iconBuilder!(
|
||||
context,
|
||||
node,
|
||||
textDirection,
|
||||
)
|
||||
: _NumberedListIcon(
|
||||
node: node,
|
||||
textStyle: textStyle,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,7 @@
|
|||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy_editor/src/core/document/attributes.dart';
|
||||
import 'package:appflowy_editor/src/core/document/node.dart';
|
||||
import 'package:appflowy_editor/src/core/document/path.dart';
|
||||
import 'package:appflowy_editor/src/core/document/text_delta.dart';
|
||||
import 'package:appflowy_editor/src/core/location/position.dart';
|
||||
import 'package:appflowy_editor/src/core/location/selection.dart';
|
||||
import 'package:appflowy_editor/src/editor/block_component/base_component/selection/block_selection_container.dart';
|
||||
import 'package:appflowy_editor/src/editor/block_component/rich_text/appflowy_rich_text_keys.dart';
|
||||
import 'package:appflowy_editor/src/editor/util/color_util.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
|
@ -122,6 +111,9 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
|
|||
bool get enableAutoComplete =>
|
||||
widget.editorState.enableAutoComplete && autoCompleteTextProvider != null;
|
||||
|
||||
TextStyleConfiguration get textStyleConfiguration =>
|
||||
widget.editorState.editorStyle.textStyleConfiguration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget child = _buildRichText(context);
|
||||
|
|
@ -328,9 +320,11 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
|
|||
final textSpan = getPlaceholderTextSpan();
|
||||
return RichText(
|
||||
key: placeholderTextKey,
|
||||
textHeightBehavior: const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false,
|
||||
applyHeightToLastDescent: false,
|
||||
textHeightBehavior: TextHeightBehavior(
|
||||
applyHeightToFirstAscent:
|
||||
textStyleConfiguration.applyHeightToFirstAscent,
|
||||
applyHeightToLastDescent:
|
||||
textStyleConfiguration.applyHeightToLastDescent,
|
||||
),
|
||||
text: widget.placeholderTextSpanDecorator != null
|
||||
? widget.placeholderTextSpanDecorator!(textSpan)
|
||||
|
|
@ -347,9 +341,11 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
|
|||
return RichText(
|
||||
key: textKey,
|
||||
textAlign: widget.textAlign ?? TextAlign.start,
|
||||
textHeightBehavior: const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false,
|
||||
applyHeightToLastDescent: false,
|
||||
textHeightBehavior: TextHeightBehavior(
|
||||
applyHeightToFirstAscent:
|
||||
textStyleConfiguration.applyHeightToFirstAscent,
|
||||
applyHeightToLastDescent:
|
||||
textStyleConfiguration.applyHeightToLastDescent,
|
||||
),
|
||||
text: widget.textSpanDecorator != null
|
||||
? widget.textSpanDecorator!(textSpan)
|
||||
|
|
@ -394,9 +390,11 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
|
|||
);
|
||||
return RichText(
|
||||
textAlign: widget.textAlign ?? TextAlign.start,
|
||||
textHeightBehavior: const TextHeightBehavior(
|
||||
applyHeightToFirstAscent: false,
|
||||
applyHeightToLastDescent: false,
|
||||
textHeightBehavior: TextHeightBehavior(
|
||||
applyHeightToFirstAscent:
|
||||
textStyleConfiguration.applyHeightToFirstAscent,
|
||||
applyHeightToLastDescent:
|
||||
textStyleConfiguration.applyHeightToLastDescent,
|
||||
),
|
||||
text: widget.textSpanDecorator != null
|
||||
? widget.textSpanDecorator!(textSpan)
|
||||
|
|
@ -410,12 +408,13 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
|
|||
}
|
||||
|
||||
TextSpan getPlaceholderTextSpan() {
|
||||
final style = widget.editorState.editorStyle.textStyleConfiguration;
|
||||
return TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: widget.placeholderText,
|
||||
style: style.text.copyWith(height: widget.lineHeight),
|
||||
style: textStyleConfiguration.text.copyWith(
|
||||
height: widget.lineHeight,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
@ -426,28 +425,28 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
|
|||
}) {
|
||||
int offset = 0;
|
||||
List<InlineSpan> textSpans = [];
|
||||
final style = widget.editorState.editorStyle.textStyleConfiguration;
|
||||
for (final textInsert in textInserts) {
|
||||
TextStyle textStyle = style.text.copyWith(height: widget.lineHeight);
|
||||
TextStyle textStyle =
|
||||
textStyleConfiguration.text.copyWith(height: widget.lineHeight);
|
||||
final attributes = textInsert.attributes;
|
||||
if (attributes != null) {
|
||||
if (attributes.bold == true) {
|
||||
textStyle = textStyle.combine(style.bold);
|
||||
textStyle = textStyle.combine(textStyleConfiguration.bold);
|
||||
}
|
||||
if (attributes.italic == true) {
|
||||
textStyle = textStyle.combine(style.italic);
|
||||
textStyle = textStyle.combine(textStyleConfiguration.italic);
|
||||
}
|
||||
if (attributes.underline == true) {
|
||||
textStyle = textStyle.combine(style.underline);
|
||||
textStyle = textStyle.combine(textStyleConfiguration.underline);
|
||||
}
|
||||
if (attributes.strikethrough == true) {
|
||||
textStyle = textStyle.combine(style.strikethrough);
|
||||
textStyle = textStyle.combine(textStyleConfiguration.strikethrough);
|
||||
}
|
||||
if (attributes.href != null) {
|
||||
textStyle = textStyle.combine(style.href);
|
||||
textStyle = textStyle.combine(textStyleConfiguration.href);
|
||||
}
|
||||
if (attributes.code == true) {
|
||||
textStyle = textStyle.combine(style.code);
|
||||
textStyle = textStyle.combine(textStyleConfiguration.code);
|
||||
}
|
||||
if (attributes.backgroundColor != null) {
|
||||
textStyle = textStyle.combine(
|
||||
|
|
@ -475,7 +474,7 @@ class _AppFlowyRichTextState extends State<AppFlowyRichText>
|
|||
);
|
||||
}
|
||||
if (attributes.autoComplete == true) {
|
||||
textStyle = textStyle.combine(style.autoComplete);
|
||||
textStyle = textStyle.combine(textStyleConfiguration.autoComplete);
|
||||
}
|
||||
if (attributes.transparent == true) {
|
||||
textStyle = textStyle.combine(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/editor/block_component/base_component/block_icon_builder.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
@ -42,6 +41,12 @@ Node todoListNode({
|
|||
);
|
||||
}
|
||||
|
||||
typedef TodoListIconBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
Node node,
|
||||
VoidCallback onCheck,
|
||||
);
|
||||
|
||||
class TodoListBlockComponentBuilder extends BlockComponentBuilder {
|
||||
TodoListBlockComponentBuilder({
|
||||
super.configuration,
|
||||
|
|
@ -53,7 +58,7 @@ class TodoListBlockComponentBuilder extends BlockComponentBuilder {
|
|||
/// The text style of the todo list block.
|
||||
final TextStyle Function(bool checked)? textStyleBuilder;
|
||||
|
||||
final BlockIconBuilder? iconBuilder;
|
||||
final TodoListIconBuilder? iconBuilder;
|
||||
|
||||
final List<LogicalKeyboardKey>? toggleChildrenTriggers;
|
||||
|
||||
|
|
@ -95,7 +100,7 @@ class TodoListBlockComponentWidget extends BlockComponentStatefulWidget {
|
|||
});
|
||||
|
||||
final TextStyle Function(bool checked)? textStyleBuilder;
|
||||
final BlockIconBuilder? iconBuilder;
|
||||
final TodoListIconBuilder? iconBuilder;
|
||||
final List<LogicalKeyboardKey>? toggleChildrenTriggers;
|
||||
|
||||
@override
|
||||
|
|
@ -151,7 +156,11 @@ class _TodoListBlockComponentWidgetState
|
|||
textDirection: textDirection,
|
||||
children: [
|
||||
widget.iconBuilder != null
|
||||
? widget.iconBuilder!(context, node)
|
||||
? widget.iconBuilder!(
|
||||
context,
|
||||
node,
|
||||
checkOrUncheck,
|
||||
)
|
||||
: _TodoListIcon(
|
||||
checked: checked,
|
||||
onTap: checkOrUncheck,
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ class CustomBlockIcon extends StatelessWidget {
|
|||
iconBuilder: (_, __) => Icon(iconMap[BulletedListBlockKeys.type]),
|
||||
),
|
||||
NumberedListBlockKeys.type: NumberedListBlockComponentBuilder(
|
||||
iconBuilder: (_, __) => Icon(iconMap[NumberedListBlockKeys.type]),
|
||||
iconBuilder: (_, __, ___) => Icon(iconMap[NumberedListBlockKeys.type]),
|
||||
),
|
||||
TodoListBlockKeys.type: TodoListBlockComponentBuilder(
|
||||
iconBuilder: (_, __) => Icon(iconMap[TodoListBlockKeys.type]),
|
||||
iconBuilder: (_, __, ___) => Icon(iconMap[TodoListBlockKeys.type]),
|
||||
),
|
||||
QuoteBlockKeys.type: QuoteBlockComponentBuilder(
|
||||
iconBuilder: (_, __) => Icon(iconMap[QuoteBlockKeys.type]),
|
||||
|
|
|
|||
Loading…
Reference in a new issue