feat: support customizing mobile page style (#769)

* feat: customize the list icons

* test: fix tests
This commit is contained in:
Lucas.Xu 2024-04-16 17:30:51 +08:00 • committed by GitHub
commit 5d4f2367a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 72 additions and 43 deletions

View file

@ -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(

View file

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

View file

@ -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,

View file

@ -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(

View file

@ -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,

View file

@ -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]),