Unverified Commit c6870ee9 authored by Chinmoy's avatar Chinmoy Committed by GitHub

Added onSelectionChange callback to SelectionRegion and SelectionArea (#108985)

parent 43d38bd4
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart';
import 'debug.dart';
import 'desktop_text_selection.dart';
......@@ -41,6 +42,7 @@ class SelectionArea extends StatefulWidget {
this.focusNode,
this.selectionControls,
this.magnifierConfiguration,
this.onSelectionChanged,
required this.child,
});
......@@ -63,6 +65,9 @@ class SelectionArea extends StatefulWidget {
/// If it is null, the platform specific selection control is used.
final TextSelectionControls? selectionControls;
/// Called when the selected content changes.
final ValueChanged<SelectedContent?>? onSelectionChanged;
/// The child widget this selection area applies to.
///
/// {@macro flutter.widgets.ProxyWidget.child}
......@@ -112,6 +117,7 @@ class _SelectionAreaState extends State<SelectionArea> {
focusNode: _effectiveFocusNode,
selectionControls: controls,
magnifierConfiguration: widget.magnifierConfiguration ?? TextMagnifier.adaptiveMagnifierConfiguration,
onSelectionChanged: widget.onSelectionChanged,
child: widget.child,
);
}
......
......@@ -204,6 +204,7 @@ class SelectableRegion extends StatefulWidget {
required this.selectionControls,
required this.child,
this.magnifierConfiguration = TextMagnifierConfiguration.disabled,
this.onSelectionChanged,
});
/// {@macro flutter.widgets.magnifier.TextMagnifierConfiguration.intro}
......@@ -230,6 +231,9 @@ class SelectableRegion extends StatefulWidget {
/// [TextSelectionControls] implementation with no controls.
final TextSelectionControls selectionControls;
/// Called when the selected content changes.
final ValueChanged<SelectedContent?>? onSelectionChanged;
@override
State<StatefulWidget> createState() => _SelectableRegionState();
}
......@@ -252,6 +256,7 @@ class _SelectableRegionState extends State<SelectableRegion> with TextSelectionD
|| _selectionDelegate.value.endSelectionPoint != null;
Orientation? _lastOrientation;
SelectedContent? _lastSelectedContent;
@override
void initState() {
......@@ -389,8 +394,16 @@ class _SelectableRegionState extends State<SelectableRegion> with TextSelectionD
_selectEndTo(offset: details.globalPosition, continuous: true);
}
void _updateSelectedContentIfNeeded() {
if (_lastSelectedContent?.plainText != _selectable?.getSelectedContent()?.plainText) {
_lastSelectedContent = _selectable?.getSelectedContent();
widget.onSelectionChanged?.call(_lastSelectedContent);
}
}
void _handleMouseDragEnd(DragEndDetails details) {
_finalizeSelection();
_updateSelectedContentIfNeeded();
}
void _handleTouchLongPressStart(LongPressStartDetails details) {
......@@ -398,6 +411,7 @@ class _SelectableRegionState extends State<SelectableRegion> with TextSelectionD
_selectWordAt(offset: details.globalPosition);
_showToolbar();
_showHandles();
_updateSelectedContentIfNeeded();
}
void _handleTouchLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
......@@ -406,6 +420,7 @@ class _SelectableRegionState extends State<SelectableRegion> with TextSelectionD
void _handleTouchLongPressEnd(LongPressEndDetails details) {
_finalizeSelection();
_updateSelectedContentIfNeeded();
}
void _handleRightClickDown(TapDownDetails details) {
......@@ -413,6 +428,7 @@ class _SelectableRegionState extends State<SelectableRegion> with TextSelectionD
_selectWordAt(offset: details.globalPosition);
_showHandles();
_showToolbar(location: details.globalPosition);
_updateSelectedContentIfNeeded();
}
// Selection update helper methods.
......@@ -451,6 +467,7 @@ class _SelectableRegionState extends State<SelectableRegion> with TextSelectionD
void _onAnyDragEnd(DragEndDetails details) {
_selectionOverlay!.hideMagnifier(shouldShowToolbar: true);
_stopSelectionEndEdgeUpdate();
_updateSelectedContentIfNeeded();
}
void _stopSelectionEndEdgeUpdate() {
......@@ -802,6 +819,7 @@ class _SelectableRegionState extends State<SelectableRegion> with TextSelectionD
void _clearSelection() {
_finalizeSelection();
_selectable?.dispatchSelectionEvent(const ClearSelectionEvent());
_updateSelectedContentIfNeeded();
}
Future<void> _copy() async {
......@@ -836,6 +854,7 @@ class _SelectableRegionState extends State<SelectableRegion> with TextSelectionD
_showToolbar();
_showHandles();
}
_updateSelectedContentIfNeeded();
}
@override
......
......@@ -4,9 +4,17 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
Offset textOffsetToPosition(RenderParagraph paragraph, int offset) {
const Rect caret = Rect.fromLTWH(0.0, 0.0, 2.0, 20.0);
final Offset localOffset = paragraph.getOffsetForCaret(TextPosition(offset: offset), caret);
return paragraph.localToGlobal(localOffset);
}
void main() {
testWidgets('SelectionArea uses correct selection controls', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
......@@ -33,4 +41,35 @@ void main() {
break;
}
}, variant: TargetPlatformVariant.all());
testWidgets('onSelectionChange is called when the selection changes', (WidgetTester tester) async {
SelectedContent? content;
await tester.pumpWidget(MaterialApp(
home: SelectionArea(
child: const Text('How are you'),
onSelectionChanged: (SelectedContent? selectedContent) => content = selectedContent,
),
));
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 4), kind: PointerDeviceKind.mouse);
expect(content, isNull);
addTearDown(gesture.removePointer);
await tester.pump();
await gesture.moveTo(textOffsetToPosition(paragraph, 7));
await gesture.up();
await tester.pump();
expect(content, isNotNull);
expect(content!.plainText, 'are');
// Backwards selection.
await gesture.down(textOffsetToPosition(paragraph, 3));
expect(content, isNull);
await gesture.moveTo(textOffsetToPosition(paragraph, 0));
await gesture.up();
await tester.pump();
expect(content, isNotNull);
expect(content!.plainText, 'How');
});
}
......@@ -1208,6 +1208,43 @@ void main() {
skip: kIsWeb, // [intended] Web uses its native context menu.
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android }),
);
testWidgets('onSelectionChange is called when the selection changes', (WidgetTester tester) async {
SelectedContent? content;
await tester.pumpWidget(
MaterialApp(
home: SelectableRegion(
onSelectionChanged: (SelectedContent? selectedContent) => content = selectedContent,
focusNode: FocusNode(),
selectionControls: materialTextSelectionControls,
child: const Center(
child: Text('How are you'),
),
),
),
);
final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText)));
final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 4), kind: PointerDeviceKind.mouse);
expect(content, isNull);
addTearDown(gesture.removePointer);
await tester.pump();
await gesture.moveTo(textOffsetToPosition(paragraph, 7));
await gesture.up();
await tester.pump();
expect(content, isNotNull);
expect(content!.plainText, 'are');
// Backwards selection.
await gesture.down(textOffsetToPosition(paragraph, 3));
expect(content, isNull);
await gesture.moveTo(textOffsetToPosition(paragraph, 0));
await gesture.up();
await tester.pump();
expect(content, isNotNull);
expect(content!.plainText, 'How');
});
}
class SelectionSpy extends LeafRenderObjectWidget {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment