feat: support RTL in toolbar (#496)

This commit is contained in:
Lakhan Baheti 2023-09-27 13:04:33 +05:30 • committed by GitHub
commit 2b58d4f9ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 22 deletions

View file

@ -66,6 +66,7 @@ class _DesktopEditorState extends State<DesktopEditor> {
...alignmentItems, ...alignmentItems,
], ],
editorState: editorState, editorState: editorState,
textDirection: widget.textDirection,
editorScrollController: editorScrollController, editorScrollController: editorScrollController,
child: Directionality( child: Directionality(
textDirection: widget.textDirection, textDirection: widget.textDirection,

View file

@ -22,6 +22,7 @@ class FloatingToolbar extends StatefulWidget {
required this.items, required this.items,
required this.editorState, required this.editorState,
required this.editorScrollController, required this.editorScrollController,
required this.textDirection,
required this.child, required this.child,
this.style = const FloatingToolbarStyle(), this.style = const FloatingToolbarStyle(),
}); });
@ -29,6 +30,7 @@ class FloatingToolbar extends StatefulWidget {
final List<ToolbarItem> items; final List<ToolbarItem> items;
final EditorState editorState; final EditorState editorState;
final EditorScrollController editorScrollController; final EditorScrollController editorScrollController;
final TextDirection? textDirection;
final Widget child; final Widget child;
final FloatingToolbarStyle style; final FloatingToolbarStyle style;
@ -172,6 +174,7 @@ class _FloatingToolbarState extends State<FloatingToolbar>
editorState: editorState, editorState: editorState,
backgroundColor: widget.style.backgroundColor, backgroundColor: widget.style.backgroundColor,
toolbarActiveColor: widget.style.toolbarActiveColor, toolbarActiveColor: widget.style.toolbarActiveColor,
textDirection: widget.textDirection ?? Directionality.of(context),
); );
return _toolbarWidget!; return _toolbarWidget!;
} }

View file

@ -4,6 +4,12 @@ import 'package:flutter/material.dart';
const floatingToolbarHeight = 32.0; 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 { class FloatingToolbarWidget extends StatefulWidget {
const FloatingToolbarWidget({ const FloatingToolbarWidget({
super.key, super.key,
@ -11,12 +17,14 @@ class FloatingToolbarWidget extends StatefulWidget {
required this.toolbarActiveColor, required this.toolbarActiveColor,
required this.items, required this.items,
required this.editorState, required this.editorState,
required this.textDirection,
}); });
final List<ToolbarItem> items; final List<ToolbarItem> items;
final Color backgroundColor; final Color backgroundColor;
final Color toolbarActiveColor; final Color toolbarActiveColor;
final EditorState editorState; final EditorState editorState;
final TextDirection textDirection;
@override @override
State<FloatingToolbarWidget> createState() => _FloatingToolbarWidgetState(); State<FloatingToolbarWidget> createState() => _FloatingToolbarWidgetState();
@ -37,18 +45,24 @@ class _FloatingToolbarWidgetState extends State<FloatingToolbarWidget> {
child: SizedBox( child: SizedBox(
height: floatingToolbarHeight, height: floatingToolbarHeight,
child: Row( child: Row(
key: floatingToolbarContainerKey,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: activeItems.map((item) { textDirection: widget.textDirection,
final builder = item.builder; children: activeItems
return Center( .mapIndexed(
child: builder!( (index, item) => Center(
context, key: Key(
widget.editorState, '${floatingToolbarItemPrefixKey}_${item.id}_$index',
widget.toolbarActiveColor, ),
), child: item.builder!(
); context,
}).toList(growable: false), widget.editorState,
widget.toolbarActiveColor,
),
),
)
.toList(growable: false),
), ),
), ),
), ),
@ -62,8 +76,10 @@ class _FloatingToolbarWidgetState extends State<FloatingToolbarWidget> {
if (activeItems.isEmpty) { if (activeItems.isEmpty) {
return []; return [];
} }
// sort by group. // sort by group.
activeItems.sort((a, b) => a.group.compareTo(b.group)); activeItems.sort((a, b) => a.group.compareTo(b.group));
// insert the divider. // insert the divider.
return activeItems return activeItems
.splitBetween((first, second) => first.group != second.group) .splitBetween((first, second) => first.group != second.group)

View file

@ -61,6 +61,7 @@ class CustomToolbarItemColor extends StatelessWidget {
border: Border.all(color: Colors.blue), border: Border.all(color: Colors.blue),
), ),
child: FloatingToolbar( child: FloatingToolbar(
textDirection: TextDirection.ltr,
items: [bulletedListItem], items: [bulletedListItem],
style: const FloatingToolbarStyle( style: const FloatingToolbarStyle(
backgroundColor: Colors.red, backgroundColor: Colors.red,

View file

@ -8,6 +8,18 @@ import 'package:flutter_test/flutter_test.dart';
import '../find_replace_menu/find_replace_menu_utils.dart'; import '../find_replace_menu/find_replace_menu_utils.dart';
import '../util/util.dart'; import '../util/util.dart';
final floatingToolbarItems = [
paragraphItem,
...headingItems,
...markdownFormatItems,
quoteItem,
bulletedListItem,
numberedListItem,
linkItem,
buildTextColorItem(),
buildHighlightColorItem(),
];
class TestableEditor { class TestableEditor {
TestableEditor({ TestableEditor({
required this.tester, required this.tester,
@ -42,6 +54,7 @@ class TestableEditor {
Widget Function(Widget child)? wrapper, Widget Function(Widget child)? wrapper,
TargetPlatform? platform, TargetPlatform? platform,
String? defaultTextDirection, String? defaultTextDirection,
TextDirection textDirection = TextDirection.ltr,
}) async { }) async {
await AppFlowyEditorLocalizations.load(locale); await AppFlowyEditorLocalizations.load(locale);
@ -106,23 +119,20 @@ class TestableEditor {
); );
} else { } else {
editor = FloatingToolbar( editor = FloatingToolbar(
items: [ items: floatingToolbarItems,
paragraphItem,
...headingItems,
...markdownFormatItems,
quoteItem,
bulletedListItem,
numberedListItem,
linkItem,
buildTextColorItem(),
buildHighlightColorItem(),
],
editorState: editorState, editorState: editorState,
textDirection: textDirection,
editorScrollController: editorScrollController, editorScrollController: editorScrollController,
child: editor, child: editor,
); );
} }
} }
editor = Directionality(
textDirection: textDirection,
child: editor,
);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData(platform: platform), theme: ThemeData(platform: platform),

View file

@ -44,6 +44,7 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
buildHighlightColorItem(), buildHighlightColorItem(),
], ],
editorState: editor.editorState, editorState: editor.editorState,
textDirection: TextDirection.ltr,
editorScrollController: EditorScrollController( editorScrollController: EditorScrollController(
editorState: editor.editorState, editorState: editor.editorState,
scrollController: scrollController, scrollController: scrollController,

View file

@ -1,4 +1,5 @@
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../../infra/testable_editor.dart'; import '../../infra/testable_editor.dart';
@ -21,11 +22,59 @@ void main() async {
endOffset: text.length, endOffset: text.length,
); );
await editor.updateSelection(selection); await editor.updateSelection(selection);
await tester.pumpAndSettle();
final floatingToolbar = find.byType(FloatingToolbarWidget); final floatingToolbar = find.byType(FloatingToolbarWidget);
expect(floatingToolbar, findsOneWidget); expect(floatingToolbar, findsOneWidget);
expect(tester.getTopLeft(floatingToolbar).dy >= 0, true); 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<Row>(
find.byKey(floatingToolbarContainerKey),
);
expect(floatingToolbarContainer.textDirection, TextDirection.rtl);
final List<Widget> 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(); await editor.dispose();
}); });
}); });