Unverified Commit 2d2bb6bf authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

CupertinoTextField vertical alignment (#34723)

CupertinoTextField now supports vertical alignment via the textAlignVertical parameter.
parent 4cd12fc8
...@@ -174,6 +174,7 @@ class CupertinoTextField extends StatefulWidget { ...@@ -174,6 +174,7 @@ class CupertinoTextField extends StatefulWidget {
this.style, this.style,
this.strutStyle, this.strutStyle,
this.textAlign = TextAlign.start, this.textAlign = TextAlign.start,
this.textAlignVertical,
this.readOnly = false, this.readOnly = false,
this.showCursor, this.showCursor,
this.autofocus = false, this.autofocus = false,
...@@ -324,6 +325,9 @@ class CupertinoTextField extends StatefulWidget { ...@@ -324,6 +325,9 @@ class CupertinoTextField extends StatefulWidget {
/// {@macro flutter.widgets.editableText.textAlign} /// {@macro flutter.widgets.editableText.textAlign}
final TextAlign textAlign; final TextAlign textAlign;
/// {@macro flutter.material.inputDecorator.textAlignVertical}
final TextAlignVertical textAlignVertical;
/// {@macro flutter.widgets.editableText.readOnly} /// {@macro flutter.widgets.editableText.readOnly}
final bool readOnly; final bool readOnly;
...@@ -491,10 +495,13 @@ class CupertinoTextField extends StatefulWidget { ...@@ -491,10 +495,13 @@ class CupertinoTextField extends StatefulWidget {
properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled')); properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null)); properties.add(DiagnosticsProperty<ScrollController>('scrollController', scrollController, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null)); properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: TextAlign.start));
properties.add(DiagnosticsProperty<TextAlignVertical>('textAlignVertical', textAlignVertical, defaultValue: null));
} }
} }
class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticKeepAliveClientMixin { class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticKeepAliveClientMixin {
final GlobalKey _clearGlobalKey = GlobalKey();
final GlobalKey<EditableTextState> _editableTextKey = GlobalKey<EditableTextState>(); final GlobalKey<EditableTextState> _editableTextKey = GlobalKey<EditableTextState>();
TextEditingController _controller; TextEditingController _controller;
...@@ -584,6 +591,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -584,6 +591,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
} }
void _handleSingleTapUp(TapUpDetails details) { void _handleSingleTapUp(TapUpDetails details) {
// Because TextSelectionGestureDetector listens to taps that happen on
// widgets in front of it, tapping the clear button will also trigger
// this handler. If the the clear button widget recognizes the up event,
// then do not handle it.
if (_clearGlobalKey.currentContext != null) {
final RenderBox renderBox = _clearGlobalKey.currentContext.findRenderObject();
final Offset localOffset = renderBox.globalToLocal(details.globalPosition);
if(renderBox.hitTest(BoxHitTestResult(), position: localOffset)) {
return;
}
}
if (widget.selectionEnabled) { if (widget.selectionEnabled) {
_renderEditable.selectWordEdge(cause: SelectionChangedCause.tap); _renderEditable.selectWordEdge(cause: SelectionChangedCause.tap);
} }
...@@ -719,16 +738,31 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -719,16 +738,31 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
); );
} }
// True if any surrounding decoration widgets will be shown.
bool get _hasDecoration {
return widget.placeholder != null ||
widget.clearButtonMode != OverlayVisibilityMode.never ||
widget.prefix != null ||
widget.suffix != null;
}
// Provide default behavior if widget.textAlignVertical is not set.
// CupertinoTextField has top alignment by default, unless it has decoration
// like a prefix or suffix, in which case it's aligned to the center.
TextAlignVertical get _textAlignVertical {
if (widget.textAlignVertical != null) {
return widget.textAlignVertical;
}
return _hasDecoration ? TextAlignVertical.center : TextAlignVertical.top;
}
Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) { Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) {
assert(editableText != null); assert(editableText != null);
assert(textStyle != null); assert(textStyle != null);
assert(placeholderStyle != null); assert(placeholderStyle != null);
// If there are no surrounding widgets, just return the core editable text // If there are no surrounding widgets, just return the core editable text
// part. // part.
if (widget.placeholder == null && if (!_hasDecoration) {
widget.clearButtonMode == OverlayVisibilityMode.never &&
widget.prefix == null &&
widget.suffix == null) {
return editableText; return editableText;
} }
...@@ -776,6 +810,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -776,6 +810,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
} else if (_showClearButton(text)) { } else if (_showClearButton(text)) {
rowChildren.add( rowChildren.add(
GestureDetector( GestureDetector(
key: _clearGlobalKey,
onTap: widget.enabled ?? true ? () { onTap: widget.enabled ?? true ? () {
// Special handle onChanged for ClearButton // Special handle onChanged for ClearButton
// Also call onChanged when the clear button is tapped. // Also call onChanged when the clear button is tapped.
...@@ -825,20 +860,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -825,20 +860,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
? widget.decoration ? widget.decoration
: widget.decoration?.copyWith(color: widget.decoration?.color ?? disabledColor); : widget.decoration?.copyWith(color: widget.decoration?.color ?? disabledColor);
final Widget paddedEditable = TextSelectionGestureDetector( final Widget paddedEditable = Padding(
onTapDown: _handleTapDown,
onForcePressStart: _handleForcePressStarted,
onForcePressEnd: _handleForcePressEnded,
onSingleTapUp: _handleSingleTapUp,
onSingleLongTapStart: _handleSingleLongTapStart,
onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate,
onSingleLongTapEnd: _handleSingleLongTapEnd,
onDoubleTapDown: _handleDoubleTapDown,
onDragSelectionStart: _handleMouseDragSelectionStart,
onDragSelectionUpdate: _handleMouseDragSelectionUpdate,
onDragSelectionEnd: _handleMouseDragSelectionEnd,
behavior: HitTestBehavior.translucent,
child: Padding(
padding: widget.padding, padding: widget.padding,
child: RepaintBoundary( child: RepaintBoundary(
child: EditableText( child: EditableText(
...@@ -884,7 +906,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -884,7 +906,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
enableInteractiveSelection: widget.enableInteractiveSelection, enableInteractiveSelection: widget.enableInteractiveSelection,
), ),
), ),
),
); );
return Semantics( return Semantics(
...@@ -898,9 +919,28 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK ...@@ -898,9 +919,28 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
ignoring: !enabled, ignoring: !enabled,
child: Container( child: Container(
decoration: effectiveDecoration, decoration: effectiveDecoration,
child: TextSelectionGestureDetector(
onTapDown: _handleTapDown,
onForcePressStart: _handleForcePressStarted,
onForcePressEnd: _handleForcePressEnded,
onSingleTapUp: _handleSingleTapUp,
onSingleLongTapStart: _handleSingleLongTapStart,
onSingleLongTapMoveUpdate: _handleSingleLongTapMoveUpdate,
onSingleLongTapEnd: _handleSingleLongTapEnd,
onDoubleTapDown: _handleDoubleTapDown,
onDragSelectionStart: _handleMouseDragSelectionStart,
onDragSelectionUpdate: _handleMouseDragSelectionUpdate,
onDragSelectionEnd: _handleMouseDragSelectionEnd,
behavior: HitTestBehavior.translucent,
child: Align(
alignment: Alignment(-1.0, _textAlignVertical.y),
widthFactor: 1.0,
heightFactor: 1.0,
child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle), child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle),
), ),
), ),
),
),
); );
} }
} }
...@@ -3576,42 +3576,3 @@ class InputDecorationTheme extends Diagnosticable { ...@@ -3576,42 +3576,3 @@ class InputDecorationTheme extends Diagnosticable {
properties.add(DiagnosticsProperty<bool>('alignLabelWithHint', alignLabelWithHint, defaultValue: defaultTheme.alignLabelWithHint)); properties.add(DiagnosticsProperty<bool>('alignLabelWithHint', alignLabelWithHint, defaultValue: defaultTheme.alignLabelWithHint));
} }
} }
/// The vertical alignment of text within an input.
///
/// A single [y] value that can range from -1.0 to 1.0. -1.0 aligns to the top
/// of the input so that the top of the first line of text fits within the input
/// and its padding. 0.0 aligns to the center of the input. 1.0 aligns so that
/// the bottom of the last line of text aligns with the bottom interior edge of
/// the input.
///
/// See also:
///
/// * [TextField.textAlignVertical], which is passed on to the [InputDecorator].
/// * [InputDecorator.textAlignVertical], which defines the alignment of
/// prefix, input, and suffix, within the [InputDecorator].
class TextAlignVertical {
/// Construct TextAlignVertical from any given y value.
const TextAlignVertical({
@required this.y,
}) : assert(y != null),
assert(y >= -1.0 && y <= 1.0);
/// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost
/// locations of the top and bottom of the input text box.
final double y;
/// Aligns a TextField's input Text with the topmost location within the
/// TextField.
static const TextAlignVertical top = TextAlignVertical(y: -1.0);
/// Aligns a TextField's input Text to the center of the TextField.
static const TextAlignVertical center = TextAlignVertical(y: 0.0);
/// Aligns a TextField's input Text with the bottommost location within the
/// TextField.
static const TextAlignVertical bottom = TextAlignVertical(y: 1.0);
@override
String toString() {
return '$runtimeType(y: $y)';
}
}
...@@ -633,3 +633,44 @@ class _MixedAlignment extends AlignmentGeometry { ...@@ -633,3 +633,44 @@ class _MixedAlignment extends AlignmentGeometry {
return null; return null;
} }
} }
/// The vertical alignment of text within an input box.
///
/// A single [y] value that can range from -1.0 to 1.0. -1.0 aligns to the top
/// of an input box so that the top of the first line of text fits within the
/// box and its padding. 0.0 aligns to the center of the box. 1.0 aligns so that
/// the bottom of the last line of text aligns with the bottom interior edge of
/// the input box.
///
/// See also:
///
/// * [TextField.textAlignVertical], which is passed on to the [InputDecorator].
/// * [CupertinoTextField.textAlignVertical], which behaves in the same way as
/// the parameter in TextField.
/// * [InputDecorator.textAlignVertical], which defines the alignment of
/// prefix, input, and suffix within an [InputDecorator].
class TextAlignVertical {
/// Creates a TextAlignVertical from any y value between -1.0 and 1.0.
const TextAlignVertical({
@required this.y,
}) : assert(y != null),
assert(y >= -1.0 && y <= 1.0);
/// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost
/// locations of the top and bottom of the input box.
final double y;
/// Aligns a TextField's input Text with the topmost location within a
/// TextField's input box.
static const TextAlignVertical top = TextAlignVertical(y: -1.0);
/// Aligns a TextField's input Text to the center of the TextField.
static const TextAlignVertical center = TextAlignVertical(y: 0.0);
/// Aligns a TextField's input Text with the bottommost location within a
/// TextField.
static const TextAlignVertical bottom = TextAlignVertical(y: 1.0);
@override
String toString() {
return '$runtimeType(y: $y)';
}
}
...@@ -1252,7 +1252,7 @@ class RenderEditable extends RenderBox { ...@@ -1252,7 +1252,7 @@ class RenderEditable extends RenderBox {
double get preferredLineHeight => _textPainter.preferredLineHeight; double get preferredLineHeight => _textPainter.preferredLineHeight;
double _preferredHeight(double width) { double _preferredHeight(double width) {
// Lock height to maxLines if needed // Lock height to maxLines if needed.
final bool lockedMax = maxLines != null && minLines == null; final bool lockedMax = maxLines != null && minLines == null;
final bool lockedBoth = minLines != null && minLines == maxLines; final bool lockedBoth = minLines != null && minLines == maxLines;
final bool singleLine = maxLines == 1; final bool singleLine = maxLines == 1;
...@@ -1260,7 +1260,7 @@ class RenderEditable extends RenderBox { ...@@ -1260,7 +1260,7 @@ class RenderEditable extends RenderBox {
return preferredLineHeight * maxLines; return preferredLineHeight * maxLines;
} }
// Clamp height to minLines or maxLines if needed // Clamp height to minLines or maxLines if needed.
final bool minLimited = minLines != null && minLines > 1; final bool minLimited = minLines != null && minLines > 1;
final bool maxLimited = maxLines != null; final bool maxLimited = maxLines != null;
if (minLimited || maxLimited) { if (minLimited || maxLimited) {
...@@ -1273,7 +1273,7 @@ class RenderEditable extends RenderBox { ...@@ -1273,7 +1273,7 @@ class RenderEditable extends RenderBox {
} }
} }
// Set the height based on the content // Set the height based on the content.
if (width == double.infinity) { if (width == double.infinity) {
final String text = _textPainter.text.toPlainText(); final String text = _textPainter.text.toPlainText();
int lines = 1; int lines = 1;
......
...@@ -173,7 +173,6 @@ void main() { ...@@ -173,7 +173,6 @@ void main() {
EditableText.debugDeterministicCursor = false; EditableText.debugDeterministicCursor = false;
}); });
testWidgets( testWidgets(
'takes available space horizontally and takes intrinsic space vertically no-strut', 'takes available space horizontally and takes intrinsic space vertically no-strut',
(WidgetTester tester) async { (WidgetTester tester) async {
...@@ -628,10 +627,12 @@ void main() { ...@@ -628,10 +627,12 @@ void main() {
testWidgets( testWidgets(
'prefix widget is in front of the text', 'prefix widget is in front of the text',
(WidgetTester tester) async { (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
await tester.pumpWidget( await tester.pumpWidget(
CupertinoApp( CupertinoApp(
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
focusNode: focusNode,
prefix: const Icon(CupertinoIcons.add), prefix: const Icon(CupertinoIcons.add),
controller: TextEditingController(text: 'input'), controller: TextEditingController(text: 'input'),
), ),
...@@ -693,11 +694,13 @@ void main() { ...@@ -693,11 +694,13 @@ void main() {
testWidgets( testWidgets(
'suffix widget is after the text', 'suffix widget is after the text',
(WidgetTester tester) async { (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
await tester.pumpWidget( await tester.pumpWidget(
const CupertinoApp( CupertinoApp(
home: Center( home: Center(
child: CupertinoTextField( child: CupertinoTextField(
suffix: Icon(CupertinoIcons.add), focusNode: focusNode,
suffix: const Icon(CupertinoIcons.add),
), ),
), ),
), ),
...@@ -3087,6 +3090,9 @@ void main() { ...@@ -3087,6 +3090,9 @@ void main() {
), ),
), ),
); );
tester.binding.window.physicalSizeTestValue = null;
tester.binding.window.devicePixelRatioTestValue = null;
}); });
testWidgets('selecting multiple words works', (WidgetTester tester) async { testWidgets('selecting multiple words works', (WidgetTester tester) async {
...@@ -3155,6 +3161,9 @@ void main() { ...@@ -3155,6 +3161,9 @@ void main() {
), ),
), ),
); );
tester.binding.window.physicalSizeTestValue = null;
tester.binding.window.devicePixelRatioTestValue = null;
}); });
testWidgets('selecting multiline works', (WidgetTester tester) async { testWidgets('selecting multiline works', (WidgetTester tester) async {
...@@ -3227,6 +3236,419 @@ void main() { ...@@ -3227,6 +3236,419 @@ void main() {
), ),
), ),
); );
tester.binding.window.physicalSizeTestValue = null;
tester.binding.window.devicePixelRatioTestValue = null;
});
});
group('textAlignVertical position', () {
group('simple case', () {
testWidgets('align top (default)', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Size size = Size(200.0, 200.0);
await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: size.width,
height: size.height,
child: CupertinoTextField(
focusNode: focusNode,
expands: true,
maxLines: null,
),
),
),
),
),
);
// Fills the whole container since expands is true.
expect(tester.getSize(find.byType(CupertinoTextField)), size);
// Tapping anywhere inside focuses it.
expect(focusNode.hasFocus, false);
await tester.tapAt(tester.getTopLeft(find.byType(CupertinoTextField)));
await tester.pumpAndSettle();
expect(focusNode.hasFocus, true);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(focusNode.hasFocus, false);
final Offset justInside = tester
.getBottomLeft(find.byType(CupertinoTextField))
.translate(0.0, -1.0);
await tester.tapAt(justInside);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(focusNode.hasFocus, true);
// The EditableText is at the top.
expect(tester.getTopLeft(find.byType(CupertinoTextField)).dy, closeTo(size.height, .0001));
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(206.0, .0001));
});
testWidgets('align center', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Size size = Size(200.0, 200.0);
await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: size.width,
height: size.height,
child: CupertinoTextField(
textAlignVertical: TextAlignVertical.center,
focusNode: focusNode,
expands: true,
maxLines: null,
),
),
),
),
),
);
// Fills the whole container since expands is true.
expect(tester.getSize(find.byType(CupertinoTextField)), size);
// Tapping anywhere inside focuses it.
expect(focusNode.hasFocus, false);
await tester.tapAt(tester.getTopLeft(find.byType(CupertinoTextField)));
await tester.pumpAndSettle();
expect(focusNode.hasFocus, true);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(focusNode.hasFocus, false);
final Offset justInside = tester
.getBottomLeft(find.byType(CupertinoTextField))
.translate(0.0, -1.0);
await tester.tapAt(justInside);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(focusNode.hasFocus, true);
// The EditableText is at the center.
expect(tester.getTopLeft(find.byType(CupertinoTextField)).dy, closeTo(size.height, .0001));
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(291.5, .0001));
});
testWidgets('align bottom', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Size size = Size(200.0, 200.0);
await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: size.width,
height: size.height,
child: CupertinoTextField(
textAlignVertical: TextAlignVertical.bottom,
focusNode: focusNode,
expands: true,
maxLines: null,
),
),
),
),
),
);
// Fills the whole container since expands is true.
expect(tester.getSize(find.byType(CupertinoTextField)), size);
// Tapping anywhere inside focuses it.
expect(focusNode.hasFocus, false);
await tester.tapAt(tester.getTopLeft(find.byType(CupertinoTextField)));
await tester.pumpAndSettle();
expect(focusNode.hasFocus, true);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(focusNode.hasFocus, false);
final Offset justInside = tester
.getBottomLeft(find.byType(CupertinoTextField))
.translate(0.0, -1.0);
await tester.tapAt(justInside);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(focusNode.hasFocus, true);
// The EditableText is at the bottom.
expect(tester.getTopLeft(find.byType(CupertinoTextField)).dy, closeTo(size.height, .0001));
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(377.0, .0001));
});
testWidgets('align as a double', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Size size = Size(200.0, 200.0);
await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: size.width,
height: size.height,
child: CupertinoTextField(
textAlignVertical: const TextAlignVertical(y: 0.75),
focusNode: focusNode,
expands: true,
maxLines: null,
),
),
),
),
),
);
// Fills the whole container since expands is true.
expect(tester.getSize(find.byType(CupertinoTextField)), size);
// Tapping anywhere inside focuses it.
expect(focusNode.hasFocus, false);
await tester.tapAt(tester.getTopLeft(find.byType(CupertinoTextField)));
await tester.pumpAndSettle();
expect(focusNode.hasFocus, true);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(focusNode.hasFocus, false);
final Offset justInside = tester
.getBottomLeft(find.byType(CupertinoTextField))
.translate(0.0, -1.0);
await tester.tapAt(justInside);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(focusNode.hasFocus, true);
// The EditableText is near the bottom.
expect(tester.getTopLeft(find.byType(CupertinoTextField)).dy, closeTo(size.height, .0001));
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(355.625, .0001));
});
});
group('tall prefix', () {
testWidgets('align center (default when prefix)', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Size size = Size(200.0, 200.0);
await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: size.width,
height: size.height,
child: CupertinoTextField(
focusNode: focusNode,
expands: true,
maxLines: null,
prefix: Container(
height: 100,
width: 10,
),
),
),
),
),
),
);
// Fills the whole container since expands is true.
expect(tester.getSize(find.byType(CupertinoTextField)), size);
// Tapping anywhere inside focuses it. This includes tapping on the
// prefix, because in this case it is transparent.
expect(focusNode.hasFocus, false);
await tester.tapAt(tester.getTopLeft(find.byType(CupertinoTextField)));
await tester.pumpAndSettle();
expect(focusNode.hasFocus, true);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(focusNode.hasFocus, false);
final Offset justInside = tester
.getBottomLeft(find.byType(CupertinoTextField))
.translate(0.0, -1.0);
await tester.tapAt(justInside);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(focusNode.hasFocus, true);
// The EditableText is at the center. Same as without prefix.
expect(tester.getTopLeft(find.byType(CupertinoTextField)).dy, closeTo(size.height, .0001));
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(291.5, .0001));
});
testWidgets('align top', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Size size = Size(200.0, 200.0);
await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: size.width,
height: size.height,
child: CupertinoTextField(
textAlignVertical: TextAlignVertical.top,
focusNode: focusNode,
expands: true,
maxLines: null,
prefix: Container(
height: 100,
width: 10,
),
),
),
),
),
),
);
// Fills the whole container since expands is true.
expect(tester.getSize(find.byType(CupertinoTextField)), size);
// Tapping anywhere inside focuses it. This includes tapping on the
// prefix, because in this case it is transparent.
expect(focusNode.hasFocus, false);
await tester.tapAt(tester.getTopLeft(find.byType(CupertinoTextField)));
await tester.pumpAndSettle();
expect(focusNode.hasFocus, true);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(focusNode.hasFocus, false);
final Offset justInside = tester
.getBottomLeft(find.byType(CupertinoTextField))
.translate(0.0, -1.0);
await tester.tapAt(justInside);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(focusNode.hasFocus, true);
// The prefix is at the top, and the EditableText is centered within its
// height.
expect(tester.getTopLeft(find.byType(CupertinoTextField)).dy, closeTo(size.height, .0001));
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(241.5, .0001));
});
testWidgets('align bottom', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Size size = Size(200.0, 200.0);
await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: size.width,
height: size.height,
child: CupertinoTextField(
textAlignVertical: TextAlignVertical.bottom,
focusNode: focusNode,
expands: true,
maxLines: null,
prefix: Container(
height: 100,
width: 10,
),
),
),
),
),
),
);
// Fills the whole container since expands is true.
expect(tester.getSize(find.byType(CupertinoTextField)), size);
// Tapping anywhere inside focuses it. This includes tapping on the
// prefix, because in this case it is transparent.
expect(focusNode.hasFocus, false);
await tester.tapAt(tester.getTopLeft(find.byType(CupertinoTextField)));
await tester.pumpAndSettle();
expect(focusNode.hasFocus, true);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(focusNode.hasFocus, false);
final Offset justInside = tester
.getBottomLeft(find.byType(CupertinoTextField))
.translate(0.0, -1.0);
await tester.tapAt(justInside);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(focusNode.hasFocus, true);
// The prefix is at the bottom, and the EditableText is centered within
// its height.
expect(tester.getTopLeft(find.byType(CupertinoTextField)).dy, closeTo(size.height, .0001));
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(341.5, .0001));
});
testWidgets('align as a double', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
const Size size = Size(200.0, 200.0);
await tester.pumpWidget(
CupertinoApp(
debugShowCheckedModeBanner: false,
home: CupertinoPageScaffold(
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: size.width,
height: size.height,
child: CupertinoTextField(
textAlignVertical: const TextAlignVertical(y: 0.75),
focusNode: focusNode,
expands: true,
maxLines: null,
prefix: Container(
height: 100,
width: 10,
),
),
),
),
),
),
);
// Fills the whole container since expands is true.
expect(tester.getSize(find.byType(CupertinoTextField)), size);
// Tapping anywhere inside focuses it. This includes tapping on the
// prefix, because in this case it is transparent.
expect(focusNode.hasFocus, false);
await tester.tapAt(tester.getTopLeft(find.byType(CupertinoTextField)));
await tester.pumpAndSettle();
expect(focusNode.hasFocus, true);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(focusNode.hasFocus, false);
final Offset justInside = tester
.getBottomLeft(find.byType(CupertinoTextField))
.translate(0.0, -1.0);
await tester.tapAt(justInside);
await tester.pumpAndSettle();
await tester.pump(const Duration(milliseconds: 300));
expect(focusNode.hasFocus, true);
// The EditableText is near the bottom.
expect(tester.getTopLeft(find.byType(CupertinoTextField)).dy, closeTo(size.height, .0001));
expect(tester.getTopLeft(find.byType(EditableText)).dy, closeTo(329.0, .0001));
});
}); });
}); });
} }
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