feat: support RTL in toolbar (#496)
This commit is contained in:
parent
d1b9084ffb
commit
2b58d4f9ca
7 changed files with 103 additions and 22 deletions
|
|
@ -66,6 +66,7 @@ class _DesktopEditorState extends State<DesktopEditor> {
|
|||
...alignmentItems,
|
||||
],
|
||||
editorState: editorState,
|
||||
textDirection: widget.textDirection,
|
||||
editorScrollController: editorScrollController,
|
||||
child: Directionality(
|
||||
textDirection: widget.textDirection,
|
||||
|
|
|
|||
|
|
@ -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<ToolbarItem> items;
|
||||
final EditorState editorState;
|
||||
final EditorScrollController editorScrollController;
|
||||
final TextDirection? textDirection;
|
||||
final Widget child;
|
||||
final FloatingToolbarStyle style;
|
||||
|
||||
|
|
@ -172,6 +174,7 @@ class _FloatingToolbarState extends State<FloatingToolbar>
|
|||
editorState: editorState,
|
||||
backgroundColor: widget.style.backgroundColor,
|
||||
toolbarActiveColor: widget.style.toolbarActiveColor,
|
||||
textDirection: widget.textDirection ?? Directionality.of(context),
|
||||
);
|
||||
return _toolbarWidget!;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ToolbarItem> items;
|
||||
final Color backgroundColor;
|
||||
final Color toolbarActiveColor;
|
||||
final EditorState editorState;
|
||||
final TextDirection textDirection;
|
||||
|
||||
@override
|
||||
State<FloatingToolbarWidget> createState() => _FloatingToolbarWidgetState();
|
||||
|
|
@ -37,18 +45,24 @@ class _FloatingToolbarWidgetState extends State<FloatingToolbarWidget> {
|
|||
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!(
|
||||
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),
|
||||
),
|
||||
)
|
||||
.toList(growable: false),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -62,8 +76,10 @@ class _FloatingToolbarWidgetState extends State<FloatingToolbarWidget> {
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
|
|||
buildHighlightColorItem(),
|
||||
],
|
||||
editorState: editor.editorState,
|
||||
textDirection: TextDirection.ltr,
|
||||
editorScrollController: EditorScrollController(
|
||||
editorState: editor.editorState,
|
||||
scrollController: scrollController,
|
||||
|
|
|
|||
|
|
@ -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<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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue