mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: selection is wrong after pressing arrow left / right
This commit is contained in:
parent
28d9b8ee3a
commit
45a120fe88
@ -5,18 +5,23 @@ class FlowySvg extends StatelessWidget {
|
|||||||
const FlowySvg({
|
const FlowySvg({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.name,
|
this.name,
|
||||||
this.size = const Size(20, 20),
|
this.width,
|
||||||
|
this.height,
|
||||||
this.color,
|
this.color,
|
||||||
this.number,
|
this.number,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String? name;
|
final String? name;
|
||||||
final Size size;
|
final double? width;
|
||||||
|
final double? height;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
final int? number;
|
final int? number;
|
||||||
final EdgeInsets? padding;
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
|
final _defaultWidth = 20.0;
|
||||||
|
final _defaultHeight = 20.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@ -27,22 +32,21 @@ class FlowySvg extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildSvg() {
|
Widget _buildSvg() {
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
return SizedBox.fromSize(
|
return SvgPicture.asset(
|
||||||
size: size,
|
'assets/images/$name.svg',
|
||||||
child: SvgPicture.asset(
|
color: color,
|
||||||
'assets/images/$name.svg',
|
fit: BoxFit.fill,
|
||||||
color: color,
|
height: height,
|
||||||
package: 'appflowy_editor',
|
width: width,
|
||||||
fit: BoxFit.fill,
|
package: 'appflowy_editor',
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else if (number != null) {
|
} else if (number != null) {
|
||||||
final numberText =
|
final numberText =
|
||||||
'<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"><text x="30" y="150" fill="black" font-size="160">$number.</text></svg>';
|
'<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"><text x="30" y="150" fill="black" font-size="160">$number.</text></svg>';
|
||||||
return SvgPicture.string(
|
return SvgPicture.string(
|
||||||
numberText,
|
numberText,
|
||||||
width: size.width,
|
width: width ?? _defaultWidth,
|
||||||
height: size.width,
|
height: height ?? _defaultHeight,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container();
|
return Container();
|
||||||
|
@ -47,7 +47,7 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
|
|||||||
final iconKey = GlobalKey();
|
final iconKey = GlobalKey();
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text');
|
final _richTextKey = GlobalKey(debugLabel: 'bulleted_list_text');
|
||||||
final _iconSize = 20.0;
|
final _iconWidth = 20.0;
|
||||||
final _iconRightPadding = 5.0;
|
final _iconRightPadding = 5.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -67,7 +67,8 @@ class _BulletedListTextNodeWidgetState extends State<BulletedListTextNodeWidget>
|
|||||||
children: [
|
children: [
|
||||||
FlowySvg(
|
FlowySvg(
|
||||||
key: iconKey,
|
key: iconKey,
|
||||||
size: Size.square(_iconSize),
|
width: _iconWidth,
|
||||||
|
height: _iconWidth,
|
||||||
padding:
|
padding:
|
||||||
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
|
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
|
||||||
name: 'point',
|
name: 'point',
|
||||||
|
@ -45,7 +45,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
|||||||
final iconKey = GlobalKey();
|
final iconKey = GlobalKey();
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'checkbox_text');
|
final _richTextKey = GlobalKey(debugLabel: 'checkbox_text');
|
||||||
final _iconSize = 20.0;
|
final _iconWidth = 20.0;
|
||||||
final _iconRightPadding = 5.0;
|
final _iconRightPadding = 5.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -74,7 +74,8 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
key: iconKey,
|
key: iconKey,
|
||||||
child: FlowySvg(
|
child: FlowySvg(
|
||||||
size: Size.square(_iconSize),
|
width: _iconWidth,
|
||||||
|
height: _iconWidth,
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
top: topPadding,
|
top: topPadding,
|
||||||
right: _iconRightPadding,
|
right: _iconRightPadding,
|
||||||
|
@ -47,7 +47,7 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
|
|||||||
final iconKey = GlobalKey();
|
final iconKey = GlobalKey();
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'number_list_text');
|
final _richTextKey = GlobalKey(debugLabel: 'number_list_text');
|
||||||
final _iconSize = 20.0;
|
final _iconWidth = 20.0;
|
||||||
final _iconRightPadding = 5.0;
|
final _iconRightPadding = 5.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -66,7 +66,8 @@ class _NumberListTextNodeWidgetState extends State<NumberListTextNodeWidget>
|
|||||||
children: [
|
children: [
|
||||||
FlowySvg(
|
FlowySvg(
|
||||||
key: iconKey,
|
key: iconKey,
|
||||||
size: Size.square(_iconSize),
|
width: _iconWidth,
|
||||||
|
height: _iconWidth,
|
||||||
padding:
|
padding:
|
||||||
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
|
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
|
||||||
number: widget.textNode.attributes.number,
|
number: widget.textNode.attributes.number,
|
||||||
|
@ -46,7 +46,7 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
|||||||
final iconKey = GlobalKey();
|
final iconKey = GlobalKey();
|
||||||
|
|
||||||
final _richTextKey = GlobalKey(debugLabel: 'quoted_text');
|
final _richTextKey = GlobalKey(debugLabel: 'quoted_text');
|
||||||
final _iconSize = 20.0;
|
final _iconWidth = 20.0;
|
||||||
final _iconRightPadding = 5.0;
|
final _iconRightPadding = 5.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -60,25 +60,27 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
|||||||
width: defaultMaxTextNodeWidth,
|
width: defaultMaxTextNodeWidth,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
padding: EdgeInsets.only(bottom: defaultLinePadding),
|
||||||
child: Row(
|
child: IntrinsicHeight(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Row(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
FlowySvg(
|
children: [
|
||||||
key: iconKey,
|
FlowySvg(
|
||||||
size: Size(_iconSize, _quoteHeight),
|
key: iconKey,
|
||||||
padding:
|
width: _iconWidth,
|
||||||
EdgeInsets.only(top: topPadding, right: _iconRightPadding),
|
padding: EdgeInsets.only(
|
||||||
name: 'quote',
|
top: topPadding, right: _iconRightPadding),
|
||||||
),
|
name: 'quote',
|
||||||
Expanded(
|
|
||||||
child: FlowyRichText(
|
|
||||||
key: _richTextKey,
|
|
||||||
placeholderText: 'Quote',
|
|
||||||
textNode: widget.textNode,
|
|
||||||
editorState: widget.editorState,
|
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
],
|
child: FlowyRichText(
|
||||||
|
key: _richTextKey,
|
||||||
|
placeholderText: 'Quote',
|
||||||
|
textNode: widget.textNode,
|
||||||
|
editorState: widget.editorState,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -86,6 +88,6 @@ class _QuotedTextNodeWidgetState extends State<QuotedTextNodeWidget>
|
|||||||
double get _quoteHeight {
|
double get _quoteHeight {
|
||||||
final lines =
|
final lines =
|
||||||
widget.textNode.toRawString().characters.where((c) => c == '\n').length;
|
widget.textNode.toRawString().characters.where((c) => c == '\n').length;
|
||||||
return (lines + 1) * _iconSize;
|
return (lines + 1) * _iconWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ class _ToolbarWidgetState extends State<ToolbarWidget> with ToolbarMixin {
|
|||||||
Size(toolbarHeight - (width != null ? 20 : 0), toolbarHeight),
|
Size(toolbarHeight - (width != null ? 20 : 0), toolbarHeight),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: FlowySvg(
|
child: FlowySvg(
|
||||||
size: Size(width ?? 20, 20),
|
width: width ?? 20,
|
||||||
name: 'toolbar/$name',
|
name: 'toolbar/$name',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -120,8 +120,9 @@ AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
|
|||||||
editorState.updateCursorSelection(Selection.collapsed(leftPosition));
|
editorState.updateCursorSelection(Selection.collapsed(leftPosition));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
editorState
|
editorState.updateCursorSelection(
|
||||||
.updateCursorSelection(currentSelection.collapse(atStart: true));
|
currentSelection.collapse(atStart: currentSelection.isBackward),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
|
||||||
@ -131,7 +132,9 @@ AppFlowyKeyEventHandler arrowKeysHandler = (editorState, event) {
|
|||||||
editorState.updateCursorSelection(Selection.collapsed(rightPosition));
|
editorState.updateCursorSelection(Selection.collapsed(rightPosition));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
editorState.updateCursorSelection(currentSelection.collapse());
|
editorState.updateCursorSelection(
|
||||||
|
currentSelection.collapse(atStart: !currentSelection.isBackward),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
@ -448,5 +448,6 @@ class PopupListItem {
|
|||||||
Widget _popupListIcon(String name) => FlowySvg(
|
Widget _popupListIcon(String name) => FlowySvg(
|
||||||
name: 'popup_list/$name',
|
name: 'popup_list/$name',
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
size: const Size.square(18.0),
|
width: 18.0,
|
||||||
|
height: 18.0,
|
||||||
);
|
);
|
||||||
|
@ -88,6 +88,12 @@ extension on LogicalKeyboardKey {
|
|||||||
if (this == LogicalKeyboardKey.delete) {
|
if (this == LogicalKeyboardKey.delete) {
|
||||||
return PhysicalKeyboardKey.delete;
|
return PhysicalKeyboardKey.delete;
|
||||||
}
|
}
|
||||||
|
if (this == LogicalKeyboardKey.arrowRight) {
|
||||||
|
return PhysicalKeyboardKey.arrowRight;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.arrowLeft) {
|
||||||
|
return PhysicalKeyboardKey.arrowLeft;
|
||||||
|
}
|
||||||
if (this == LogicalKeyboardKey.pageDown) {
|
if (this == LogicalKeyboardKey.pageDown) {
|
||||||
return PhysicalKeyboardKey.pageDown;
|
return PhysicalKeyboardKey.pageDown;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import '../../infra/test_editor.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
setUpAll(() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('arrow_keys_handler.dart', () {
|
||||||
|
testWidgets('Presses arrow right key, move the cursor from left to right',
|
||||||
|
(tester) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
|
||||||
|
await editor.updateSelection(
|
||||||
|
Selection.single(path: [0], startOffset: 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.arrowRight);
|
||||||
|
|
||||||
|
if (i == text.length - 1) {
|
||||||
|
// Wrap to next node if the cursor is at the end of the current node.
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
Selection.single(
|
||||||
|
path: [1],
|
||||||
|
startOffset: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
Selection.single(
|
||||||
|
path: [0],
|
||||||
|
startOffset: textNode.delta.nextRunePosition(i),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Presses arrow left/right key since selection is not collapsed and backward',
|
||||||
|
(tester) async {
|
||||||
|
await _testPressArrowKeyInNotCollapsedSelection(tester, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Presses arrow left/right key since selection is not collapsed and forward',
|
||||||
|
(tester) async {
|
||||||
|
await _testPressArrowKeyInNotCollapsedSelection(tester, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _testPressArrowKeyInNotCollapsedSelection(
|
||||||
|
WidgetTester tester, bool isBackward) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
|
||||||
|
final start = Position(path: [0], offset: 5);
|
||||||
|
final end = Position(path: [1], offset: 10);
|
||||||
|
final selection = Selection(
|
||||||
|
start: isBackward ? start : end,
|
||||||
|
end: isBackward ? end : start,
|
||||||
|
);
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.arrowLeft);
|
||||||
|
expect(editor.documentSelection?.start, start);
|
||||||
|
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.arrowRight);
|
||||||
|
expect(editor.documentSelection?.end, end);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user