diff --git a/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart b/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart index 7f517678a9..a29f8df49d 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_test_runner.dart @@ -5,6 +5,7 @@ import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test; import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test; import 'document_create_and_delete_test.dart' as document_create_and_delete_test; +import 'document_text_direction_test.dart' as document_text_direction_test; import 'document_with_cover_image_test.dart' as document_with_cover_image_test; import 'document_with_database_test.dart' as document_with_database_test; import 'document_with_inline_math_equation_test.dart' @@ -29,4 +30,5 @@ void startTesting() { document_copy_and_paste_test.main(); document_codeblock_paste_test.main(); document_alignment_test.main(); + document_text_direction_test.main(); } diff --git a/frontend/appflowy_flutter/integration_test/document/document_text_direction_test.dart b/frontend/appflowy_flutter/integration_test/document/document_text_direction_test.dart new file mode 100644 index 0000000000..5499143881 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/document/document_text_direction_test.dart @@ -0,0 +1,58 @@ +import 'package:appflowy/workspace/application/appearance.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../util/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('text direction', () { + testWidgets( + '''no text direction items will be displayed in the default/LTR mode,and three text direction items will be displayed in the RTL mode.''', + (tester) async { + // combine the two tests into one to avoid the time-consuming process of initializing the app + await tester.initializeAppFlowy(); + await tester.tapGoButton(); + + final selection = Selection.single( + path: [0], + startOffset: 0, + endOffset: 1, + ); + // click the first line of the readme + await tester.editor.tapLineOfEditorAt(0); + await tester.editor.updateSelection(selection); + await tester.pumpAndSettle(); + + // because this icons are defined in the appflowy_editor package, we can't fetch the icons by SVG data. [textDirectionItems] + final textDirectionIconNames = [ + 'toolbar/text_direction_auto', + 'toolbar/text_direction_left', + 'toolbar/text_direction_right', + ]; + // no text direction items in default/LTR mode + var button = find.byWidgetPredicate( + (widget) => + widget is SVGIconItemWidget && + textDirectionIconNames.contains(widget.iconName), + ); + expect(button, findsNothing); + + // switch to the RTL mode + await tester.switchLayoutDirectionMode(LayoutDirection.rtlLayout); + + await tester.editor.tapLineOfEditorAt(0); + await tester.editor.updateSelection(selection); + await tester.pumpAndSettle(); + + button = find.byWidgetPredicate( + (widget) => + widget is SVGIconItemWidget && + textDirectionIconNames.contains(widget.iconName), + ); + expect(button, findsNWidgets(3)); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/util/settings.dart b/frontend/appflowy_flutter/integration_test/util/settings.dart index 1c6e4e919f..1fd885de0a 100644 --- a/frontend/appflowy_flutter/integration_test/util/settings.dart +++ b/frontend/appflowy_flutter/integration_test/util/settings.dart @@ -1,9 +1,11 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/application/appearance.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'base.dart'; @@ -72,4 +74,35 @@ extension AppFlowySettings on WidgetTester { await testTextInput.receiveAction(TextInputAction.done); await pumpAndSettle(); } + + // go to settings page and switch the layout direction + Future switchLayoutDirectionMode( + LayoutDirection layoutDirection, + ) async { + await openSettings(); + await openSettingsPage(SettingsPage.appearance); + + final button = find.byKey(const ValueKey('layout_direction_option_button')); + expect(button, findsOneWidget); + await tapButton(button); + + switch (layoutDirection) { + case LayoutDirection.ltrLayout: + final ltrButton = find.text( + LocaleKeys.settings_appearance_layoutDirection_ltr.tr(), + ); + await tapButton(ltrButton); + break; + case LayoutDirection.rtlLayout: + final rtlButton = find.text( + LocaleKeys.settings_appearance_layoutDirection_rtl.tr(), + ); + await tapButton(rtlButton); + break; + } + + // tap anywhere to close the settings page + await tapAt(Offset.zero); + await pumpAndSettle(); + } } 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 b3b6e1350e..a49263a8bf 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -76,7 +76,6 @@ class _AppFlowyEditorPageState extends State { buildTextColorItem(), buildHighlightColorItem(), customizeFontToolbarItem, - ...textDirectionItems, ]; late final List slashMenuItems; @@ -137,6 +136,9 @@ class _AppFlowyEditorPageState extends State { convertibleBlockTypes.add(ToggleListBlockKeys.type); slashMenuItems = _customSlashMenuItems(); effectiveScrollController = widget.scrollController ?? ScrollController(); + + // keep the previous font style when typing new text. + AppFlowyRichTextKeys.supportSliced.add(AppFlowyRichTextKeys.fontFamily); } @override @@ -153,6 +155,13 @@ class _AppFlowyEditorPageState extends State { final (bool autoFocus, Selection? selection) = _computeAutoFocusParameters(); + final isRTL = + context.read().state.layoutDirection == + LayoutDirection.rtlLayout; + final layoutDirection = isRTL ? TextDirection.rtl : TextDirection.ltr; + + _setRTLToolbarItems(isRTL); + final editorScrollController = EditorScrollController( editorState: widget.editorState, shrinkWrap: widget.shrinkWrap, @@ -178,12 +187,6 @@ class _AppFlowyEditorPageState extends State { footer: const VSpace(200), ); - final layoutDirection = - context.read().state.layoutDirection == - LayoutDirection.rtlLayout - ? TextDirection.rtl - : TextDirection.ltr; - return Center( child: FloatingToolbar( style: styleCustomizer.floatingToolbarStyleBuilder(), @@ -463,4 +466,16 @@ class _AppFlowyEditorPageState extends State { customizeShortcuts, ); } + + void _setRTLToolbarItems(bool isRTL) { + final textDirectionItemIds = textDirectionItems.map((e) => e.id); + // clear all the text direction items + toolbarItems.removeWhere( + (item) => textDirectionItemIds.contains(item.id), + ); + // only show the rtl item when the layout direction is ltr. + if (isRTL) { + toolbarItems.addAll(textDirectionItems); + } + } } 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 index bf89adbd67..fec3156f02 100644 --- 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 @@ -16,9 +16,11 @@ final customizeFontToolbarItem = ToolbarItem( cursor: SystemMouseCursors.click, child: FontFamilyDropDown( currentFontFamily: '', + offset: const Offset(0, 12), popoverController: popoverController, onOpen: () => keepEditorFocusNotifier.value += 1, onClose: () => keepEditorFocusNotifier.value -= 1, + showResetButton: true, onFontFamilyChanged: (fontFamily) async { await popoverController.close(); try { @@ -29,6 +31,9 @@ final customizeFontToolbarItem = ToolbarItem( Log.error('Failed to set font family: $e'); } }, + onResetFont: () async => await editorState.formatDelta(selection, { + AppFlowyRichTextKeys.fontFamily: null, + }), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 4.0), child: FlowySvg( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart index 36017493f8..d96e5e338f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_appearance/direction_setting.dart @@ -25,6 +25,7 @@ class LayoutDirectionSetting extends StatelessWidget { hint: LocaleKeys.settings_appearance_layoutDirection_hint.tr(), trailing: [ ThemeValueDropDown( + key: const ValueKey('layout_direction_option_button'), currentValue: _layoutDirectionLabelText(currentLayoutDirection), popupBuilder: (context) => Column( mainAxisSize: MainAxisSize.min, 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 25bffbcf26..5a981f5d99 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 @@ -60,6 +60,9 @@ class FontFamilyDropDown extends StatefulWidget { this.onFontFamilyChanged, this.child, this.popoverController, + this.offset, + this.showResetButton = false, + this.onResetFont, }); final String currentFontFamily; @@ -68,6 +71,9 @@ class FontFamilyDropDown extends StatefulWidget { final void Function(String fontFamily)? onFontFamilyChanged; final Widget? child; final PopoverController? popoverController; + final Offset? offset; + final bool showResetButton; + final VoidCallback? onResetFont; @override State createState() => _FontFamilyDropDownState(); @@ -87,12 +93,20 @@ class _FontFamilyDropDownState extends State { query.value = ''; widget.onClose?.call(); }, + offset: widget.offset, child: widget.child, popupBuilder: (_) { widget.onOpen?.call(); return CustomScrollView( shrinkWrap: true, slivers: [ + if (widget.showResetButton) + SliverPersistentHeader( + delegate: _ResetFontButton( + onPressed: widget.onResetFont, + ), + pinned: true, + ), SliverPadding( padding: const EdgeInsets.only(right: 8), sliver: SliverToBoxAdapter( @@ -188,3 +202,36 @@ class _FontFamilyDropDownState extends State { ); } } + +class _ResetFontButton extends SliverPersistentHeaderDelegate { + _ResetFontButton({ + this.onPressed, + }); + + final VoidCallback? onPressed; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return Padding( + padding: const EdgeInsets.only(right: 8, bottom: 8.0), + child: FlowyTextButton( + LocaleKeys.document_toolbar_resetToDefaultFont.tr(), + onPressed: onPressed, + ), + ); + } + + @override + double get maxExtent => 35; + + @override + double get minExtent => 35; + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) => + true; +} 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 ba81b8712b..03481a6a95 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 @@ -74,6 +74,7 @@ class ThemeValueDropDown extends StatefulWidget { this.onClose, this.child, this.popoverController, + this.offset, }); final String currentValue; @@ -82,6 +83,7 @@ class ThemeValueDropDown extends StatefulWidget { final void Function()? onClose; final Widget? child; final PopoverController? popoverController; + final Offset? offset; @override State createState() => _ThemeValueDropDownState(); @@ -93,13 +95,14 @@ class _ThemeValueDropDownState extends State { return AppFlowyPopover( key: widget.popoverKey, controller: widget.popoverController, - direction: PopoverDirection.bottomWithRightAligned, + direction: PopoverDirection.bottomWithCenterAligned, popupBuilder: widget.popupBuilder, constraints: const BoxConstraints( minWidth: 80, maxWidth: 160, maxHeight: 400, ), + offset: widget.offset, onClose: widget.onClose, child: widget.child ?? FlowyTextButton( diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 753244119d..90c8a3d81a 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -54,11 +54,11 @@ packages: dependency: "direct main" description: path: "." - ref: a0ff609 - resolved-ref: a0ff609cb1ac53e5d167489f43452074860dd80e + ref: "8e618465258b3de0ce5253c4fa97bacb24884e8c" + resolved-ref: "8e618465258b3de0ce5253c4fa97bacb24884e8c" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git - version: "1.4.0" + version: "1.4.1" appflowy_popover: dependency: "direct main" description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 8ee6eae68a..0bf5680026 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: a0ff609 + ref: 8e618465258b3de0ce5253c4fa97bacb24884e8c appflowy_popover: path: packages/appflowy_popover diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 610a35f6d9..eb3a740dc7 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -647,6 +647,9 @@ "label": "Link to page", "tooltip": "Click to open page" } + }, + "toolbar": { + "resetToDefaultFont": "Reset to default" } }, "board": {