Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
ad946f84
Unverified
Commit
ad946f84
authored
Sep 09, 2022
by
Renzo Olivares
Committed by
GitHub
Sep 09, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add ability to show magnifier on long press (#111224)
parent
d339517b
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
275 additions
and
13 deletions
+275
-13
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+30
-2
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+31
-0
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+90
-11
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+60
-0
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+64
-0
No files found.
packages/flutter/lib/src/material/text_field.dart
View file @
ad946f84
...
...
@@ -65,7 +65,9 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete
@override
void
onSingleLongTapMoveUpdate
(
LongPressMoveUpdateDetails
details
)
{
if
(
delegate
.
selectionEnabled
)
{
switch
(
Theme
.
of
(
_state
.
context
).
platform
)
{
final
TargetPlatform
targetPlatform
=
Theme
.
of
(
_state
.
context
).
platform
;
switch
(
targetPlatform
)
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
renderEditable
.
selectPositionAt
(
...
...
@@ -84,6 +86,18 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete
);
break
;
}
switch
(
targetPlatform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
iOS
:
editableText
.
showMagnifier
(
details
.
globalPosition
);
break
;
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
macOS
:
case
TargetPlatform
.
windows
:
break
;
}
}
}
...
...
@@ -97,7 +111,9 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete
@override
void
onSingleLongTapStart
(
LongPressStartDetails
details
)
{
if
(
delegate
.
selectionEnabled
)
{
switch
(
Theme
.
of
(
_state
.
context
).
platform
)
{
final
TargetPlatform
targetPlatform
=
Theme
.
of
(
_state
.
context
).
platform
;
switch
(
targetPlatform
)
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
renderEditable
.
selectPositionAt
(
...
...
@@ -113,6 +129,18 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete
Feedback
.
forLongPress
(
_state
.
context
);
break
;
}
switch
(
targetPlatform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
iOS
:
editableText
.
showMagnifier
(
details
.
globalPosition
);
break
;
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
macOS
:
case
TargetPlatform
.
windows
:
break
;
}
}
}
}
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
ad946f84
...
...
@@ -3324,6 +3324,37 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
}
/// Shows the magnifier at the position given by `positionToShow`,
/// if there is no magnifier visible.
///
/// Updates the magnifier to the position given by `positionToShow`,
/// if there is a magnifier visible.
///
/// Does nothing if a magnifier couldn't be shown, such as when the selection
/// overlay does not currently exist.
void
showMagnifier
(
Offset
positionToShow
)
{
if
(
_selectionOverlay
==
null
)
{
return
;
}
if
(
_selectionOverlay
!.
magnifierIsVisible
)
{
_selectionOverlay
!.
updateMagnifier
(
positionToShow
);
}
else
{
_selectionOverlay
!.
showMagnifier
(
positionToShow
);
}
}
/// Hides the magnifier if it is visible.
void
hideMagnifier
({
required
bool
shouldShowToolbar
})
{
if
(
_selectionOverlay
==
null
)
{
return
;
}
if
(
_selectionOverlay
!.
magnifierIsVisible
)
{
_selectionOverlay
!.
hideMagnifier
(
shouldShowToolbar:
shouldShowToolbar
);
}
}
// Tracks the location a [_ScribblePlaceholder] should be rendered in the
// text.
//
...
...
packages/flutter/lib/src/widgets/text_selection.dart
View file @
ad946f84
...
...
@@ -408,6 +408,37 @@ class TextSelectionOverlay {
_selectionOverlay
.
showToolbar
();
}
/// {@macro flutter.widgets.SelectionOverlay.showMagnifier}
void
showMagnifier
(
Offset
positionToShow
)
{
final
TextPosition
position
=
renderObject
.
getPositionForPoint
(
positionToShow
);
_updateSelectionOverlay
();
_selectionOverlay
.
showMagnifier
(
_buildMagnifier
(
currentTextPosition:
position
,
globalGesturePosition:
positionToShow
,
renderEditable:
renderObject
,
),
);
}
/// {@macro flutter.widgets.SelectionOverlay.updateMagnifier}
void
updateMagnifier
(
Offset
positionToShow
)
{
final
TextPosition
position
=
renderObject
.
getPositionForPoint
(
positionToShow
);
_updateSelectionOverlay
();
_selectionOverlay
.
updateMagnifier
(
_buildMagnifier
(
currentTextPosition:
position
,
globalGesturePosition:
positionToShow
,
renderEditable:
renderObject
,
),
);
}
/// {@macro flutter.widgets.SelectionOverlay.hideMagnifier}
void
hideMagnifier
({
required
bool
shouldShowToolbar
})
{
_selectionOverlay
.
hideMagnifier
(
shouldShowToolbar:
shouldShowToolbar
);
}
/// Updates the overlay after the selection has changed.
///
/// If this method is called while the [SchedulerBinding.schedulerPhase] is
...
...
@@ -457,6 +488,9 @@ class TextSelectionOverlay {
/// Whether the toolbar is currently visible.
bool
get
toolbarIsVisible
=>
_selectionOverlay
.
_toolbar
!=
null
;
/// Whether the magnifier is currently visible.
bool
get
magnifierIsVisible
=>
_selectionOverlay
.
_magnifierController
.
shown
;
/// {@macro flutter.widgets.SelectionOverlay.hide}
void
hide
()
=>
_selectionOverlay
.
hide
();
...
...
@@ -554,11 +588,13 @@ class TextSelectionOverlay {
_dragEndPosition
=
details
.
globalPosition
+
Offset
(
0.0
,
-
handleSize
.
height
);
final
TextPosition
position
=
renderObject
.
getPositionForPoint
(
_dragEndPosition
);
_selectionOverlay
.
showMagnifier
(
_buildMagnifier
(
currentTextPosition:
position
,
globalGesturePosition:
details
.
globalPosition
,
renderEditable:
renderObject
,
));
_selectionOverlay
.
showMagnifier
(
_buildMagnifier
(
currentTextPosition:
position
,
globalGesturePosition:
details
.
globalPosition
,
renderEditable:
renderObject
,
),
);
}
void
_handleSelectionEndHandleDragUpdate
(
DragUpdateDetails
details
)
{
...
...
@@ -629,11 +665,13 @@ class TextSelectionOverlay {
_dragStartPosition
=
details
.
globalPosition
+
Offset
(
0.0
,
-
handleSize
.
height
);
final
TextPosition
position
=
renderObject
.
getPositionForPoint
(
_dragStartPosition
);
_selectionOverlay
.
showMagnifier
(
_buildMagnifier
(
currentTextPosition:
position
,
globalGesturePosition:
details
.
globalPosition
,
renderEditable:
renderObject
,
));
_selectionOverlay
.
showMagnifier
(
_buildMagnifier
(
currentTextPosition:
position
,
globalGesturePosition:
details
.
globalPosition
,
renderEditable:
renderObject
,
),
);
}
void
_handleSelectionStartHandleDragUpdate
(
DragUpdateDetails
details
)
{
...
...
@@ -788,6 +826,7 @@ class SelectionOverlay {
/// {@macro flutter.widgets.magnifier.TextMagnifierConfiguration.details}
final
TextMagnifierConfiguration
magnifierConfiguration
;
/// {@template flutter.widgets.SelectionOverlay.showMagnifier}
/// Shows the magnifier, and hides the toolbar if it was showing when [showMagnifier]
/// was called. This is safe to call on platforms not mobile, since
/// a magnifierBuilder will not be provided, or the magnifierBuilder will return null
...
...
@@ -796,6 +835,7 @@ class SelectionOverlay {
/// This is NOT the source of truth for if the magnifier is up or not,
/// since magnifiers may hide themselves. If this info is needed, check
/// [MagnifierController.shown].
/// {@endtemplate}
void
showMagnifier
(
MagnifierOverlayInfoBearer
initialInfoBearer
)
{
if
(
_toolbar
!=
null
)
{
hideToolbar
();
...
...
@@ -813,7 +853,7 @@ class SelectionOverlay {
_magnifierOverlayInfoBearer
,
);
if
(
builtMagnifier
==
null
)
{
if
(
builtMagnifier
==
null
||
_handles
==
null
)
{
return
;
}
...
...
@@ -825,10 +865,12 @@ class SelectionOverlay {
builder:
(
_
)
=>
builtMagnifier
);
}
/// {@template flutter.widgets.SelectionOverlay.hideMagnifier}
/// Hide the current magnifier, optionally immediately showing
/// the toolbar.
///
/// This does nothing if there is no magnifier.
/// {@endtemplate}
void
hideMagnifier
({
required
bool
shouldShowToolbar
})
{
// This cannot be a check on `MagnifierController.shown`, since
// it's possible that the magnifier is still in the overlay, but
...
...
@@ -1250,6 +1292,7 @@ class SelectionOverlay {
);
}
/// {@template flutter.widgets.SelectionOverlay.updateMagnifier}
/// Update the current magnifier with new selection data, so the magnifier
/// can respond accordingly.
///
...
...
@@ -1258,6 +1301,7 @@ class SelectionOverlay {
/// itself.
///
/// If there is no magnifier in the overlay, this does nothing,
/// {@endtemplate}
void
updateMagnifier
(
MagnifierOverlayInfoBearer
magnifierOverlayInfoBearer
)
{
if
(
_magnifierController
.
overlayEntry
==
null
)
{
return
;
...
...
@@ -1919,6 +1963,18 @@ class TextSelectionGestureDetectorBuilder {
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
switch
(
defaultTargetPlatform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
iOS
:
editableText
.
showMagnifier
(
details
.
globalPosition
);
break
;
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
macOS
:
case
TargetPlatform
.
windows
:
break
;
}
}
}
...
...
@@ -1938,6 +1994,18 @@ class TextSelectionGestureDetectorBuilder {
from:
details
.
globalPosition
,
cause:
SelectionChangedCause
.
longPress
,
);
switch
(
defaultTargetPlatform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
iOS
:
editableText
.
showMagnifier
(
details
.
globalPosition
);
break
;
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
macOS
:
case
TargetPlatform
.
windows
:
break
;
}
}
}
...
...
@@ -1951,6 +2019,17 @@ class TextSelectionGestureDetectorBuilder {
/// callback.
@protected
void
onSingleLongTapEnd
(
LongPressEndDetails
details
)
{
switch
(
defaultTargetPlatform
)
{
case
TargetPlatform
.
android
:
case
TargetPlatform
.
iOS
:
editableText
.
hideMagnifier
(
shouldShowToolbar:
false
);
break
;
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
macOS
:
case
TargetPlatform
.
windows
:
break
;
}
if
(
shouldShowSelectionToolbar
)
{
editableText
.
showToolbar
();
}
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
ad946f84
...
...
@@ -6178,6 +6178,66 @@ void main() {
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsNothing
);
},
variant:
TargetPlatformVariant
.
only
(
TargetPlatform
.
iOS
));
testWidgets
(
'Can long press to show, unshow, and update magnifier'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
final
bool
isTargetPlatformAndroid
=
defaultTargetPlatform
==
TargetPlatform
.
android
;
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
CupertinoTextField
(
dragStartBehavior:
DragStartBehavior
.
down
,
controller:
controller
,
magnifierConfiguration:
TextMagnifierConfiguration
(
magnifierBuilder:
(
_
,
MagnifierController
controller
,
ValueNotifier
<
MagnifierOverlayInfoBearer
>
localInfoBearer
)
{
infoBearer
=
localInfoBearer
;
return
fakeMagnifier
;
},
),
),
),
),
);
const
String
testValue
=
'abc def ghi'
;
await
tester
.
enterText
(
find
.
byType
(
CupertinoTextField
),
testValue
);
await
tester
.
pumpAndSettle
();
// Tap at 'e' to set the selection to position 5 on Android.
// Tap at 'e' to set the selection to the closest word edge, which is position 4 on iOS.
await
tester
.
tapAt
(
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'e'
)));
await
tester
.
pumpAndSettle
(
const
Duration
(
milliseconds:
300
));
expect
(
controller
.
selection
.
isCollapsed
,
true
);
expect
(
controller
.
selection
.
baseOffset
,
isTargetPlatformAndroid
?
5
:
4
);
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsNothing
);
// Long press the 'e' to move the cursor in front of the 'e' and show the magnifier.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'e'
)));
await
tester
.
pumpAndSettle
(
const
Duration
(
milliseconds:
1000
));
expect
(
controller
.
selection
.
baseOffset
,
5
);
expect
(
controller
.
selection
.
extentOffset
,
5
);
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsOneWidget
);
final
Offset
firstLongPressGesturePosition
=
infoBearer
.
value
.
globalGesturePosition
;
// Move the gesture to 'h' to update the magnifier and move the cursor to 'h'.
await
gesture
.
moveTo
(
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'h'
)));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
.
baseOffset
,
9
);
expect
(
controller
.
selection
.
extentOffset
,
9
);
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsOneWidget
);
// Expect the position the magnifier gets to have moved.
expect
(
firstLongPressGesturePosition
,
isNot
(
infoBearer
.
value
.
globalGesturePosition
));
// End the long press to hide the magnifier.
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsNothing
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
android
,
TargetPlatform
.
iOS
}));
group
(
'TapRegion integration'
,
()
{
testWidgets
(
'Tapping outside loses focus on desktop'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
...
...
packages/flutter/test/material/text_field_test.dart
View file @
ad946f84
...
...
@@ -12254,6 +12254,70 @@ void main() {
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsNothing
);
});
testWidgets
(
'Can long press to show, unshow, and update magnifier'
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
=
TextEditingController
();
final
bool
isTargetPlatformAndroid
=
defaultTargetPlatform
==
TargetPlatform
.
android
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
Center
(
child:
TextField
(
dragStartBehavior:
DragStartBehavior
.
down
,
controller:
controller
,
magnifierConfiguration:
TextMagnifierConfiguration
(
magnifierBuilder:
(
_
,
MagnifierController
controller
,
ValueNotifier
<
MagnifierOverlayInfoBearer
>
localInfoBearer
)
{
infoBearer
=
localInfoBearer
;
return
fakeMagnifier
;
},
),
),
),
),
),
);
const
String
testValue
=
'abc def ghi'
;
await
tester
.
enterText
(
find
.
byType
(
TextField
),
testValue
);
await
skipPastScrollingAnimation
(
tester
);
// Tap at 'e' to set the selection to position 5 on Android.
// Tap at 'e' to set the selection to the closest word edge, which is position 4 on iOS.
await
tester
.
tapAt
(
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'e'
)));
await
tester
.
pumpAndSettle
(
const
Duration
(
milliseconds:
300
));
expect
(
controller
.
selection
.
isCollapsed
,
true
);
expect
(
controller
.
selection
.
baseOffset
,
isTargetPlatformAndroid
?
5
:
4
);
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsNothing
);
// Long press the 'e' to select 'def' on Android and show magnifier.
// Long press the 'e' to move the cursor in front of the 'e' on iOS and show the magnifier.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'e'
)));
await
tester
.
pumpAndSettle
(
const
Duration
(
milliseconds:
1000
));
expect
(
controller
.
selection
.
baseOffset
,
isTargetPlatformAndroid
?
4
:
5
);
expect
(
controller
.
selection
.
extentOffset
,
isTargetPlatformAndroid
?
7
:
5
);
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsOneWidget
);
final
Offset
firstLongPressGesturePosition
=
infoBearer
.
value
.
globalGesturePosition
;
// Move the gesture to 'h' on Android to update the magnifier and select 'ghi'.
// Move the gesture to 'h' on iOS to update the magnifier and move the cursor to 'h'.
await
gesture
.
moveTo
(
textOffsetToPosition
(
tester
,
testValue
.
indexOf
(
'h'
)));
await
tester
.
pumpAndSettle
();
expect
(
controller
.
selection
.
baseOffset
,
isTargetPlatformAndroid
?
4
:
9
);
expect
(
controller
.
selection
.
extentOffset
,
isTargetPlatformAndroid
?
11
:
9
);
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsOneWidget
);
// Expect the position the magnifier gets to have moved.
expect
(
firstLongPressGesturePosition
,
isNot
(
infoBearer
.
value
.
globalGesturePosition
));
// End the long press to hide the magnifier.
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
byKey
(
fakeMagnifier
.
key
!),
findsNothing
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
android
,
TargetPlatform
.
iOS
}));
group
(
'TapRegion integration'
,
()
{
testWidgets
(
'Tapping outside loses focus on desktop'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment