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
2 years ago
by
Renzo Olivares
Committed by
GitHub
2 years ago
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add ability to show magnifier on long press (#111224)
parent
d339517b
Changes
5
Show 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
;
}
}
}
}
...
...
This diff is collapsed.
Click to expand it.
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.
//
...
...
This diff is collapsed.
Click to expand it.
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
(
_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
(
_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
();
}
...
...
This diff is collapsed.
Click to expand it.
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'
);
...
...
This diff is collapsed.
Click to expand it.
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'
);
...
...
This diff is collapsed.
Click to expand it.
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