import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; import 'package:appflowy/util/google_font_family_extension.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; class EditorStyleCustomizer { EditorStyleCustomizer({ required this.context, required this.padding, }); final BuildContext context; final EdgeInsets padding; EditorStyle style() { if (PlatformExtension.isDesktopOrWeb) { return desktop(); } else if (PlatformExtension.isMobile) { return mobile(); } throw UnimplementedError(); } EditorStyle desktop() { final theme = Theme.of(context); final fontSize = context.read().state.fontSize; final fontFamily = context.read().state.fontFamily; final defaultTextDirection = context.read().state.defaultTextDirection; return EditorStyle.desktop( padding: padding, cursorColor: theme.colorScheme.primary, defaultTextDirection: defaultTextDirection, textStyleConfiguration: TextStyleConfiguration( text: baseTextStyle(fontFamily).copyWith( fontSize: fontSize, color: theme.colorScheme.onBackground, height: 1.5, ), bold: baseTextStyle(fontFamily, fontWeight: FontWeight.bold).copyWith( fontWeight: FontWeight.w600, ), italic: baseTextStyle(fontFamily).copyWith( fontStyle: FontStyle.italic, ), underline: baseTextStyle(fontFamily).copyWith( decoration: TextDecoration.underline, ), strikethrough: baseTextStyle(fontFamily).copyWith( decoration: TextDecoration.lineThrough, ), href: baseTextStyle(fontFamily).copyWith( color: theme.colorScheme.primary, decoration: TextDecoration.underline, ), code: GoogleFonts.arefRuqaaInk( textStyle: baseTextStyle(fontFamily).copyWith( fontSize: fontSize, fontWeight: FontWeight.normal, fontStyle: FontStyle.italic, color: Colors.red, backgroundColor: theme.colorScheme.inverseSurface.withOpacity(0.8), ), ), ), textSpanDecorator: customizeAttributeDecorator, ); } EditorStyle mobile() { final theme = Theme.of(context); final fontSize = context.read().state.fontSize; final fontFamily = context.read().state.fontFamily; return EditorStyle.desktop( padding: padding, cursorColor: theme.colorScheme.primary, textStyleConfiguration: TextStyleConfiguration( text: baseTextStyle(fontFamily).copyWith( fontSize: fontSize, color: theme.colorScheme.onBackground, height: 1.5, ), bold: baseTextStyle(fontFamily).copyWith( fontWeight: FontWeight.w600, ), italic: baseTextStyle(fontFamily).copyWith(fontStyle: FontStyle.italic), underline: baseTextStyle(fontFamily) .copyWith(decoration: TextDecoration.underline), strikethrough: baseTextStyle(fontFamily) .copyWith(decoration: TextDecoration.lineThrough), href: baseTextStyle(fontFamily).copyWith( color: theme.colorScheme.primary, decoration: TextDecoration.underline, ), code: GoogleFonts.arefRuqaaInk( textStyle: baseTextStyle(fontFamily).copyWith( fontSize: fontSize, fontWeight: FontWeight.normal, color: Colors.red, backgroundColor: theme.colorScheme.inverseSurface, ), ), ), ); } TextStyle headingStyleBuilder(int level) { final fontSize = context.read().state.fontSize; final fontSizes = [ fontSize + 16, fontSize + 12, fontSize + 8, fontSize + 4, fontSize + 2, fontSize ]; return TextStyle( fontSize: fontSizes.elementAtOrNull(level - 1) ?? fontSize, fontWeight: FontWeight.bold, ); } TextStyle codeBlockStyleBuilder() { final theme = Theme.of(context); final fontSize = context.read().state.fontSize; final fontFamily = context.read().state.fontFamily; return baseTextStyle(fontFamily).copyWith( fontSize: fontSize, height: 1.5, color: theme.colorScheme.onBackground, ); } TextStyle outlineBlockPlaceholderStyleBuilder() { final theme = Theme.of(context); final fontSize = context.read().state.fontSize; return TextStyle( fontFamily: 'poppins', fontSize: fontSize, height: 1.5, color: theme.colorScheme.onBackground.withOpacity(0.6), ); } SelectionMenuStyle selectionMenuStyleBuilder() { final theme = Theme.of(context); return SelectionMenuStyle( selectionMenuBackgroundColor: theme.cardColor, selectionMenuItemTextColor: theme.colorScheme.onBackground, selectionMenuItemIconColor: theme.colorScheme.onBackground, selectionMenuItemSelectedIconColor: theme.colorScheme.onSurface, selectionMenuItemSelectedTextColor: theme.colorScheme.onSurface, selectionMenuItemSelectedColor: theme.hoverColor, ); } InlineActionsMenuStyle inlineActionsMenuStyleBuilder() { final theme = Theme.of(context); return InlineActionsMenuStyle( backgroundColor: theme.cardColor, groupTextColor: theme.colorScheme.onBackground.withOpacity(.8), menuItemTextColor: theme.colorScheme.onBackground, menuItemSelectedColor: theme.hoverColor, menuItemSelectedTextColor: theme.colorScheme.onSurface, ); } FloatingToolbarStyle floatingToolbarStyleBuilder() { final theme = Theme.of(context); return FloatingToolbarStyle( backgroundColor: theme.colorScheme.onTertiary, ); } TextStyle baseTextStyle( String fontFamily, { FontWeight? fontWeight, }) { try { return GoogleFonts.getFont( fontFamily, fontWeight: fontWeight, ); } on Exception { return GoogleFonts.getFont('Poppins'); } } InlineSpan customizeAttributeDecorator( BuildContext context, Node node, int index, TextInsert text, TextSpan textSpan, ) { final attributes = text.attributes; if (attributes == null) { return textSpan; } // try to refresh font here. if (attributes.fontFamily != null) { try { GoogleFonts.getFont(attributes.fontFamily!.parseFontFamilyName()); } catch (e) { // ignore } } // Inline Mentions (Page Reference, Date, Reminder, etc.) final mention = attributes[MentionBlockKeys.mention] as Map?; if (mention != null) { final type = mention[MentionBlockKeys.type]; return WidgetSpan( alignment: PlaceholderAlignment.middle, child: MentionBlock( key: ValueKey( switch (type) { MentionType.page => mention[MentionBlockKeys.pageId], MentionType.date || MentionType.reminder => mention[MentionBlockKeys.date], _ => MentionBlockKeys.mention, }, ), node: node, index: index, mention: mention, ), ); } // customize the inline math equation block final formula = attributes[InlineMathEquationKeys.formula] as String?; if (formula != null) { return WidgetSpan( alignment: PlaceholderAlignment.middle, child: InlineMathEquation( node: node, index: index, formula: formula, textStyle: style().textStyleConfiguration.text, ), ); } return defaultTextSpanDecoratorForAttribute( context, node, index, text, textSpan, ); } }