diff --git a/example/lib/pages/desktop_editor.dart b/example/lib/pages/desktop_editor.dart index eb279188f..24d3d6776 100644 --- a/example/lib/pages/desktop_editor.dart +++ b/example/lib/pages/desktop_editor.dart @@ -66,6 +66,7 @@ class _DesktopEditorState extends State { ...alignmentItems, ], editorState: editorState, + textDirection: widget.textDirection, editorScrollController: editorScrollController, child: Directionality( textDirection: widget.textDirection, diff --git a/lib/src/editor/toolbar/desktop/floating_toolbar.dart b/lib/src/editor/toolbar/desktop/floating_toolbar.dart index c895696f0..04a84824a 100644 --- a/lib/src/editor/toolbar/desktop/floating_toolbar.dart +++ b/lib/src/editor/toolbar/desktop/floating_toolbar.dart @@ -22,6 +22,7 @@ class FloatingToolbar extends StatefulWidget { required this.items, required this.editorState, required this.editorScrollController, + required this.textDirection, required this.child, this.style = const FloatingToolbarStyle(), }); @@ -29,6 +30,7 @@ class FloatingToolbar extends StatefulWidget { final List items; final EditorState editorState; final EditorScrollController editorScrollController; + final TextDirection? textDirection; final Widget child; final FloatingToolbarStyle style; @@ -172,6 +174,7 @@ class _FloatingToolbarState extends State editorState: editorState, backgroundColor: widget.style.backgroundColor, toolbarActiveColor: widget.style.toolbarActiveColor, + textDirection: widget.textDirection ?? Directionality.of(context), ); return _toolbarWidget!; } diff --git a/lib/src/editor/toolbar/desktop/floating_toolbar_widget.dart b/lib/src/editor/toolbar/desktop/floating_toolbar_widget.dart index 1385fd9c4..cb88f3ef6 100644 --- a/lib/src/editor/toolbar/desktop/floating_toolbar_widget.dart +++ b/lib/src/editor/toolbar/desktop/floating_toolbar_widget.dart @@ -4,6 +4,12 @@ import 'package:flutter/material.dart'; const floatingToolbarHeight = 32.0; +@visibleForTesting +const floatingToolbarContainerKey = + Key('appflowy_editor_floating_toolbar_container'); +@visibleForTesting +const floatingToolbarItemPrefixKey = 'appflowy_editor_floating_toolbar_item'; + class FloatingToolbarWidget extends StatefulWidget { const FloatingToolbarWidget({ super.key, @@ -11,12 +17,14 @@ class FloatingToolbarWidget extends StatefulWidget { required this.toolbarActiveColor, required this.items, required this.editorState, + required this.textDirection, }); final List items; final Color backgroundColor; final Color toolbarActiveColor; final EditorState editorState; + final TextDirection textDirection; @override State createState() => _FloatingToolbarWidgetState(); @@ -37,18 +45,24 @@ class _FloatingToolbarWidgetState extends State { child: SizedBox( height: floatingToolbarHeight, child: Row( + key: floatingToolbarContainerKey, crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: activeItems.map((item) { - final builder = item.builder; - return Center( - child: builder!( - context, - widget.editorState, - widget.toolbarActiveColor, - ), - ); - }).toList(growable: false), + textDirection: widget.textDirection, + children: activeItems + .mapIndexed( + (index, item) => Center( + key: Key( + '${floatingToolbarItemPrefixKey}_${item.id}_$index', + ), + child: item.builder!( + context, + widget.editorState, + widget.toolbarActiveColor, + ), + ), + ) + .toList(growable: false), ), ), ), @@ -62,8 +76,10 @@ class _FloatingToolbarWidgetState extends State { if (activeItems.isEmpty) { return []; } + // sort by group. activeItems.sort((a, b) => a.group.compareTo(b.group)); + // insert the divider. return activeItems .splitBetween((first, second) => first.group != second.group) diff --git a/test/customer/custom_toolbar_item_color_test.dart b/test/customer/custom_toolbar_item_color_test.dart index 3fad46bb6..f1b0aea42 100644 --- a/test/customer/custom_toolbar_item_color_test.dart +++ b/test/customer/custom_toolbar_item_color_test.dart @@ -61,6 +61,7 @@ class CustomToolbarItemColor extends StatelessWidget { border: Border.all(color: Colors.blue), ), child: FloatingToolbar( + textDirection: TextDirection.ltr, items: [bulletedListItem], style: const FloatingToolbarStyle( backgroundColor: Colors.red, diff --git a/test/new/infra/testable_editor.dart b/test/new/infra/testable_editor.dart index 2eca4c1c7..ae66af3c9 100644 --- a/test/new/infra/testable_editor.dart +++ b/test/new/infra/testable_editor.dart @@ -8,6 +8,18 @@ import 'package:flutter_test/flutter_test.dart'; import '../find_replace_menu/find_replace_menu_utils.dart'; import '../util/util.dart'; +final floatingToolbarItems = [ + paragraphItem, + ...headingItems, + ...markdownFormatItems, + quoteItem, + bulletedListItem, + numberedListItem, + linkItem, + buildTextColorItem(), + buildHighlightColorItem(), +]; + class TestableEditor { TestableEditor({ required this.tester, @@ -42,6 +54,7 @@ class TestableEditor { Widget Function(Widget child)? wrapper, TargetPlatform? platform, String? defaultTextDirection, + TextDirection textDirection = TextDirection.ltr, }) async { await AppFlowyEditorLocalizations.load(locale); @@ -106,23 +119,20 @@ class TestableEditor { ); } else { editor = FloatingToolbar( - items: [ - paragraphItem, - ...headingItems, - ...markdownFormatItems, - quoteItem, - bulletedListItem, - numberedListItem, - linkItem, - buildTextColorItem(), - buildHighlightColorItem(), - ], + items: floatingToolbarItems, editorState: editorState, + textDirection: textDirection, editorScrollController: editorScrollController, child: editor, ); } } + + editor = Directionality( + textDirection: textDirection, + child: editor, + ); + await tester.pumpWidget( MaterialApp( theme: ThemeData(platform: platform), diff --git a/test/new/service/shortcuts/command_shortcut_events/show_link_menu_command_test.dart b/test/new/service/shortcuts/command_shortcut_events/show_link_menu_command_test.dart index 17975baca..d5aa03d8d 100644 --- a/test/new/service/shortcuts/command_shortcut_events/show_link_menu_command_test.dart +++ b/test/new/service/shortcuts/command_shortcut_events/show_link_menu_command_test.dart @@ -44,6 +44,7 @@ Future _testLinkMenuInSingleTextSelection(WidgetTester tester) async { buildHighlightColorItem(), ], editorState: editor.editorState, + textDirection: TextDirection.ltr, editorScrollController: EditorScrollController( editorState: editor.editorState, scrollController: scrollController, diff --git a/test/new/toolbar/desktop/floating_toolbar_test.dart b/test/new/toolbar/desktop/floating_toolbar_test.dart index b7e8d0b06..c0733cbdd 100644 --- a/test/new/toolbar/desktop/floating_toolbar_test.dart +++ b/test/new/toolbar/desktop/floating_toolbar_test.dart @@ -1,4 +1,5 @@ import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../../infra/testable_editor.dart'; @@ -21,11 +22,59 @@ void main() async { endOffset: text.length, ); await editor.updateSelection(selection); - await tester.pumpAndSettle(); final floatingToolbar = find.byType(FloatingToolbarWidget); expect(floatingToolbar, findsOneWidget); expect(tester.getTopLeft(floatingToolbar).dy >= 0, true); + + await editor.dispose(); + }); + + testWidgets( + 'select the first line of the document, the toolbar layout should be right to left in RTL mode', + (tester) async { + final editor = tester.editor..addParagraphs(3, initialText: text); + await editor.startTesting( + withFloatingToolbar: true, + textDirection: TextDirection.rtl, + ); + + final selection = Selection.single( + path: [0], + startOffset: 0, + endOffset: text.length, + ); + await editor.updateSelection(selection); + + final floatingToolbar = find.byType(FloatingToolbarWidget); + expect(floatingToolbar, findsOneWidget); + final floatingToolbarContainer = tester.widget( + find.byKey(floatingToolbarContainerKey), + ); + expect(floatingToolbarContainer.textDirection, TextDirection.rtl); + final List toolbarItemWidgets = floatingToolbarContainer.children; + expect( + toolbarItemWidgets.length, + // the floating toolbar items will add the divider between the groups, + // so the length of the toolbar items will be the sum of the (floatingToolbarItems.length + + // the number of the groups) - 1. + floatingToolbarItems.length + + floatingToolbarItems.map((e) => e.group).toSet().length - + 1, + ); + + final expectedIds = floatingToolbarItems.map((e) => e.id).toList(); + var j = 0; + for (int i = 0; i < toolbarItemWidgets.length; i++) { + final id = '${floatingToolbarItemPrefixKey}_${expectedIds[j]}_$i'; + final key = toolbarItemWidgets[i].key as ValueKey; + if (key.value.contains(placeholderItem.id)) { + continue; + } + expect(key, Key(id)); + j++; + } + await editor.dispose(); }); });