From 5e8ab2b3c848a58eb0933b73ee7f84d47025ec5c Mon Sep 17 00:00:00 2001 From: Amir Panahandeh Date: Mon, 25 Mar 2024 17:46:08 +0330 Subject: [PATCH] Hide collapsed selection handle when editor is read-only (#299) * Hide collapsed selection handle when editor is read-only --- packages/fleather/lib/src/widgets/editor.dart | 53 ++++++++++++++++--- .../fleather/test/widgets/editor_test.dart | 15 ++++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/packages/fleather/lib/src/widgets/editor.dart b/packages/fleather/lib/src/widgets/editor.dart index cfe84230..d82e5eed 100644 --- a/packages/fleather/lib/src/widgets/editor.dart +++ b/packages/fleather/lib/src/widgets/editor.dart @@ -308,6 +308,8 @@ class _FleatherEditorState extends State implements EditorTextSelectionGestureDetectorBuilderDelegate { GlobalKey? _editorKey; + bool _showSelectionHandles = false; + @override GlobalKey get editableTextKey => widget.editorKey ?? _editorKey!; @@ -342,10 +344,42 @@ class _FleatherEditorState extends State _FleatherEditorSelectionGestureDetectorBuilder(state: this); } - static const Set _mobilePlatforms = { - TargetPlatform.iOS, - TargetPlatform.android - }; + void _handleSelectionChanged( + TextSelection selection, SelectionChangedCause? cause) { + final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause); + if (willShowSelectionHandles != _showSelectionHandles) { + setState(() { + _showSelectionHandles = willShowSelectionHandles; + }); + } + } + + bool _shouldShowSelectionHandles(SelectionChangedCause? cause) { + // When the editor is activated by something that doesn't trigger the + // selection overlay, we shouldn't show the handles either. + if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) { + return false; + } + + if (cause == SelectionChangedCause.keyboard) { + return false; + } + + if (widget.readOnly && widget.controller.selection.isCollapsed) { + return false; + } + + if (cause == SelectionChangedCause.longPress || + cause == SelectionChangedCause.scribble) { + return true; + } + + if (widget.controller.document.toPlainText().length > 2) { + return true; + } + + return false; + } @override Widget build(BuildContext context) { @@ -360,7 +394,6 @@ class _FleatherEditorState extends State Color selectionColor; Radius? cursorRadius; - final showSelectionHandles = _mobilePlatforms.contains(theme.platform); final keyboardAppearance = widget.keyboardAppearance ?? theme.brightness; switch (theme.platform) { @@ -445,7 +478,8 @@ class _FleatherEditorState extends State opacityAnimates: cursorOpacityAnimates, ), selectionColor: selectionColor, - showSelectionHandles: showSelectionHandles, + showSelectionHandles: _showSelectionHandles, + onSelectionChanged: _handleSelectionChanged, selectionControls: textSelectionControls, ); @@ -532,6 +566,7 @@ class RawEditor extends StatefulWidget { required this.clipboardManager, this.showSelectionHandles = false, this.selectionControls, + this.onSelectionChanged, this.contextMenuBuilder = defaultContextMenuBuilder, this.spellCheckConfiguration, this.embedBuilder = defaultFleatherEmbedBuilder, @@ -600,6 +635,10 @@ class RawEditor extends StatefulWidget { /// * [showCursor], which controls the visibility of the cursor.. final bool showSelectionHandles; + /// Called when the user changes the selection of text (including the cursor + /// location). + final SelectionChangedCallback? onSelectionChanged; + /// Whether to show cursor. /// /// The cursor refers to the blinking caret when the editor is focused. @@ -1431,6 +1470,8 @@ class RawEditorState extends EditorState bringIntoView(selection.extent); } } + + widget.onSelectionChanged?.call(selection, cause); } EditorTextSelectionOverlay _createSelectionOverlay() { diff --git a/packages/fleather/test/widgets/editor_test.dart b/packages/fleather/test/widgets/editor_test.dart index b13495cb..b89ebb59 100644 --- a/packages/fleather/test/widgets/editor_test.dart +++ b/packages/fleather/test/widgets/editor_test.dart @@ -56,6 +56,21 @@ void main() { expect(widget.readOnly, true); }); + testWidgets('Selection handle is hidden when editor is read-only', + (tester) async { + final editor = EditorSandBox( + tester: tester, + document: ParchmentDocument.fromDelta(Delta()..insert('Text\n'))); + await editor.pump(); + await editor.disable(); + await tester.tapAt( + tester.getTopLeft(find.byType(RawEditor)) + const Offset(15, 5)); + await tester.pumpAndSettle(); + final handle = tester.widget(editor.findSelectionHandles().first) + as SelectionHandleOverlay; + expect(handle.visibility?.value, false); + }, variant: TargetPlatformVariant.only(TargetPlatform.android)); + testWidgets('ability to paste upon long press on an empty document', (tester) async { // if Clipboard not initialize (status 'unknown'), an shrunken toolbar appears