-
Notifications
You must be signed in to change notification settings - Fork 29.7k
iOS text selection #11224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
iOS text selection #11224
Conversation
collinjackson
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a fair bit of code in an area that I'm not knowledgeable about so a second reviewer might be useful. Most of my comments are stylistic.
Can we test this?
| final double pressedOpacity; | ||
|
|
||
| /// The radius of the rounded corners of the button when it has a background | ||
| /// color. Defaults to 8 logical pixels. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put the second sentence in a separate paragraph.
Perhaps this should be a BorderRadius instead of a double to make it more configurable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| borderRadius: const BorderRadius.all(const Radius.circular(8.0)), | ||
| borderRadius: widget.borderRadius == null | ||
| ? null | ||
| : new BorderRadius.all(new Radius.circular(widget.borderRadius)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're going to do it this way, I think you can use the BorderRadius.circular constructor here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored
|
|
||
| /// Manages a copy/paste text selection toolbar. | ||
| class _TextSelectionToolbar extends StatelessWidget { | ||
| const _TextSelectionToolbar( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though this widget is private, I would use named parameters here because you have several parameters of the same type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
|
||
| @override | ||
| bool shouldRepaint(_TextSelectionToolbarNotchPainter oldPainter) { | ||
| return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible candidate for fat-arrow syntax
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks!
| void paint(Canvas canvas, Size size) { | ||
| final Paint paint = new Paint()..color = _selectionToolbarBackgroundBlack; | ||
| final Path triangle = new Path(); | ||
| triangle.lineTo(_selectionToolbarTriangleSize.width / 2, 0.0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would use the .. cascade operator here and below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah nice. It's a lot cleaner. Thanks!
| Size get handleSize; | ||
|
|
||
| void handleCut(TextSelectionDelegate delegate) { | ||
| Clipboard.setData(new ClipboardData( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You use delegate.textEditingValue here a lot, I think you should assign it to a local final.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| final ValueChanged<String> onSubmitted; | ||
|
|
||
| /// Optional input validation and formatting overrides. Formatters are run | ||
| /// Optional input validation and formatting overrides. Formatters are run |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You didn't introduce this, but since you touched this line, can you move the second sentence into its own paragraph?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| child: new Row(mainAxisSize: MainAxisSize.min, children: items), | ||
| ), | ||
| // TODO(xster): position the triangle based on the layout delegate. | ||
| // And avoid letting the triangle line up with any dividers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this get an issue as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
|
||
| const Color _selectionToolbarBackgroundBlack = const Color(0xFF2E2E2E); | ||
| const Color _selectionToolbarDividerGray = const Color(0xFFB9B9B9); | ||
| const Color _selectionHandlesBlue = const Color(0xFF146DDE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Having a description of the color in the name of color constants seems a bit odd to me. "Color" instead of "Black/Gray/Blue" would concentrate the knowledge of what these colors actually are to a single place instead of everywhere the constants are used.
You can see this in other places in Flutter, e.g. _kActiveTickColor, _kDisabledBackground
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| // Padding around the line at the edge of the text selection that has 0 width and | ||
| // the height of the text font. | ||
| const double _selectionHandlesPadding = 18.0; | ||
| const double _kToolbarScreenPadding = 8.0; // pixels |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I found this comment a bit confusing since all padding is pixels and was wondering if it is trying to specify logical vs physical? I realized it's probably copy-pasted from previous code, but the comment isn't on the line above. May be better to remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleaned up a bit here and it material
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like several of these arguments should be asserted-non-null and have a comment in the docs for the constructor that says they must not be null (see other widgets for examples of the style for these docs).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a few more null handling instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0.5 doesn't correspond to one physical pixel, does it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't it for ios?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iPhone 6 Plus has a device pixel ratio of 3. iPhone 3 has a device pixel ratio of 1. But also, all the iOS code has to work on Android devices and Fuchsia and whatever else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double checked. It is indeed exactly one physical pixel on all devices. Changed from constant to read the device pixel ratio.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as written this will never match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, sorry, missed some refactor cleanup
3b537ce to
d30e07d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 -> 2.0 to avoid an int-to-double conversion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what "Also flipped for iOS" means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reworded a bit
d30e07d to
19a277d
Compare
009556b to
42d2b09
Compare
42d2b09 to
67a3037
Compare
|
Posse wanted to check if there's still work to be done here or if this can be merged. |
|
It's done, just waiting for an LGTM |
| child: widget.selectionControls.buildHandle( | ||
| context, | ||
| type, | ||
| widget.renderObject.size.height / widget.renderObject.maxLines, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,
Seems this changes cause exception in case when TextField was created with unlimited lines new TextField(maxLines: null).
Steps:
- Create TextField with maxLines = null
- Enter some text, tap Done button
- Tap on TextField again
➜ mobileapp git:(develop) ✗ flutter --version
Flutter • channel master • https://github.com/flutter/flutter.git
Framework • revision a92a62706c (16 hours ago) • 2017-07-21 16:57:33 -0700
Engine • revision 5fcfb995bb
Tools • Dart 1.25.0-dev.7.0
The following NoSuchMethodError was thrown building _TextSelectionHandleOverlay(dirty; state:
_TextSelectionHandleOverlayState#201d1()):
The method 'toDouble' was called on null.
Receiver: null
Tried calling: toDouble()
When the exception was thrown, this was the stack:
#0 Object._noSuchMethod (dart:core-patch/object_patch.dart:42)
#1 Object.noSuchMethod (dart:core-patch/object_patch.dart:46)
#2 double./ (dart:core-patch/double.dart:39)
#3 _TextSelectionHandleOverlayState.build (package:flutter/src/widgets/text_selection.dart:482:49)
#4 StatefulElement.build (package:flutter/src/widgets/framework.dart:3632:27)
#5 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3542:15)
#6 Element.rebuild (package:flutter/src/widgets/framework.dart:3443:5)
#7 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:3522:5)
#8 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:3660:22)
#9 ComponentElement.mount (package:flutter/src/widgets/framework.dart:3517:5)
#10 Element.inflateWidget (package:flutter/src/widgets/framework.dart:2901:14)
#11 Element.updateChild (package:flutter/src/widgets/framework.dart:2704:12)
#12 SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:4526:14)
#13 Element.inflateWidget (package:flutter/src/widgets/framework.dart:2901:14)
#14 Element.updateChild (package:flutter/src/widgets/framework.dart:2704:12)
#15 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3554:16)
#16 Element.rebuild (package:flutter/src/widgets/framework.dart:3443:5)
#17 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:3522:5)
#18 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:3660:22)
#19 ComponentElement.mount (package:flutter/src/widgets/framework.dart:3517:5)
#20 Element.inflateWidget (package:flutter/src/widgets/framework.dart:2901:14)
#21 Element.updateChild (package:flutter/src/widgets/framework.dart:2704:12)
#22 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3554:16)
#23 Element.rebuild (package:flutter/src/widgets/framework.dart:3443:5)
#24 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:3522:5)
#25 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:3660:22)
#26 ComponentElement.mount (package:flutter/src/widgets/framework.dart:3517:5)
#27 Element.inflateWidget (package:flutter/src/widgets/framework.dart:2901:14)
#28 Element.updateChild (package:flutter/src/widgets/framework.dart:2704:12)
#29 RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:4317:32)
#30 MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4641:17)
#31 Element.updateChild (package:flutter/src/widgets/framework.dart:2693:15)
#32 _TheatreElement.update (package:flutter/src/widgets/overlay.dart:505:16)
#33 Element.updateChild (package:flutter/src/widgets/framework.dart:2693:15)
#34 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3554:16)
#35 Element.rebuild (package:flutter/src/widgets/framework.dart:3443:5)
#36 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2279:33)
#37 BindingBase&SchedulerBinding&GestureBinding&ServicesBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:503:20)
#38 BindingBase&SchedulerBinding&GestureBinding&ServicesBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:189:5)
#39 BindingBase&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:688:15)
#40 BindingBase&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:636:9)
#41 _invoke (file:///Users/herman/Projects/github/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart:86)
#42 _drawFrame (file:///Users/herman/Projects/github/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart:75)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much for tracking this down. Got a patch in #11353

#11148
Refactored some text selection stuff from material to widget.
Not in this PR: