mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This reverts commit 0ab5ecc86b
.
This commit is contained in:
parent
47596c6203
commit
6536f65048
@ -1569,6 +1569,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
|
|||||||
|
|
||||||
final Offset paintOffset = _paintOffset;
|
final Offset paintOffset = _paintOffset;
|
||||||
|
|
||||||
|
|
||||||
final List<ui.TextBox> boxes = selection.isCollapsed ?
|
final List<ui.TextBox> boxes = selection.isCollapsed ?
|
||||||
<ui.TextBox>[] : _textPainter.getBoxesForSelection(selection);
|
<ui.TextBox>[] : _textPainter.getBoxesForSelection(selection);
|
||||||
if (boxes.isEmpty) {
|
if (boxes.isEmpty) {
|
||||||
|
@ -14,8 +14,6 @@ import 'debug_overflow_indicator.dart';
|
|||||||
import 'object.dart';
|
import 'object.dart';
|
||||||
import 'stack.dart' show RelativeRect;
|
import 'stack.dart' show RelativeRect;
|
||||||
|
|
||||||
typedef BoxConstraintsTransform = BoxConstraints Function(BoxConstraints);
|
|
||||||
|
|
||||||
/// Abstract class for one-child-layout render boxes that provide control over
|
/// Abstract class for one-child-layout render boxes that provide control over
|
||||||
/// the child's position.
|
/// the child's position.
|
||||||
abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
||||||
@ -596,62 +594,59 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [RenderBox] that applies an arbitary transform to its [constraints], and
|
/// Renders a box, imposing no constraints on its child, allowing the child to
|
||||||
/// sizes its child using the normalized output [BoxConstraints], treating any
|
/// render at its "natural" size.
|
||||||
/// overflow as error.
|
|
||||||
///
|
///
|
||||||
/// This [RenderBox] sizes its child using a normalized [BoxConstraints] created
|
/// This allows a child to render at the size it would render if it were alone
|
||||||
/// by applying [constraintsTransform] to this [RenderBox]'s own [constraints].
|
/// on an infinite canvas with no constraints. This box will then attempt to
|
||||||
/// This box will then attempt to adopt the same size, within the limits of its
|
/// adopt the same size, within the limits of its own constraints. If it ends
|
||||||
/// own constraints. If it ends up with a different size, it will align the
|
/// up with a different size, it will align the child based on [alignment].
|
||||||
/// child based on [alignment]. If the box cannot expand enough to accommodate
|
/// If the box cannot expand enough to accommodate the entire child, the
|
||||||
/// the entire child, the child will be clipped if [clipBehavior] is not
|
/// child will be clipped.
|
||||||
/// [Clip.none].
|
|
||||||
///
|
///
|
||||||
/// In debug mode, if the child overflows the box, a warning will be printed on
|
/// In debug mode, if the child overflows the box, a warning will be printed on
|
||||||
/// the console, and black and yellow striped areas will appear where the
|
/// the console, and black and yellow striped areas will appear where the
|
||||||
/// overflow occurs.
|
/// overflow occurs.
|
||||||
///
|
///
|
||||||
/// This [RenderBox] can be used to allow the child to enforce some of its
|
|
||||||
/// intrinsic sizing rules, partially disregard the constraints set by its
|
|
||||||
/// parent render object, and display a warning in debug mode if the parent
|
|
||||||
/// render object fails to provide enough space.
|
|
||||||
///
|
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [ConstraintsTransformBox], the widget that makes use of this
|
|
||||||
/// [RenderObject] and exposes the same functionality.
|
|
||||||
/// * [RenderConstrainedBox], which renders a box which imposes constraints
|
/// * [RenderConstrainedBox], which renders a box which imposes constraints
|
||||||
/// on its child.
|
/// on its child.
|
||||||
/// * [RenderConstrainedOverflowBox], which renders a box that imposes different
|
/// * [RenderConstrainedOverflowBox], which renders a box that imposes different
|
||||||
/// constraints on its child than it gets from its parent, possibly allowing
|
/// constraints on its child than it gets from its parent, possibly allowing
|
||||||
/// the child to overflow the parent.
|
/// the child to overflow the parent.
|
||||||
/// * [RenderUnconstrainedBox] which allows its children to render themselves
|
/// * [RenderSizedOverflowBox], a render object that is a specific size but
|
||||||
/// unconstrained, expands to fit them, and considers overflow to be an error.
|
/// passes its original constraints through to its child, which it allows to
|
||||||
class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
|
/// overflow.
|
||||||
/// Create a render object that sizes itself to the child and modifies the
|
class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
|
||||||
/// [constraints] before passing it down to that child.
|
/// Create a render object that sizes itself to the child but does not
|
||||||
|
/// pass the [constraints] down to that child.
|
||||||
///
|
///
|
||||||
/// The [alignment] and [clipBehavior] must not be null.
|
/// The [alignment] must not be null.
|
||||||
RenderConstraintsTransformBox({
|
RenderUnconstrainedBox({
|
||||||
@required AlignmentGeometry alignment,
|
@required AlignmentGeometry alignment,
|
||||||
@required TextDirection textDirection,
|
@required TextDirection textDirection,
|
||||||
@required BoxConstraintsTransform constraintsTransform,
|
Axis constrainedAxis,
|
||||||
RenderBox child,
|
RenderBox child,
|
||||||
Clip clipBehavior = Clip.none,
|
Clip clipBehavior = Clip.none,
|
||||||
}) : assert(alignment != null),
|
}) : assert(alignment != null),
|
||||||
assert(clipBehavior != null),
|
assert(clipBehavior != null),
|
||||||
_constraintsTransform = constraintsTransform,
|
_constrainedAxis = constrainedAxis,
|
||||||
_clipBehavior = clipBehavior,
|
_clipBehavior = clipBehavior,
|
||||||
super.mixin(alignment, textDirection, child);
|
super.mixin(alignment, textDirection, child);
|
||||||
|
|
||||||
/// @{macro flutter.widgets.constraintsTransform}
|
/// The axis to retain constraints on, if any.
|
||||||
BoxConstraintsTransform get constraintsTransform => _constraintsTransform;
|
///
|
||||||
BoxConstraintsTransform _constraintsTransform;
|
/// If not set, or set to null (the default), neither axis will retain its
|
||||||
set constraintsTransform(BoxConstraintsTransform value) {
|
/// constraints. If set to [Axis.vertical], then vertical constraints will
|
||||||
if (_constraintsTransform == value)
|
/// be retained, and if set to [Axis.horizontal], then horizontal constraints
|
||||||
|
/// will be retained.
|
||||||
|
Axis get constrainedAxis => _constrainedAxis;
|
||||||
|
Axis _constrainedAxis;
|
||||||
|
set constrainedAxis(Axis value) {
|
||||||
|
if (_constrainedAxis == value)
|
||||||
return;
|
return;
|
||||||
_constraintsTransform = value;
|
_constrainedAxis = value;
|
||||||
markNeedsLayout();
|
markNeedsLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -677,10 +672,21 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO
|
|||||||
void performLayout() {
|
void performLayout() {
|
||||||
final BoxConstraints constraints = this.constraints;
|
final BoxConstraints constraints = this.constraints;
|
||||||
if (child != null) {
|
if (child != null) {
|
||||||
assert(constraintsTransform != null);
|
// Let the child lay itself out at it's "natural" size, but if
|
||||||
final BoxConstraints childConstraints = constraintsTransform(constraints);
|
// constrainedAxis is non-null, keep any constraints on that axis.
|
||||||
assert(childConstraints != null);
|
BoxConstraints childConstraints;
|
||||||
assert(childConstraints.isNormalized, '$childConstraints is not normalized');
|
if (constrainedAxis != null) {
|
||||||
|
switch (constrainedAxis) {
|
||||||
|
case Axis.horizontal:
|
||||||
|
childConstraints = BoxConstraints(maxWidth: constraints.maxWidth, minWidth: constraints.minWidth);
|
||||||
|
break;
|
||||||
|
case Axis.vertical:
|
||||||
|
childConstraints = BoxConstraints(maxHeight: constraints.maxHeight, minHeight: constraints.minHeight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
childConstraints = const BoxConstraints();
|
||||||
|
}
|
||||||
child.layout(childConstraints, parentUsesSize: true);
|
child.layout(childConstraints, parentUsesSize: true);
|
||||||
size = constraints.constrain(child.size);
|
size = constraints.constrain(child.size);
|
||||||
alignChild();
|
alignChild();
|
||||||
@ -735,78 +741,6 @@ class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a box, imposing no constraints on its child, allowing the child to
|
|
||||||
/// render at its "natural" size.
|
|
||||||
///
|
|
||||||
/// This allows a child to render at the size it would render if it were alone
|
|
||||||
/// on an infinite canvas with no constraints. This box will then attempt to
|
|
||||||
/// adopt the same size, within the limits of its own constraints. If it ends
|
|
||||||
/// up with a different size, it will align the child based on [alignment].
|
|
||||||
/// If the box cannot expand enough to accommodate the entire child, the
|
|
||||||
/// child will be clipped.
|
|
||||||
///
|
|
||||||
/// In debug mode, if the child overflows the box, a warning will be printed on
|
|
||||||
/// the console, and black and yellow striped areas will appear where the
|
|
||||||
/// overflow occurs.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [RenderConstrainedBox], which renders a box which imposes constraints
|
|
||||||
/// on its child.
|
|
||||||
/// * [RenderConstrainedOverflowBox], which renders a box that imposes different
|
|
||||||
/// constraints on its child than it gets from its parent, possibly allowing
|
|
||||||
/// the child to overflow the parent.
|
|
||||||
/// * [RenderSizedOverflowBox], a render object that is a specific size but
|
|
||||||
/// passes its original constraints through to its child, which it allows to
|
|
||||||
/// overflow.
|
|
||||||
class RenderUnconstrainedBox extends RenderConstraintsTransformBox {
|
|
||||||
/// Create a render object that sizes itself to the child but does not
|
|
||||||
/// pass the [constraints] down to that child.
|
|
||||||
///
|
|
||||||
/// The [alignment] and [clipBehavior] must not be null.
|
|
||||||
RenderUnconstrainedBox({
|
|
||||||
@required AlignmentGeometry alignment,
|
|
||||||
@required TextDirection textDirection,
|
|
||||||
Axis constrainedAxis,
|
|
||||||
RenderBox child,
|
|
||||||
Clip clipBehavior = Clip.none,
|
|
||||||
}) : assert(alignment != null),
|
|
||||||
assert(clipBehavior != null),
|
|
||||||
_constrainedAxis = constrainedAxis,
|
|
||||||
super(alignment: alignment, textDirection: textDirection, child: child, clipBehavior: clipBehavior, constraintsTransform: null) {
|
|
||||||
_constraintsTransform = _convertAxis;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The axis to retain constraints on, if any.
|
|
||||||
///
|
|
||||||
/// If not set, or set to null (the default), neither axis will retain its
|
|
||||||
/// constraints. If set to [Axis.vertical], then vertical constraints will
|
|
||||||
/// be retained, and if set to [Axis.horizontal], then horizontal constraints
|
|
||||||
/// will be retained.
|
|
||||||
Axis get constrainedAxis => _constrainedAxis;
|
|
||||||
Axis _constrainedAxis;
|
|
||||||
set constrainedAxis(Axis value) {
|
|
||||||
if (_constrainedAxis == value)
|
|
||||||
return;
|
|
||||||
_constrainedAxis = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
BoxConstraints _convertAxis(BoxConstraints constriants) {
|
|
||||||
if (constrainedAxis == null) {
|
|
||||||
return const BoxConstraints();
|
|
||||||
}
|
|
||||||
switch (constrainedAxis) {
|
|
||||||
case Axis.horizontal:
|
|
||||||
return constraints.copyWith(maxHeight: double.infinity, minHeight: 0);
|
|
||||||
case Axis.vertical:
|
|
||||||
return constraints.copyWith(maxWidth: double.infinity, minWidth: 0);
|
|
||||||
}
|
|
||||||
assert(false);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A render object that is a specific size but passes its original constraints
|
/// A render object that is a specific size but passes its original constraints
|
||||||
/// through to its child, which it allows to overflow.
|
/// through to its child, which it allows to overflow.
|
||||||
///
|
///
|
||||||
|
@ -30,7 +30,6 @@ export 'package:flutter/rendering.dart' show
|
|||||||
AlignmentGeometryTween,
|
AlignmentGeometryTween,
|
||||||
Axis,
|
Axis,
|
||||||
BoxConstraints,
|
BoxConstraints,
|
||||||
BoxConstraintsTransform,
|
|
||||||
CrossAxisAlignment,
|
CrossAxisAlignment,
|
||||||
CustomClipper,
|
CustomClipper,
|
||||||
CustomPainter,
|
CustomPainter,
|
||||||
@ -2219,114 +2218,6 @@ class ConstrainedBox extends SingleChildRenderObjectWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A widget that applies an arbitary transform to its constraints, and sizes
|
|
||||||
/// its child using the resulting [BoxConstraints], treating any overflow
|
|
||||||
/// as error.
|
|
||||||
///
|
|
||||||
/// This container sizes its child using a [BoxConstraints] created by applying
|
|
||||||
/// [constraintsTransform] to its own constraints. This container will then
|
|
||||||
/// attempt to adopt the same size, within the limits of its own constraints. If
|
|
||||||
/// it ends up with a different size, it will align the child based on
|
|
||||||
/// [alignment]. If the container cannot expand enough to accommodate the entire
|
|
||||||
/// child, the child will be clipped if [clipBehavior] is not [Clip.none].
|
|
||||||
///
|
|
||||||
/// In debug mode, if the child overflows the container, a warning will be
|
|
||||||
/// printed on the console, and black and yellow striped areas will appear where
|
|
||||||
/// the overflow occurs.
|
|
||||||
///
|
|
||||||
/// {@tool snippet}
|
|
||||||
/// This snippet guarantees the child [Card] will be at least as tall as its
|
|
||||||
/// intrinsic height. Unlike [UnconstrainedBox], the child can be taller if the
|
|
||||||
/// the parent's minHeight is constrained. If parent container's maxHeight is
|
|
||||||
/// less than [Card]'s intrinsic height, in debug mode a warning will be given.
|
|
||||||
///
|
|
||||||
/// ```dart
|
|
||||||
/// ConstraintsTransformBox(
|
|
||||||
/// constraintsTransform: (BoxConstraints constraints) => constraints.copyWith(maxHeight: double.infinity),
|
|
||||||
/// child: const Card(child: Text('Hello World!')),
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
/// {@end-tool}
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [ConstrainedBox], which renders a box which imposes constraints
|
|
||||||
/// on its child.
|
|
||||||
/// * [OverflowBox], a widget that imposes additional constraints on its child,
|
|
||||||
/// and allows the child to overflow itself.
|
|
||||||
/// * [UnconstrainedBox] which allows its children to render themselves
|
|
||||||
/// unconstrained, expands to fit them, and considers overflow to be an error.
|
|
||||||
class ConstraintsTransformBox extends SingleChildRenderObjectWidget {
|
|
||||||
/// Creates a widget that uses a function to transform the constraints it
|
|
||||||
/// passes to its child. If the child overflows the parent's constraints, a
|
|
||||||
/// warning will be given in debug mode.
|
|
||||||
///
|
|
||||||
/// The `alignment`, `clipBehavior` and `constraintsTransform` arguments must
|
|
||||||
/// not be null.
|
|
||||||
const ConstraintsTransformBox({
|
|
||||||
Key key,
|
|
||||||
Widget child,
|
|
||||||
this.textDirection,
|
|
||||||
this.alignment = Alignment.center,
|
|
||||||
this.constraintsTransform,
|
|
||||||
this.clipBehavior = Clip.hardEdge,
|
|
||||||
}) : assert(alignment != null),
|
|
||||||
assert(clipBehavior != null),
|
|
||||||
assert(constraintsTransform != null),
|
|
||||||
super(key: key, child: child);
|
|
||||||
|
|
||||||
/// The text direction to use when interpreting the [alignment] if it is an
|
|
||||||
/// [AlignmentDirectional].
|
|
||||||
///
|
|
||||||
/// Defaults to null, in which case [Directionality.of] is used to determine
|
|
||||||
/// the text direction.
|
|
||||||
final TextDirection textDirection;
|
|
||||||
|
|
||||||
/// The alignment to use when laying out the child, if it has a different size
|
|
||||||
/// than this widget.
|
|
||||||
///
|
|
||||||
/// If this is an [AlignmentDirectional], then [textDirection] must not be
|
|
||||||
/// null.
|
|
||||||
///
|
|
||||||
/// See also:
|
|
||||||
///
|
|
||||||
/// * [Alignment] for non-[Directionality]-aware alignments.
|
|
||||||
/// * [AlignmentDirectional] for [Directionality]-aware alignments.
|
|
||||||
final AlignmentGeometry alignment;
|
|
||||||
|
|
||||||
/// @{template flutter.widgets.constraintsTransform}
|
|
||||||
/// The function used to transform the incoming [BoxConstraints], to impose on
|
|
||||||
/// [child].
|
|
||||||
///
|
|
||||||
/// Must not be null. The function must return a non-null and normalized
|
|
||||||
/// [BoxConstraints].
|
|
||||||
/// @{endtemplate}
|
|
||||||
final BoxConstraintsTransform constraintsTransform;
|
|
||||||
|
|
||||||
// TODO(liyuqian): defaults to [Clip.none] once Google references are updated.
|
|
||||||
/// {@macro flutter.widgets.Clip}
|
|
||||||
///
|
|
||||||
/// Defaults to [Clip.hardEdge].
|
|
||||||
final Clip clipBehavior;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void updateRenderObject(BuildContext context, covariant RenderConstraintsTransformBox renderObject) {
|
|
||||||
renderObject
|
|
||||||
..textDirection = textDirection ?? Directionality.of(context)
|
|
||||||
..constraintsTransform = constraintsTransform
|
|
||||||
..alignment = alignment
|
|
||||||
..clipBehavior = clipBehavior;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
RenderConstraintsTransformBox createRenderObject(BuildContext context) => RenderConstraintsTransformBox(
|
|
||||||
textDirection: textDirection ?? Directionality.of(context),
|
|
||||||
alignment: alignment,
|
|
||||||
constraintsTransform: constraintsTransform,
|
|
||||||
clipBehavior: clipBehavior,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A widget that imposes no constraints on its child, allowing it to render
|
/// A widget that imposes no constraints on its child, allowing it to render
|
||||||
/// at its "natural" size.
|
/// at its "natural" size.
|
||||||
///
|
///
|
||||||
@ -2351,20 +2242,20 @@ class ConstraintsTransformBox extends SingleChildRenderObjectWidget {
|
|||||||
/// * [OverflowBox], a widget that imposes different constraints on its child
|
/// * [OverflowBox], a widget that imposes different constraints on its child
|
||||||
/// than it gets from its parent, possibly allowing the child to overflow
|
/// than it gets from its parent, possibly allowing the child to overflow
|
||||||
/// the parent.
|
/// the parent.
|
||||||
class UnconstrainedBox extends StatelessWidget {
|
class UnconstrainedBox extends SingleChildRenderObjectWidget {
|
||||||
/// Creates a widget that imposes no constraints on its child, allowing it to
|
/// Creates a widget that imposes no constraints on its child, allowing it to
|
||||||
/// render at its "natural" size. If the child overflows the parents
|
/// render at its "natural" size. If the child overflows the parents
|
||||||
/// constraints, a warning will be given in debug mode.
|
/// constraints, a warning will be given in debug mode.
|
||||||
const UnconstrainedBox({
|
const UnconstrainedBox({
|
||||||
Key key,
|
Key key,
|
||||||
this.child,
|
Widget child,
|
||||||
this.textDirection,
|
this.textDirection,
|
||||||
this.alignment = Alignment.center,
|
this.alignment = Alignment.center,
|
||||||
this.constrainedAxis,
|
this.constrainedAxis,
|
||||||
this.clipBehavior = Clip.hardEdge,
|
this.clipBehavior = Clip.hardEdge,
|
||||||
}) : assert(alignment != null),
|
}) : assert(alignment != null),
|
||||||
assert(clipBehavior != null),
|
assert(clipBehavior != null),
|
||||||
super(key: key);
|
super(key: key, child: child);
|
||||||
|
|
||||||
/// The text direction to use when interpreting the [alignment] if it is an
|
/// The text direction to use when interpreting the [alignment] if it is an
|
||||||
/// [AlignmentDirectional].
|
/// [AlignmentDirectional].
|
||||||
@ -2395,39 +2286,22 @@ class UnconstrainedBox extends StatelessWidget {
|
|||||||
/// Defaults to [Clip.hardEdge].
|
/// Defaults to [Clip.hardEdge].
|
||||||
final Clip clipBehavior;
|
final Clip clipBehavior;
|
||||||
|
|
||||||
/// The widget below this widget in the tree.
|
@override
|
||||||
///
|
void updateRenderObject(BuildContext context, covariant RenderUnconstrainedBox renderObject) {
|
||||||
/// {@macro flutter.widgets.child}
|
renderObject
|
||||||
final Widget child;
|
..textDirection = textDirection ?? Directionality.of(context)
|
||||||
|
..alignment = alignment
|
||||||
static BoxConstraints _unconstrained(BoxConstraints constraints) => const BoxConstraints();
|
..constrainedAxis = constrainedAxis
|
||||||
static BoxConstraints _widthConstrained(BoxConstraints constraints) => constraints.widthConstraints();
|
..clipBehavior = clipBehavior;
|
||||||
static BoxConstraints _heightConstrained(BoxConstraints constraints) => constraints.heightConstraints();
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
RenderUnconstrainedBox createRenderObject(BuildContext context) => RenderUnconstrainedBox(
|
||||||
BoxConstraintsTransform constraintsTransform;
|
textDirection: textDirection ?? Directionality.of(context),
|
||||||
|
alignment: alignment,
|
||||||
if (constrainedAxis != null) {
|
constrainedAxis: constrainedAxis,
|
||||||
switch (constrainedAxis) {
|
clipBehavior: clipBehavior,
|
||||||
case Axis.horizontal:
|
);
|
||||||
constraintsTransform = _widthConstrained;
|
|
||||||
break;
|
|
||||||
case Axis.vertical:
|
|
||||||
constraintsTransform = _heightConstrained;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
constraintsTransform ??= _unconstrained;
|
|
||||||
|
|
||||||
return ConstraintsTransformBox(
|
|
||||||
child: child,
|
|
||||||
textDirection: textDirection ?? Directionality.of(context),
|
|
||||||
alignment: alignment,
|
|
||||||
clipBehavior: clipBehavior,
|
|
||||||
constraintsTransform: constraintsTransform,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
@ -2397,9 +2397,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static BoxConstraints _unmodified(BoxConstraints constraints) => constraints;
|
|
||||||
static BoxConstraints _removeMaxHeightConstraint(BoxConstraints constraints) => constraints.copyWith(maxHeight: double.infinity);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
assert(debugCheckHasMediaQuery(context));
|
assert(debugCheckHasMediaQuery(context));
|
||||||
@ -2407,80 +2404,74 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
super.build(context); // See AutomaticKeepAliveClientMixin.
|
super.build(context); // See AutomaticKeepAliveClientMixin.
|
||||||
|
|
||||||
final TextSelectionControls controls = widget.selectionControls;
|
final TextSelectionControls controls = widget.selectionControls;
|
||||||
final bool ignoreOverflow = _isMultiline || widget.clipBehavior == Clip.none;
|
return MouseRegion(
|
||||||
|
cursor: widget.mouseCursor ?? SystemMouseCursors.text,
|
||||||
return ConstraintsTransformBox(
|
child: Scrollable(
|
||||||
constraintsTransform: ignoreOverflow ? _unmodified : _removeMaxHeightConstraint,
|
excludeFromSemantics: true,
|
||||||
clipBehavior: widget.clipBehavior,
|
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
|
||||||
child: MouseRegion(
|
controller: _scrollController,
|
||||||
cursor: widget.mouseCursor ?? SystemMouseCursors.text,
|
physics: widget.scrollPhysics,
|
||||||
child: Scrollable(
|
dragStartBehavior: widget.dragStartBehavior,
|
||||||
excludeFromSemantics: true,
|
restorationId: widget.restorationId,
|
||||||
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
|
viewportBuilder: (BuildContext context, ViewportOffset offset) {
|
||||||
controller: _scrollController,
|
return CompositedTransformTarget(
|
||||||
physics: widget.scrollPhysics,
|
link: _toolbarLayerLink,
|
||||||
dragStartBehavior: widget.dragStartBehavior,
|
child: Semantics(
|
||||||
restorationId: widget.restorationId,
|
onCopy: _semanticsOnCopy(controls),
|
||||||
viewportBuilder: (BuildContext context, ViewportOffset offset) {
|
onCut: _semanticsOnCut(controls),
|
||||||
return CompositedTransformTarget(
|
onPaste: _semanticsOnPaste(controls),
|
||||||
link: _toolbarLayerLink,
|
child: _Editable(
|
||||||
child: Semantics(
|
key: _editableKey,
|
||||||
onCopy: _semanticsOnCopy(controls),
|
startHandleLayerLink: _startHandleLayerLink,
|
||||||
onCut: _semanticsOnCut(controls),
|
endHandleLayerLink: _endHandleLayerLink,
|
||||||
onPaste: _semanticsOnPaste(controls),
|
textSpan: buildTextSpan(),
|
||||||
child: _Editable(
|
value: _value,
|
||||||
key: _editableKey,
|
cursorColor: _cursorColor,
|
||||||
startHandleLayerLink: _startHandleLayerLink,
|
backgroundCursorColor: widget.backgroundCursorColor,
|
||||||
endHandleLayerLink: _endHandleLayerLink,
|
showCursor: EditableText.debugDeterministicCursor
|
||||||
textSpan: buildTextSpan(),
|
? ValueNotifier<bool>(widget.showCursor)
|
||||||
value: _value,
|
: _cursorVisibilityNotifier,
|
||||||
cursorColor: _cursorColor,
|
forceLine: widget.forceLine,
|
||||||
backgroundCursorColor: widget.backgroundCursorColor,
|
readOnly: widget.readOnly,
|
||||||
showCursor: EditableText.debugDeterministicCursor
|
hasFocus: _hasFocus,
|
||||||
? ValueNotifier<bool>(widget.showCursor)
|
maxLines: widget.maxLines,
|
||||||
: _cursorVisibilityNotifier,
|
minLines: widget.minLines,
|
||||||
forceLine: widget.forceLine,
|
expands: widget.expands,
|
||||||
readOnly: widget.readOnly,
|
strutStyle: widget.strutStyle,
|
||||||
hasFocus: _hasFocus,
|
selectionColor: widget.selectionColor,
|
||||||
maxLines: widget.maxLines,
|
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
||||||
minLines: widget.minLines,
|
textAlign: widget.textAlign,
|
||||||
expands: widget.expands,
|
textDirection: _textDirection,
|
||||||
strutStyle: widget.strutStyle,
|
locale: widget.locale,
|
||||||
selectionColor: widget.selectionColor,
|
textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
|
||||||
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
textWidthBasis: widget.textWidthBasis,
|
||||||
textAlign: widget.textAlign,
|
obscuringCharacter: widget.obscuringCharacter,
|
||||||
textDirection: _textDirection,
|
obscureText: widget.obscureText,
|
||||||
locale: widget.locale,
|
autocorrect: widget.autocorrect,
|
||||||
textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
|
smartDashesType: widget.smartDashesType,
|
||||||
textWidthBasis: widget.textWidthBasis,
|
smartQuotesType: widget.smartQuotesType,
|
||||||
obscuringCharacter: widget.obscuringCharacter,
|
enableSuggestions: widget.enableSuggestions,
|
||||||
obscureText: widget.obscureText,
|
offset: offset,
|
||||||
autocorrect: widget.autocorrect,
|
onSelectionChanged: _handleSelectionChanged,
|
||||||
smartDashesType: widget.smartDashesType,
|
onCaretChanged: _handleCaretChanged,
|
||||||
smartQuotesType: widget.smartQuotesType,
|
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
||||||
enableSuggestions: widget.enableSuggestions,
|
cursorWidth: widget.cursorWidth,
|
||||||
offset: offset,
|
cursorHeight: widget.cursorHeight,
|
||||||
onSelectionChanged: _handleSelectionChanged,
|
cursorRadius: widget.cursorRadius,
|
||||||
onCaretChanged: _handleCaretChanged,
|
cursorOffset: widget.cursorOffset,
|
||||||
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
selectionHeightStyle: widget.selectionHeightStyle,
|
||||||
cursorWidth: widget.cursorWidth,
|
selectionWidthStyle: widget.selectionWidthStyle,
|
||||||
cursorHeight: widget.cursorHeight,
|
paintCursorAboveText: widget.paintCursorAboveText,
|
||||||
cursorRadius: widget.cursorRadius,
|
enableInteractiveSelection: widget.enableInteractiveSelection,
|
||||||
cursorOffset: widget.cursorOffset,
|
textSelectionDelegate: this,
|
||||||
selectionHeightStyle: widget.selectionHeightStyle,
|
devicePixelRatio: _devicePixelRatio,
|
||||||
selectionWidthStyle: widget.selectionWidthStyle,
|
promptRectRange: _currentPromptRectRange,
|
||||||
paintCursorAboveText: widget.paintCursorAboveText,
|
promptRectColor: widget.autocorrectionTextRectColor,
|
||||||
enableInteractiveSelection: widget.enableInteractiveSelection,
|
clipBehavior: widget.clipBehavior,
|
||||||
textSelectionDelegate: this,
|
|
||||||
devicePixelRatio: _devicePixelRatio,
|
|
||||||
promptRectRange: _currentPromptRectRange,
|
|
||||||
promptRectColor: widget.autocorrectionTextRectColor,
|
|
||||||
clipBehavior: widget.clipBehavior,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -380,74 +380,6 @@ void main() {
|
|||||||
expect(unconstrained.getMaxIntrinsicWidth(100.0), equals(200.0));
|
expect(unconstrained.getMaxIntrinsicWidth(100.0), equals(200.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
group('ConstraintsTransfromBox', () {
|
|
||||||
FlutterErrorDetails firstErrorDetails;
|
|
||||||
void exhaustErrors() {
|
|
||||||
FlutterErrorDetails next;
|
|
||||||
do {
|
|
||||||
next = renderer.takeFlutterErrorDetails();
|
|
||||||
firstErrorDetails ??= next;
|
|
||||||
} while (next != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
tearDown(() {
|
|
||||||
firstErrorDetails = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('throws if the resulting constraints are not normalized', () {
|
|
||||||
final RenderConstrainedBox child = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(height: 0));
|
|
||||||
final RenderConstraintsTransformBox box = RenderConstraintsTransformBox(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
constraintsTransform: (BoxConstraints constraints) => const BoxConstraints(maxHeight: -1, minHeight: 200),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
layout(box, constraints: const BoxConstraints(), onErrors: exhaustErrors);
|
|
||||||
|
|
||||||
expect(firstErrorDetails?.toString(), contains('is not normalized'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('overflow is reported when insufficient size is given', () {
|
|
||||||
final RenderConstrainedBox child = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: double.maxFinite));
|
|
||||||
final RenderConstraintsTransformBox box = RenderConstraintsTransformBox(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
constraintsTransform: (BoxConstraints constraints) => constraints.copyWith(maxWidth: double.infinity),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
// No error reported.
|
|
||||||
layout(box, constraints: const BoxConstraints(), phase: EnginePhase.composite, onErrors: expectOverflowedErrors);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('throws when null is returned by the transform function', () {
|
|
||||||
final RenderConstrainedBox child = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 200));
|
|
||||||
final RenderConstraintsTransformBox box = RenderConstraintsTransformBox(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
constraintsTransform: (BoxConstraints constraints) => null,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
layout(box, constraints: const BoxConstraints(), onErrors: exhaustErrors);
|
|
||||||
expect(firstErrorDetails.toString(), contains('!= null'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("the transform function can't be set to null", () {
|
|
||||||
final RenderConstrainedBox child = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 200));
|
|
||||||
final RenderConstraintsTransformBox box = RenderConstraintsTransformBox(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
constraintsTransform: null,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
|
|
||||||
layout(box, constraints: const BoxConstraints(), onErrors: exhaustErrors);
|
|
||||||
expect(firstErrorDetails.toString(), contains('!= null'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test ('getMinIntrinsicWidth error handling', () {
|
test ('getMinIntrinsicWidth error handling', () {
|
||||||
final RenderUnconstrainedBox unconstrained = RenderUnconstrainedBox(
|
final RenderUnconstrainedBox unconstrained = RenderUnconstrainedBox(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
|
@ -38,7 +38,7 @@ void main() {
|
|||||||
final dynamic exception = tester.takeException();
|
final dynamic exception = tester.takeException();
|
||||||
expect(exception, isFlutterError);
|
expect(exception, isFlutterError);
|
||||||
expect(exception.diagnostics.first.level, DiagnosticLevel.summary);
|
expect(exception.diagnostics.first.level, DiagnosticLevel.summary);
|
||||||
expect(exception.diagnostics.first.toString(), startsWith('A RenderConstraintsTransformBox overflowed by '));
|
expect(exception.diagnostics.first.toString(), startsWith('A RenderUnconstrainedBox overflowed by '));
|
||||||
expect(find.byType(UnconstrainedBox), paints..rect());
|
expect(find.byType(UnconstrainedBox), paints..rect());
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
|
@ -339,7 +339,7 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('UnconstrainedBox can set and update clipBehavior', (WidgetTester tester) async {
|
testWidgets('UnconstrainedBox can set and update clipBehavior', (WidgetTester tester) async {
|
||||||
await tester.pumpWidget(const UnconstrainedBox());
|
await tester.pumpWidget(const UnconstrainedBox());
|
||||||
final RenderConstraintsTransformBox renderObject = tester.allRenderObjects.whereType<RenderConstraintsTransformBox>().first;
|
final RenderUnconstrainedBox renderObject = tester.allRenderObjects.whereType<RenderUnconstrainedBox>().first;
|
||||||
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
|
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
|
||||||
|
|
||||||
await tester.pumpWidget(const UnconstrainedBox(clipBehavior: Clip.antiAlias));
|
await tester.pumpWidget(const UnconstrainedBox(clipBehavior: Clip.antiAlias));
|
||||||
|
@ -5385,130 +5385,6 @@ void main() {
|
|||||||
expectToAssert(const TextEditingValue(text: 'test', composing: TextRange(start: -1, end: 9)), false);
|
expectToAssert(const TextEditingValue(text: 'test', composing: TextRange(start: -1, end: 9)), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('shows overflow indicator for single line text with insufficient height', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(MediaQuery(
|
|
||||||
data: const MediaQueryData(devicePixelRatio: 1.0),
|
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 10,
|
|
||||||
child: EditableText(
|
|
||||||
maxLines: 1,
|
|
||||||
backgroundCursorColor: Colors.grey,
|
|
||||||
controller: controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
style: textStyle,
|
|
||||||
cursorColor: cursorColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
|
|
||||||
final dynamic exception = tester.takeException();
|
|
||||||
expect(exception, isFlutterError);
|
|
||||||
expect(exception.diagnostics.first.level, DiagnosticLevel.summary);
|
|
||||||
expect(exception.diagnostics.first.toString(), startsWith('A RenderConstraintsTransformBox overflowed by '));
|
|
||||||
|
|
||||||
// Can be fixed by given enough height;
|
|
||||||
await tester.pumpWidget(MediaQuery(
|
|
||||||
data: const MediaQueryData(devicePixelRatio: 1.0),
|
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 10,
|
|
||||||
child: EditableText(
|
|
||||||
maxLines: 1,
|
|
||||||
backgroundCursorColor: Colors.grey,
|
|
||||||
controller: controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
style: textStyle,
|
|
||||||
cursorColor: cursorColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
|
|
||||||
expect(tester.takeException(), isNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('No overflow indicator for multiline line text or no clipping', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(MediaQuery(
|
|
||||||
data: const MediaQueryData(devicePixelRatio: 1.0),
|
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 10,
|
|
||||||
child: EditableText(
|
|
||||||
maxLines: 1,
|
|
||||||
backgroundCursorColor: Colors.grey,
|
|
||||||
controller: controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
style: textStyle,
|
|
||||||
cursorColor: cursorColor,
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
|
|
||||||
expect(tester.takeException(), isNull);
|
|
||||||
|
|
||||||
await tester.pumpWidget(MediaQuery(
|
|
||||||
data: const MediaQueryData(devicePixelRatio: 1.0),
|
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 50,
|
|
||||||
child: EditableText(
|
|
||||||
backgroundCursorColor: Colors.grey,
|
|
||||||
controller: controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
style: textStyle,
|
|
||||||
cursorColor: cursorColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
|
|
||||||
expect(tester.takeException(), isNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('RenderEditable can stretch vertically', (WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(MediaQuery(
|
|
||||||
data: const MediaQueryData(devicePixelRatio: 1.0),
|
|
||||||
child: Directionality(
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
child: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
height: 400,
|
|
||||||
child: EditableText(
|
|
||||||
maxLines: 1,
|
|
||||||
backgroundCursorColor: Colors.grey,
|
|
||||||
controller: controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
style: textStyle,
|
|
||||||
cursorColor: cursorColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
|
|
||||||
final dynamic exception = tester.takeException();
|
|
||||||
expect(exception, isNull);
|
|
||||||
|
|
||||||
final EditableTextState state = tester.state(find.byType(EditableText));
|
|
||||||
expect(state.renderEditable.size.height, 400);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Regression test for https://github.com/flutter/flutter/issues/65374.
|
// Regression test for https://github.com/flutter/flutter/issues/65374.
|
||||||
testWidgets('Length formatter will not cause crash while the TextEditingValue is composing', (WidgetTester tester) async {
|
testWidgets('Length formatter will not cause crash while the TextEditingValue is composing', (WidgetTester tester) async {
|
||||||
final TextInputFormatter formatter = LengthLimitingTextInputFormatter(5);
|
final TextInputFormatter formatter = LengthLimitingTextInputFormatter(5);
|
||||||
|
@ -632,11 +632,11 @@ void main() {
|
|||||||
),
|
),
|
||||||
children: <InlineSpan>[
|
children: <InlineSpan>[
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
child: SizedBox(width: 70, height: 55, child: TextField()),
|
child: SizedBox(width: 70, height: 25, child: TextField()),
|
||||||
),
|
),
|
||||||
TextSpan(text: ', and my favorite city is: ', style: TextStyle(fontSize: 20)),
|
TextSpan(text: ', and my favorite city is: ', style: TextStyle(fontSize: 20)),
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
child: SizedBox(width: 70, height: 55, child: TextField()),
|
child: SizedBox(width: 70, height: 25, child: TextField()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -743,7 +743,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
TextSpan(text: 'outer', style: TextStyle(fontSize: 20)),
|
TextSpan(text: 'outer', style: TextStyle(fontSize: 20)),
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
child: SizedBox(width: 70, height: 55, child: TextField()),
|
child: SizedBox(width: 70, height: 25, child: TextField()),
|
||||||
),
|
),
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
|
@ -570,7 +570,7 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 40.0,
|
height: 25.0,
|
||||||
child: EditableText(
|
child: EditableText(
|
||||||
key: const ValueKey<String>('text3'),
|
key: const ValueKey<String>('text3'),
|
||||||
controller: TextEditingController(text: 'Hello3'),
|
controller: TextEditingController(text: 'Hello3'),
|
||||||
@ -581,14 +581,14 @@ void main() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 40.0,
|
height: 25.0,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
key: const ValueKey<String>('text4'),
|
key: const ValueKey<String>('text4'),
|
||||||
controller: TextEditingController(text: 'Hello4'),
|
controller: TextEditingController(text: 'Hello4'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 40.0,
|
height: 25.0,
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
key: const ValueKey<String>('text5'),
|
key: const ValueKey<String>('text5'),
|
||||||
controller: TextEditingController(text: 'Hello5'),
|
controller: TextEditingController(text: 'Hello5'),
|
||||||
|
Loading…
Reference in New Issue
Block a user