From 9c59e1487e5b49775338ea22e1e6e746b7efdbcf Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Thu, 21 Sep 2023 09:12:25 +0800 Subject: [PATCH] feat: customize text font (#3467) * feat: update UI in settings page * feat: customzing font in document page * fix: flutter analyze and format issues --- .../appearance_settings_test.dart | 3 +- .../util/mock/mock_openai_repository.dart | 11 +- .../board/presentation/board_page.dart | 6 +- .../document/presentation/editor_page.dart | 4 +- .../callout/callout_block_component.dart | 5 +- .../code_block/code_block_component.dart | 5 +- .../database_view_block_component.dart | 5 +- .../emoji_picker/src/config.dart | 40 ++--- .../src/default_emoji_picker_view.dart | 73 ++++---- .../emoji_picker/src/emoji_picker.dart | 74 +++++--- .../extensions/flowy_tint_extension.dart | 1 - .../font/customize_font_toolbar_item.dart | 43 +++++ .../math_equation_block_component.dart | 5 +- .../outline/outline_block_component.dart | 5 +- .../presentation/editor_plugins/plugins.dart | 1 + .../toggle/toggle_block_component.dart | 5 +- .../document/presentation/editor_style.dart | 10 ++ .../util/google_font_family_extension.dart | 7 + .../lib/workspace/application/appearance.dart | 4 - .../font_family_setting.dart | 170 +++++++++++------- .../theme_setting_entry_template.dart | 17 +- .../widgets/settings_appearance_view.dart | 22 +-- frontend/appflowy_flutter/pubspec.lock | 6 +- frontend/appflowy_flutter/pubspec.yaml | 2 +- .../resources/flowy_icons/16x/font_family.svg | 3 + frontend/resources/translations/en.json | 4 +- 26 files changed, 334 insertions(+), 197 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart create mode 100644 frontend/appflowy_flutter/lib/util/google_font_family_extension.dart create mode 100644 frontend/resources/flowy_icons/16x/font_family.svg diff --git a/frontend/appflowy_flutter/integration_test/appearance_settings_test.dart b/frontend/appflowy_flutter/integration_test/appearance_settings_test.dart index cbfe970f85..a9e3193ad2 100644 --- a/frontend/appflowy_flutter/integration_test/appearance_settings_test.dart +++ b/frontend/appflowy_flutter/integration_test/appearance_settings_test.dart @@ -80,7 +80,8 @@ void main() { await tester.openSettingsPage(SettingsPage.files); await tester.openSettingsPage(SettingsPage.appearance); - expect(find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily), + expect( + find.textContaining(DefaultAppearanceSettings.kDefaultFontFamily), findsOneWidget, ); }); diff --git a/frontend/appflowy_flutter/integration_test/util/mock/mock_openai_repository.dart b/frontend/appflowy_flutter/integration_test/util/mock/mock_openai_repository.dart index ec2e3149d9..66d63b248b 100644 --- a/frontend/appflowy_flutter/integration_test/util/mock/mock_openai_repository.dart +++ b/frontend/appflowy_flutter/integration_test/util/mock/mock_openai_repository.dart @@ -12,8 +12,11 @@ class MyMockClient extends Mock implements http.Client { final requestType = request.method; final requestUri = request.url; - if (requestType == 'POST' && requestUri == OpenAIRequestType.textCompletion.uri) { - final responseHeaders = {'content-type': 'text/event-stream'}; + if (requestType == 'POST' && + requestUri == OpenAIRequestType.textCompletion.uri) { + final responseHeaders = { + 'content-type': 'text/event-stream' + }; final responseBody = Stream.fromIterable([ utf8.encode( '{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}', @@ -51,7 +54,9 @@ class MockOpenAIRepository extends HttpOpenAIRepository { var previousSyntax = ''; if (response.statusCode == 200) { - await for (final chunk in response.stream.transform(const Utf8Decoder()).transform(const LineSplitter())) { + await for (final chunk in response.stream + .transform(const Utf8Decoder()) + .transform(const LineSplitter())) { await onStart(); final data = chunk.trim().split('data: '); if (data[0] != '[DONE]') { diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart index 8b8bf681db..16cdcfe149 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart @@ -382,8 +382,10 @@ Widget? _buildHeaderIcon(GroupData customData) { case FieldType.Checkbox: final group = customData.asCheckboxGroup()!; if (group.isCheck) { - widget = - const FlowySvg(FlowySvgs.check_filled_s, blendMode: BlendMode.dst,); + widget = const FlowySvg( + FlowySvgs.check_filled_s, + blendMode: BlendMode.dst, + ); } else { widget = const FlowySvg(FlowySvgs.uncheck_s); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 44b590c11d..b3b6e1350e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -75,8 +75,8 @@ class _AppFlowyEditorPageState extends State { alignToolbarItem, buildTextColorItem(), buildHighlightColorItem(), - // TODO: enable it in version 0.3.3 - // ...textDirectionItems, + customizeFontToolbarItem, + ...textDirectionItems, ]; late final List slashMenuItems; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart index 48b6325017..f742bde38a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart @@ -61,13 +61,10 @@ SelectionMenuItem calloutItem = SelectionMenuItem.node( // building the callout block widget class CalloutBlockComponentBuilder extends BlockComponentBuilder { CalloutBlockComponentBuilder({ - this.configuration = const BlockComponentConfiguration(), + super.configuration, required this.defaultColor, }); - @override - final BlockComponentConfiguration configuration; - final Color defaultColor; @override diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart index dc6611a35d..00d33775fb 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_component.dart @@ -51,13 +51,10 @@ SelectionMenuItem codeBlockItem = SelectionMenuItem.node( class CodeBlockComponentBuilder extends BlockComponentBuilder { CodeBlockComponentBuilder({ - this.configuration = const BlockComponentConfiguration(), + super.configuration, this.padding = const EdgeInsets.all(0), }); - @override - final BlockComponentConfiguration configuration; - final EdgeInsets padding; @override diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart index 5aabc84040..b7ad4528a5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart @@ -17,12 +17,9 @@ class DatabaseBlockKeys { class DatabaseViewBlockComponentBuilder extends BlockComponentBuilder { DatabaseViewBlockComponentBuilder({ - this.configuration = const BlockComponentConfiguration(), + super.configuration, }); - @override - final BlockComponentConfiguration configuration; - @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { final node = blockComponentContext.node; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/config.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/config.dart index cbe6554df0..e8ddc7f883 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/config.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/config.dart @@ -8,26 +8,26 @@ import 'emoji_picker.dart'; /// Config for customizations class Config { /// Constructor - const Config( - {this.columns = 7, - this.emojiSizeMax = 32.0, - this.verticalSpacing = 0, - this.horizontalSpacing = 0, - this.initCategory = Category.RECENT, - this.bgColor = const Color(0xFFEBEFF2), - this.indicatorColor = Colors.blue, - this.iconColor = Colors.grey, - this.iconColorSelected = Colors.blue, - this.progressIndicatorColor = Colors.blue, - this.backspaceColor = Colors.blue, - this.showRecentsTab = true, - this.recentsLimit = 28, - this.noRecentsText = 'No Recents', - this.noRecentsStyle = - const TextStyle(fontSize: 20, color: Colors.black26), - this.tabIndicatorAnimDuration = kTabScrollDuration, - this.categoryIcons = const CategoryIcons(), - this.buttonMode = ButtonMode.MATERIAL,}); + const Config({ + this.columns = 7, + this.emojiSizeMax = 32.0, + this.verticalSpacing = 0, + this.horizontalSpacing = 0, + this.initCategory = Category.RECENT, + this.bgColor = const Color(0xFFEBEFF2), + this.indicatorColor = Colors.blue, + this.iconColor = Colors.grey, + this.iconColorSelected = Colors.blue, + this.progressIndicatorColor = Colors.blue, + this.backspaceColor = Colors.blue, + this.showRecentsTab = true, + this.recentsLimit = 28, + this.noRecentsText = 'No Recents', + this.noRecentsStyle = const TextStyle(fontSize: 20, color: Colors.black26), + this.tabIndicatorAnimDuration = kTabScrollDuration, + this.categoryIcons = const CategoryIcons(), + this.buttonMode = ButtonMode.MATERIAL, + }); /// Number of emojis per row final int columns; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/default_emoji_picker_view.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/default_emoji_picker_view.dart index 421331d464..e0b8e2188e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/default_emoji_picker_view.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/default_emoji_picker_view.dart @@ -27,14 +27,16 @@ class DefaultEmojiPickerViewState extends State @override void initState() { var initCategory = widget.state.categoryEmoji.indexWhere( - (element) => element.category == widget.config.initCategory,); + (element) => element.category == widget.config.initCategory, + ); if (initCategory == -1) { initCategory = 0; } _tabController = TabController( - initialIndex: initCategory, - length: widget.state.categoryEmoji.length, - vsync: this,); + initialIndex: initCategory, + length: widget.state.categoryEmoji.length, + vsync: this, + ); _pageController = PageController(initialPage: initCategory); _emojiFocusNode.requestFocus(); @@ -72,14 +74,15 @@ class DefaultEmojiPickerViewState extends State return Material( type: MaterialType.transparency, child: IconButton( - padding: const EdgeInsets.only(bottom: 2), - icon: Icon( - Icons.backspace, - color: widget.config.backspaceColor, - ), - onPressed: () { - widget.state.onBackspacePressed!(); - },), + padding: const EdgeInsets.only(bottom: 2), + icon: Icon( + Icons.backspace, + color: widget.config.backspaceColor, + ), + onPressed: () { + widget.state.onBackspacePressed!(); + }, + ), ); } return Container(); @@ -160,8 +163,12 @@ class DefaultEmojiPickerViewState extends State : widget.state.categoryEmoji .asMap() .entries - .map((item) => _buildCategory( - item.value.category, emojiSize,),) + .map( + (item) => _buildCategory( + item.value.category, + emojiSize, + ), + ) .toList(), ), ), @@ -206,8 +213,10 @@ class DefaultEmojiPickerViewState extends State ); } - Widget _buildButtonWidget( - {required VoidCallback onPressed, required Widget child,}) { + Widget _buildButtonWidget({ + required VoidCallback onPressed, + required Widget child, + }) { if (widget.config.buttonMode == ButtonMode.MATERIAL) { return InkWell( onTap: onPressed, @@ -266,29 +275,31 @@ class DefaultEmojiPickerViewState extends State Emoji emoji, ) { return _buildButtonWidget( - onPressed: () { - widget.state.onEmojiSelected(categoryEmoji.category, emoji); - }, - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - emoji.emoji, - textScaleFactor: 1.0, - style: TextStyle( - fontSize: emojiSize, - backgroundColor: Colors.transparent, - ), + onPressed: () { + widget.state.onEmojiSelected(categoryEmoji.category, emoji); + }, + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + emoji.emoji, + textScaleFactor: 1.0, + style: TextStyle( + fontSize: emojiSize, + backgroundColor: Colors.transparent, ), - ),); + ), + ), + ); } Widget _buildNoRecent() { return Center( - child: FlowyText.regular( + child: FlowyText.regular( widget.config.noRecentsText, color: Theme.of(context).colorScheme.tertiary.withAlpha(77), fontSize: widget.config.noRecentsStyle.fontSize, textAlign: TextAlign.center, - ),); + ), + ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/emoji_picker.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/emoji_picker.dart index ca45d4d155..0a8793a5d5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/emoji_picker.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/emoji_picker/src/emoji_picker.dart @@ -190,30 +190,49 @@ class EmojiPickerState extends State { categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap)); } categoryEmoji.addAll([ - CategoryEmoji(Category.SMILEYS, - await _getAvailableEmojis(emoji_list.smileys, title: 'smileys'),), - CategoryEmoji(Category.ANIMALS, - await _getAvailableEmojis(emoji_list.animals, title: 'animals'),), - CategoryEmoji(Category.FOODS, - await _getAvailableEmojis(emoji_list.foods, title: 'foods'),), CategoryEmoji( - Category.ACTIVITIES, - await _getAvailableEmojis(emoji_list.activities, - title: 'activities',),), - CategoryEmoji(Category.TRAVEL, - await _getAvailableEmojis(emoji_list.travel, title: 'travel'),), - CategoryEmoji(Category.OBJECTS, - await _getAvailableEmojis(emoji_list.objects, title: 'objects'),), - CategoryEmoji(Category.SYMBOLS, - await _getAvailableEmojis(emoji_list.symbols, title: 'symbols'),), - CategoryEmoji(Category.FLAGS, - await _getAvailableEmojis(emoji_list.flags, title: 'flags'),) + Category.SMILEYS, + await _getAvailableEmojis(emoji_list.smileys, title: 'smileys'), + ), + CategoryEmoji( + Category.ANIMALS, + await _getAvailableEmojis(emoji_list.animals, title: 'animals'), + ), + CategoryEmoji( + Category.FOODS, + await _getAvailableEmojis(emoji_list.foods, title: 'foods'), + ), + CategoryEmoji( + Category.ACTIVITIES, + await _getAvailableEmojis( + emoji_list.activities, + title: 'activities', + ), + ), + CategoryEmoji( + Category.TRAVEL, + await _getAvailableEmojis(emoji_list.travel, title: 'travel'), + ), + CategoryEmoji( + Category.OBJECTS, + await _getAvailableEmojis(emoji_list.objects, title: 'objects'), + ), + CategoryEmoji( + Category.SYMBOLS, + await _getAvailableEmojis(emoji_list.symbols, title: 'symbols'), + ), + CategoryEmoji( + Category.FLAGS, + await _getAvailableEmojis(emoji_list.flags, title: 'flags'), + ) ]); } // Get available emoji for given category title - Future> _getAvailableEmojis(Map map, - {required String title,}) async { + Future> _getAvailableEmojis( + Map map, { + required String title, + }) async { Map? newMap; // Get Emojis cached locally if available @@ -236,15 +255,18 @@ class EmojiPickerState extends State { // Check if emoji is available on current platform Future?> _getPlatformAvailableEmoji( - Map emoji,) async { + Map emoji, + ) async { if (Platform.isAndroid) { Map? filtered = {}; const delimiter = '|'; try { final entries = emoji.values.join(delimiter); final keys = emoji.keys.join(delimiter); - final result = (await platform.invokeMethod('checkAvailability', - {'emojiKeys': keys, 'emojiEntries': entries},)) as String; + final result = (await platform.invokeMethod( + 'checkAvailability', + {'emojiKeys': keys, 'emojiEntries': entries}, + )) as String; final resultKeys = result.split(delimiter); for (var i = 0; i < resultKeys.length; i++) { filtered[resultKeys[i]] = emoji[resultKeys[i]]!; @@ -272,7 +294,9 @@ class EmojiPickerState extends State { // Stores filtered emoji locally for faster access next time Future _cacheFilteredEmojis( - String title, Map emojis,) async { + String title, + Map emojis, + ) async { final prefs = await SharedPreferences.getInstance(); final emojiJson = jsonEncode(emojis); prefs.setString(title, emojiJson); @@ -305,7 +329,9 @@ class EmojiPickerState extends State { recentEmoji.sort((a, b) => b.counter - a.counter); // Limit entries to recentsLimit recentEmoji = recentEmoji.sublist( - 0, min(widget.config.recentsLimit, recentEmoji.length),); + 0, + min(widget.config.recentsLimit, recentEmoji.length), + ); // save locally prefs.setString('recent', jsonEncode(recentEmoji)); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/extensions/flowy_tint_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/extensions/flowy_tint_extension.dart index 522d9df1a8..65ade628f1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/extensions/flowy_tint_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/extensions/flowy_tint_extension.dart @@ -1,4 +1,3 @@ - import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart new file mode 100644 index 0000000000..bf89adbd67 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart @@ -0,0 +1,43 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flutter/material.dart'; + +final customizeFontToolbarItem = ToolbarItem( + id: 'editor.font', + group: 4, + isActive: onlyShowInTextType, + builder: (context, editorState, highlightColor) { + final selection = editorState.selection!; + final popoverController = PopoverController(); + return MouseRegion( + cursor: SystemMouseCursors.click, + child: FontFamilyDropDown( + currentFontFamily: '', + popoverController: popoverController, + onOpen: () => keepEditorFocusNotifier.value += 1, + onClose: () => keepEditorFocusNotifier.value -= 1, + onFontFamilyChanged: (fontFamily) async { + await popoverController.close(); + try { + await editorState.formatDelta(selection, { + AppFlowyRichTextKeys.fontFamily: fontFamily, + }); + } catch (e) { + Log.error('Failed to set font family: $e'); + } + }, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 4.0), + child: FlowySvg( + FlowySvgs.font_family_s, + size: Size.square(16.0), + color: Colors.white, + ), + ), + ), + ); + }, +); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart index a729339779..41bc8b5bbc 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart @@ -54,12 +54,9 @@ SelectionMenuItem mathEquationItem = SelectionMenuItem.node( class MathEquationBlockComponentBuilder extends BlockComponentBuilder { MathEquationBlockComponentBuilder({ - this.configuration = const BlockComponentConfiguration(), + super.configuration, }); - @override - final BlockComponentConfiguration configuration; - @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { final node = blockComponentContext.node; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart index 738b632041..46389d0a06 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart @@ -32,12 +32,9 @@ Node outlineBlockNode() { class OutlineBlockComponentBuilder extends BlockComponentBuilder { OutlineBlockComponentBuilder({ - this.configuration = const BlockComponentConfiguration(), + super.configuration, }); - @override - final BlockComponentConfiguration configuration; - @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { final node = blockComponentContext.node; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart index c2cc76748e..c0b9c98e52 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart @@ -14,6 +14,7 @@ export 'database/inline_database_menu_item.dart'; export 'database/referenced_database_menu_item.dart'; export 'emoji_picker/emoji_menu_item.dart'; export 'extensions/flowy_tint_extension.dart'; +export 'font/customize_font_toolbar_item.dart'; export 'header/cover_editor_bloc.dart'; export 'header/custom_cover_picker.dart'; export 'header/document_header_node_widget.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart index ea30fba04a..6569a5a8d7 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart @@ -55,13 +55,10 @@ SelectionMenuItem toggleListBlockItem = SelectionMenuItem.node( class ToggleListBlockComponentBuilder extends BlockComponentBuilder { ToggleListBlockComponentBuilder({ - this.configuration = const BlockComponentConfiguration(), + super.configuration, this.padding = const EdgeInsets.all(0), }); - @override - final BlockComponentConfiguration configuration; - final EdgeInsets padding; @override diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart index 1b9726656a..1f37d666b5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart @@ -2,6 +2,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_mat import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.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/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'; @@ -193,6 +194,15 @@ class EditorStyleCustomizer { return textSpan; } + // try to refresh font here. + if (attributes.fontFamily != null) { + try { + GoogleFonts.getFont(attributes.fontFamily!.parseFontFamilyName()); + } catch (e) { + // ignore + } + } + // customize the inline mention block, like inline page final mention = attributes[MentionBlockKeys.mention] as Map?; if (mention != null) { diff --git a/frontend/appflowy_flutter/lib/util/google_font_family_extension.dart b/frontend/appflowy_flutter/lib/util/google_font_family_extension.dart new file mode 100644 index 0000000000..d20a42fc87 --- /dev/null +++ b/frontend/appflowy_flutter/lib/util/google_font_family_extension.dart @@ -0,0 +1,7 @@ +extension GoogleFontsParser on String { + String parseFontFamilyName() { + final camelCase = RegExp('(?<=[a-z])[A-Z]'); + return replaceAll('_regular', '') + .replaceAllMapped(camelCase, (m) => ' ${m.group(0)}'); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/application/appearance.dart b/frontend/appflowy_flutter/lib/workspace/application/appearance.dart index e92c633651..9a4ece33cd 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/appearance.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/appearance.dart @@ -300,10 +300,6 @@ class AppearanceSettingsState with _$AppearanceSettingsState { ThemeData get lightTheme => _getThemeData(Brightness.light); ThemeData get darkTheme => _getThemeData(Brightness.dark); - // only support LTR layout in version 0.3.2, enable it in version 0.3.3 - LayoutDirectionPB get layoutDirectionPB => LayoutDirectionPB.LTRLayout; - TextDirectionPB get textDirectionPB => TextDirectionPB.LTR; - ThemeData _getThemeData(Brightness brightness) { // Poppins and SF Mono are not well supported in some languages, so use the // built-in font for the following languages. diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart index 950efd118e..7759ef01cf 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart @@ -1,8 +1,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; +import 'package:appflowy/util/google_font_family_extension.dart'; import 'package:appflowy/workspace/application/appearance.dart'; import 'package:appflowy/workspace/application/appearance_defaults.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -29,9 +31,6 @@ class ThemeFontFamilySetting extends StatefulWidget { } class _ThemeFontFamilySettingState extends State { - final List availableFonts = GoogleFonts.asMap().keys.toList(); - final ValueNotifier query = ValueNotifier(''); - @override Widget build(BuildContext context) { return ThemeSettingEntryTemplateWidget( @@ -44,61 +43,101 @@ class _ThemeFontFamilySettingState extends State { .syncFontFamily(DefaultAppearanceSettings.kDefaultFontFamily); }, trailing: [ - ThemeValueDropDown( - popoverKey: ThemeFontFamilySetting.popoverKey, - currentValue: parseFontFamilyName(widget.currentFontFamily), - onClose: () { - query.value = ''; - }, - popupBuilder: (_) => CustomScrollView( - shrinkWrap: true, - slivers: [ - SliverPadding( - padding: const EdgeInsets.only(right: 8), - sliver: SliverToBoxAdapter( - child: FlowyTextField( - key: ThemeFontFamilySetting.textFieldKey, - hintText: - LocaleKeys.settings_appearance_fontFamily_search.tr(), - autoFocus: false, - debounceDuration: const Duration(milliseconds: 300), - onChanged: (value) { - query.value = value; - }, - ), + FontFamilyDropDown( + currentFontFamily: widget.currentFontFamily, + ) + ], + ); + } +} + +class FontFamilyDropDown extends StatefulWidget { + const FontFamilyDropDown({ + super.key, + required this.currentFontFamily, + this.onOpen, + this.onClose, + this.onFontFamilyChanged, + this.child, + this.popoverController, + }); + + final String currentFontFamily; + final VoidCallback? onOpen; + final VoidCallback? onClose; + final void Function(String fontFamily)? onFontFamilyChanged; + final Widget? child; + final PopoverController? popoverController; + + @override + State createState() => _FontFamilyDropDownState(); +} + +class _FontFamilyDropDownState extends State { + final List availableFonts = GoogleFonts.asMap().keys.toList(); + final ValueNotifier query = ValueNotifier(''); + + @override + Widget build(BuildContext context) { + return ThemeValueDropDown( + popoverKey: ThemeFontFamilySetting.popoverKey, + popoverController: widget.popoverController, + currentValue: parseFontFamilyName(widget.currentFontFamily), + onClose: () { + query.value = ''; + widget.onClose?.call(); + }, + child: widget.child, + popupBuilder: (_) { + widget.onOpen?.call(); + return CustomScrollView( + shrinkWrap: true, + slivers: [ + SliverPadding( + padding: const EdgeInsets.only(right: 8), + sliver: SliverToBoxAdapter( + child: FlowyTextField( + key: ThemeFontFamilySetting.textFieldKey, + hintText: + LocaleKeys.settings_appearance_fontFamily_search.tr(), + autoFocus: false, + debounceDuration: const Duration(milliseconds: 300), + onChanged: (value) { + query.value = value; + }, ), ), - const SliverToBoxAdapter( - child: SizedBox(height: 4), - ), - ValueListenableBuilder( - valueListenable: query, - builder: (context, value, child) { - var displayed = availableFonts; - if (value.isNotEmpty) { - displayed = availableFonts - .where( - (font) => font - .toLowerCase() - .contains(value.toLowerCase().toString()), - ) - .sorted((a, b) => levenshtein(a, b)) - .toList(); - } - return SliverFixedExtentList.builder( - itemBuilder: (context, index) => _fontFamilyItemButton( - context, - GoogleFonts.getFont(displayed[index]), - ), - itemCount: displayed.length, - itemExtent: 32, - ); - }, - ), - ], - ), - ), - ], + ), + const SliverToBoxAdapter( + child: SizedBox(height: 4), + ), + ValueListenableBuilder( + valueListenable: query, + builder: (context, value, child) { + var displayed = availableFonts; + if (value.isNotEmpty) { + displayed = availableFonts + .where( + (font) => font + .toLowerCase() + .contains(value.toLowerCase().toString()), + ) + .sorted((a, b) => levenshtein(a, b)) + .toList(); + } + return SliverFixedExtentList.builder( + itemBuilder: (context, index) => _fontFamilyItemButton( + context, + GoogleFonts.getFont(displayed[index]), + ), + itemCount: displayed.length, + itemExtent: 32, + ); + }, + ), + ], + ); + }, ); } @@ -128,14 +167,17 @@ class _ThemeFontFamilySettingState extends State { ) : null, onTap: () { - if (parseFontFamilyName(widget.currentFontFamily) != - buttonFontFamily) { - context - .read() - .setFontFamily(parseFontFamilyName(style.fontFamily!)); - context - .read() - .syncFontFamily(parseFontFamilyName(style.fontFamily!)); + if (widget.onFontFamilyChanged != null) { + widget.onFontFamilyChanged!(style.fontFamily!); + } else { + final fontFamily = style.fontFamily!.parseFontFamilyName(); + if (parseFontFamilyName(widget.currentFontFamily) != + buttonFontFamily) { + context.read().setFontFamily(fontFamily); + context + .read() + .syncFontFamily(fontFamily); + } } }, ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart index 0d65c359d8..368f551d1d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart @@ -31,6 +31,7 @@ class ThemeSettingEntryTemplateWidget extends StatelessWidget { children: [ FlowyText.medium( label, + fontSize: 14, overflow: TextOverflow.ellipsis, ), if (hint != null) @@ -71,12 +72,16 @@ class ThemeValueDropDown extends StatefulWidget { required this.popupBuilder, this.popoverKey, this.onClose, + this.child, + this.popoverController, }); final String currentValue; final Key? popoverKey; final Widget Function(BuildContext) popupBuilder; final void Function()? onClose; + final Widget? child; + final PopoverController? popoverController; @override State createState() => _ThemeValueDropDownState(); @@ -87,6 +92,7 @@ class _ThemeValueDropDownState extends State { Widget build(BuildContext context) { return AppFlowyPopover( key: widget.popoverKey, + controller: widget.popoverController, direction: PopoverDirection.bottomWithRightAligned, popupBuilder: widget.popupBuilder, constraints: const BoxConstraints( @@ -95,11 +101,12 @@ class _ThemeValueDropDownState extends State { maxHeight: 400, ), onClose: widget.onClose, - child: FlowyTextButton( - widget.currentValue, - fontColor: Theme.of(context).colorScheme.onBackground, - fillColor: Colors.transparent, - ), + child: widget.child ?? + FlowyTextButton( + widget.currentValue, + fontColor: Theme.of(context).colorScheme.onBackground, + fillColor: Colors.transparent, + ), ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart index 498bf56362..20e03115c6 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart @@ -19,23 +19,25 @@ class SettingsAppearanceView extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - BrightnessSetting( - currentThemeMode: state.themeMode, - ), ColorSchemeSetting( currentTheme: state.appTheme.themeName, bloc: context.read(), ), + BrightnessSetting( + currentThemeMode: state.themeMode, + ), + const Divider(), ThemeFontFamilySetting( currentFontFamily: state.font, ), - // TODO: enablt them in version 0.3.3 - // LayoutDirectionSetting( - // currentLayoutDirection: state.layoutDirection, - // ), - // TextDirectionSetting( - // currentTextDirection: state.textDirection, - // ), + const Divider(), + LayoutDirectionSetting( + currentLayoutDirection: state.layoutDirection, + ), + TextDirectionSetting( + currentTextDirection: state.textDirection, + ), + const Divider(), CreateFileSettings(), ], ); diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 3dfd97f53f..753244119d 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -54,11 +54,11 @@ packages: dependency: "direct main" description: path: "." - ref: "4a87ec4" - resolved-ref: "4a87ec4bd440344b8f51dd61ab84e2c68d4196d2" + ref: a0ff609 + resolved-ref: a0ff609cb1ac53e5d167489f43452074860dd80e url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git - version: "1.3.0" + version: "1.4.0" appflowy_popover: dependency: "direct main" description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index bff129fd6b..8ee6eae68a 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -47,7 +47,7 @@ dependencies: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: 4a87ec4 + ref: a0ff609 appflowy_popover: path: packages/appflowy_popover diff --git a/frontend/resources/flowy_icons/16x/font_family.svg b/frontend/resources/flowy_icons/16x/font_family.svg new file mode 100644 index 0000000000..6eb7e012e3 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/font_family.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index e4fa3d5d1b..0e525bc45f 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -265,13 +265,13 @@ }, "layoutDirection": { "label": "Layout Direction", - "hint": "To start aligning elements from left or right of the screen.", + "hint": "Control the flow of content on your screen, from left to right or right to left.", "ltr": "LTR", "rtl": "RTL" }, "textDirection": { "label": "Default text direction", - "hint": "Default text direction when the text direction is not set on the element.", + "hint": "Specify whether text should start from left or right as the default.", "ltr": "LTR", "rtl": "RTL", "auto": "AUTO",