Commit f9d24f65 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

More docs. (#10214)

parent 04aeef84
...@@ -185,5 +185,5 @@ class ValueNotifier<T> extends ChangeNotifier { ...@@ -185,5 +185,5 @@ class ValueNotifier<T> extends ChangeNotifier {
} }
@override @override
String toString() => '$runtimeType(value: $value)'; String toString() => '$runtimeType#$hashCode($value)';
} }
...@@ -30,9 +30,21 @@ const TextStyle _kLabelStyle = const TextStyle( ...@@ -30,9 +30,21 @@ const TextStyle _kLabelStyle = const TextStyle(
/// ///
/// Requires one of its ancestors to be a [Material] widget. /// Requires one of its ancestors to be a [Material] widget.
/// ///
/// ## Sample code
///
/// ```dart
/// new Chip(
/// avatar: new CircleAvatar(
/// backgroundColor: Colors.grey.shade800,
/// child: new Text('AB'),
/// ),
/// label: new Text('Aaron Burr'),
/// )
/// ```
///
/// See also: /// See also:
/// ///
/// * [CircleAvatar] /// * [CircleAvatar], which shows images or initials of people.
/// * <https://material.google.com/components/chips.html> /// * <https://material.google.com/components/chips.html>
class Chip extends StatelessWidget { class Chip extends StatelessWidget {
/// Creates a material design chip. /// Creates a material design chip.
......
...@@ -15,6 +15,8 @@ import 'typography.dart'; ...@@ -15,6 +15,8 @@ import 'typography.dart';
/// such an image, the user's initials. A given user's initials should /// such an image, the user's initials. A given user's initials should
/// always be paired with the same background color, for consistency. /// always be paired with the same background color, for consistency.
/// ///
/// ## Sample code
///
/// If the avatar is to have an image, the image should be specified in the /// If the avatar is to have an image, the image should be specified in the
/// [backgroundImage] property: /// [backgroundImage] property:
/// ///
...@@ -33,7 +35,7 @@ import 'typography.dart'; ...@@ -33,7 +35,7 @@ import 'typography.dart';
/// new CircleAvatar( /// new CircleAvatar(
/// backgroundColor: Colors.brown.shade800, /// backgroundColor: Colors.brown.shade800,
/// child: new Text('AH'), /// child: new Text('AH'),
/// ); /// )
/// ``` /// ```
/// ///
/// See also: /// See also:
......
...@@ -121,6 +121,22 @@ class InkResponse extends StatefulWidget { ...@@ -121,6 +121,22 @@ class InkResponse extends StatefulWidget {
@override @override
_InkResponseState<InkResponse> createState() => new _InkResponseState<InkResponse>(); _InkResponseState<InkResponse> createState() => new _InkResponseState<InkResponse>();
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
final List<String> gestures = <String>[];
if (onTap != null)
gestures.add('tap');
if (onDoubleTap != null)
gestures.add('double tap');
if (onLongPress != null)
gestures.add('long press');
if (gestures.isEmpty)
gestures.add('<none>');
description.add('gestures: ${gestures.join(", ")}');
description.add('${containedInkWell ? "clipped to " : ""}$highlightShape');
}
} }
class _InkResponseState<T extends InkResponse> extends State<T> { class _InkResponseState<T extends InkResponse> extends State<T> {
......
...@@ -84,7 +84,7 @@ class FittedSizes { ...@@ -84,7 +84,7 @@ class FittedSizes {
/// provides a convenience function, [FractionalOffset.inscribe], for resolving /// provides a convenience function, [FractionalOffset.inscribe], for resolving
/// the sizes to rects, as shown in the example below. /// the sizes to rects, as shown in the example below.
/// ///
/// == Example == /// ## Sample code
/// ///
/// This example paints an [Image] `image` onto the [Rect] `outputRect` on a /// This example paints an [Image] `image` onto the [Rect] `outputRect` on a
/// [Canvas] `canvas`, using a [Paint] paint, applying the [BoxFit] algorithm /// [Canvas] `canvas`, using a [Paint] paint, applying the [BoxFit] algorithm
...@@ -97,6 +97,13 @@ class FittedSizes { ...@@ -97,6 +97,13 @@ class FittedSizes {
/// final Rect outputSubrect = FractionalOffset.center.inscribe(sizes.destination, outputRect); /// final Rect outputSubrect = FractionalOffset.center.inscribe(sizes.destination, outputRect);
/// canvas.drawImageRect(image, inputSubrect, outputSubrect, paint); /// canvas.drawImageRect(image, inputSubrect, outputSubrect, paint);
/// ``` /// ```
///
/// See also:
///
/// * [FittedBox], a widget that applies this algorithm to another widget.
/// * [paintImage], a function that applies this algorithm to images for painting.
/// * [DecoratedBox], [BoxDecoration], and [DecorationImage], which together
/// provide access to [paintImage] at the widgets layer.
FittedSizes applyBoxFit(BoxFit fit, Size inputSize, Size outputSize) { FittedSizes applyBoxFit(BoxFit fit, Size inputSize, Size outputSize) {
Size sourceSize, destinationSize; Size sourceSize, destinationSize;
switch (fit) { switch (fit) {
......
...@@ -1323,15 +1323,21 @@ class DecorationImage { ...@@ -1323,15 +1323,21 @@ class DecorationImage {
}) : assert(image != null); }) : assert(image != null);
/// The image to be painted into the decoration. /// The image to be painted into the decoration.
///
/// Typically this will be an [AssetImage] (for an image shipped with the
/// application) or a [NetworkImage] (for an image obtained from the network).
final ImageProvider image; final ImageProvider image;
/// How the image should be inscribed into the box. /// How the image should be inscribed into the box.
/// ///
/// The default varies based on the other fields. See the discussion at /// The default is [BoxFit.scaleDown] if [centerSlice] is null, and
/// [paintImage]. /// [BoxFit.fill] if [centerSlice] is not null.
///
/// See the discussion at [paintImage] for more details.
final BoxFit fit; final BoxFit fit;
/// How to paint any portions of the box not covered by the image. /// How to paint any portions of the box that would not otherwise be covered
/// by the image.
final ImageRepeat repeat; final ImageRepeat repeat;
/// The center slice for a nine-patch image. /// The center slice for a nine-patch image.
...@@ -1341,6 +1347,14 @@ class DecorationImage { ...@@ -1341,6 +1347,14 @@ class DecorationImage {
/// region of the image above and below the center slice will be stretched /// region of the image above and below the center slice will be stretched
/// only horizontally and the region of the image to the left and right of /// only horizontally and the region of the image to the left and right of
/// the center slice will be stretched only vertically. /// the center slice will be stretched only vertically.
///
/// The stretching will be applied in order to make the image fit into the box
/// specified by [fit]. When [centerSlice] is not null, [fit] defaults to
/// [BoxFit.fill], which distorts the destination image size relative to the
/// image's original aspect ratio. Values of [BoxFit] which do not distort the
/// destination image size will result in [centerSlice] having no effect
/// (since the nine regions of the image will be rendered with the same
/// scaling, as if it wasn't specified).
final Rect centerSlice; final Rect centerSlice;
/// A color filter to apply to the image before painting it. /// A color filter to apply to the image before painting it.
...@@ -1379,6 +1393,22 @@ class DecorationImage { ...@@ -1379,6 +1393,22 @@ class DecorationImage {
/// An immutable description of how to paint a box. /// An immutable description of how to paint a box.
/// ///
/// The [BoxDecoration] class provides a variety of ways to draw a box.
///
/// The box has a [border], a body, and may cast a [shadow].
///
/// The [shape] of the box can be a circle or a rectangle. If it is a rectangle,
/// then the [borderRadius] property controls the roundness of the corners.
///
/// The body of the box is painted in layers. The bottom-most layer is the
/// [color], which fills the box. Above that is the [gradient], which also fills
/// the box. Finally there is the [image], the precise alignment of which is
/// controlled by the [DecorationImage] class.
///
/// The [border] paints over the body; the [shadow], naturally, paints below it.
///
/// ## Sample code
///
/// The following example uses the [Container] widget from the widgets layer to /// The following example uses the [Container] widget from the widgets layer to
/// draw an image with a border: /// draw an image with a border:
/// ///
...@@ -1397,6 +1427,13 @@ class DecorationImage { ...@@ -1397,6 +1427,13 @@ class DecorationImage {
/// ), /// ),
/// ) /// )
/// ``` /// ```
///
/// See also:
///
/// * [DecoratedBox] and [Container], widgets that can be configured with
/// [BoxDecoration] objects.
/// * [CustomPaint], a widget that lets you draw arbitrary graphics.
/// * [Decoration], the base class which lets you define other decorations.
class BoxDecoration extends Decoration { class BoxDecoration extends Decoration {
/// Creates a box decoration. /// Creates a box decoration.
/// ///
......
...@@ -95,9 +95,10 @@ abstract class FlowDelegate { ...@@ -95,9 +95,10 @@ abstract class FlowDelegate {
/// it paint entirely outside the container's clip. /// it paint entirely outside the container's clip.
/// ///
/// To paint a child, call [FlowPaintingContext.paintChild] on the given /// To paint a child, call [FlowPaintingContext.paintChild] on the given
/// [context]. The given context is valid only within the scope of this /// [FlowPaintingContext] (the `context` argument). The given context is valid
/// function call and contains information (such as the size of the container) /// only within the scope of this function call and contains information (such
/// that is useful for picking transformation matrices for the children. /// as the size of the container) that is useful for picking transformation
/// matrices for the children.
/// ///
/// If this function depends on information other than the given context, /// If this function depends on information other than the given context,
/// override [shouldRepaint] to indicate when when the container should /// override [shouldRepaint] to indicate when when the container should
......
...@@ -285,11 +285,21 @@ class RenderConstrainedBox extends RenderProxyBox { ...@@ -285,11 +285,21 @@ class RenderConstrainedBox extends RenderProxyBox {
} }
} }
/// Constrains the child's maxWidth and maxHeight if they're otherwise /// Constrains the child's [BoxConstraints.maxWidth] and
/// unconstrained. /// [BoxConstraints.maxHeight] if they're otherwise unconstrained.
///
/// This has the effect of giving the child a natural dimension in unbounded
/// environments. For example, by providing a [maxHeight] to a widget that
/// normally tries to be as big as possible, the widget will normally size
/// itself to fit its parent, but when placed in a vertical list, it will take
/// on the given height.
///
/// This is useful when composing widgets that normally try to match their
/// parents' size, so that they behave reasonably in lists (which are
/// unbounded).
class RenderLimitedBox extends RenderProxyBox { class RenderLimitedBox extends RenderProxyBox {
/// Creates a render box that imposes a maxWidth or maxHeight on its child if /// Creates a render box that imposes a maximum width or maximum height on its
/// the child is otherwise unconstrained. /// child if the child is otherwise unconstrained.
/// ///
/// The [maxWidth] and [maxHeight] arguments not be null and must be /// The [maxWidth] and [maxHeight] arguments not be null and must be
/// non-negative. /// non-negative.
......
...@@ -1177,8 +1177,18 @@ class FractionallySizedBox extends SingleChildRenderObjectWidget { ...@@ -1177,8 +1177,18 @@ class FractionallySizedBox extends SingleChildRenderObjectWidget {
/// A box that limits its size only when it's unconstrained. /// A box that limits its size only when it's unconstrained.
/// ///
/// If this widget's maximum width is unconstrained then its child's width is /// If this widget's maximum width is unconstrained then its child's width is
/// limited to maxWidth. Similarly, if this widget's maximum height is unconstrained /// limited to [maxWidth]. Similarly, if this widget's maximum height is
/// then its child's height is limited to to maxHeight. /// unconstrained then its child's height is limited to [maxHeight].
///
/// This has the effect of giving the child a natural dimension in unbounded
/// environments. For example, by providing a [maxHeight] to a widget that
/// normally tries to be as big as possible, the widget will normally size
/// itself to fit its parent, but when placed in a vertical list, it will take
/// on the given height.
///
/// This is useful when composing widgets that normally try to match their
/// parents' size, so that they behave reasonably in lists (which are
/// unbounded).
class LimitedBox extends SingleChildRenderObjectWidget { class LimitedBox extends SingleChildRenderObjectWidget {
/// Creates a box that limits its size only when it's unconstrained. /// Creates a box that limits its size only when it's unconstrained.
/// ///
...@@ -1193,10 +1203,12 @@ class LimitedBox extends SingleChildRenderObjectWidget { ...@@ -1193,10 +1203,12 @@ class LimitedBox extends SingleChildRenderObjectWidget {
assert(maxHeight != null && maxHeight >= 0.0), assert(maxHeight != null && maxHeight >= 0.0),
super(key: key, child: child); super(key: key, child: child);
/// The maximum width limit to apply in the absence of a maxWidth constraint. /// The maximum width limit to apply in the absence of a
/// [BoxConstraints.maxWidth] constraint.
final double maxWidth; final double maxWidth;
/// The maximum height limit to apply in the absence of a maxHeight constraint. /// The maximum height limit to apply in the absence of a
/// [BoxConstraints.maxHeight] constraint.
final double maxHeight; final double maxHeight;
@override @override
...@@ -1677,7 +1689,7 @@ class ListBody extends MultiChildRenderObjectWidget { ...@@ -1677,7 +1689,7 @@ class ListBody extends MultiChildRenderObjectWidget {
} }
} }
/// A widget that uses the stack layout algorithm for its children. /// A widget that positions its children relative to the edges of its box.
/// ///
/// This class is useful if you want to overlap several children in a simple /// This class is useful if you want to overlap several children in a simple
/// way, for example having some text and an image, overlaid with a gradient and /// way, for example having some text and an image, overlaid with a gradient and
...@@ -2380,6 +2392,41 @@ class Expanded extends Flexible { ...@@ -2380,6 +2392,41 @@ class Expanded extends Flexible {
/// ///
/// The runs themselves are then positioned in the cross axis according to the /// The runs themselves are then positioned in the cross axis according to the
/// [runSpacing] and [runAlignment]. /// [runSpacing] and [runAlignment].
///
/// ## Sample code
///
/// This example renders some [Chip]s representing four contacts in a [Wrap] so
/// that they flow across lines as necessary.
///
/// ```dart
/// new Wrap(
/// spacing: 8.0, // gap between adjacent chips
/// runSpacing: 4.0, // gap between lines
/// children: <Widget>[
/// new Chip(
/// avatar: new CircleAvatar(backgroundColor: Colors.blue.shade900, child: new Text('AH')),
/// label: new Text('Hamilton'),
/// ),
/// new Chip(
/// avatar: new CircleAvatar(backgroundColor: Colors.blue.shade900, child: new Text('ML')),
/// label: new Text('Lafayette'),
/// ),
/// new Chip(
/// avatar: new CircleAvatar(backgroundColor: Colors.blue.shade900, child: new Text('HM')),
/// label: new Text('Mulligan'),
/// ),
/// new Chip(
/// avatar: new CircleAvatar(backgroundColor: Colors.blue.shade900, child: new Text('JL')),
/// label: new Text('Laurens'),
/// ),
/// ],
/// )
/// ```
///
/// See also:
///
/// * [Row], which places children in one line, and gives control over their
/// alignment and spacing.
class Wrap extends MultiChildRenderObjectWidget { class Wrap extends MultiChildRenderObjectWidget {
/// Creates a wrap layout. /// Creates a wrap layout.
/// ///
...@@ -2500,7 +2547,8 @@ class Wrap extends MultiChildRenderObjectWidget { ...@@ -2500,7 +2547,8 @@ class Wrap extends MultiChildRenderObjectWidget {
} }
} }
/// A widget that implements the flow layout algorithm. /// A widget that sizes and positions children efficiently, according to the
/// logic in a [FlowDelegate].
/// ///
/// Flow layouts are optimized for repositioning children using transformation /// Flow layouts are optimized for repositioning children using transformation
/// matrices. /// matrices.
...@@ -2513,15 +2561,19 @@ class Wrap extends MultiChildRenderObjectWidget { ...@@ -2513,15 +2561,19 @@ class Wrap extends MultiChildRenderObjectWidget {
/// Rather than positioning the children during layout, the children are /// Rather than positioning the children during layout, the children are
/// positioned using transformation matrices during the paint phase using the /// positioned using transformation matrices during the paint phase using the
/// matrices from the [FlowDelegate.paintChildren] function. The children can be /// matrices from the [FlowDelegate.paintChildren] function. The children can be
/// repositioned efficiently by simply repainting the flow. /// repositioned efficiently by simply repainting the flow, which happens
/// without the children being laid out again (contrast this with a [Stack],
/// which does the sizing and positioning together during layout).
/// ///
/// The most efficient way to trigger a repaint of the flow is to supply a /// The most efficient way to trigger a repaint of the flow is to supply an
/// repaint argument to the constructor of the [FlowDelegate]. The flow will /// animation to the constructor of the [FlowDelegate]. The flow will listen to
/// listen to this animation and repaint whenever the animation ticks, avoiding /// this animation and repaint whenever the animation ticks, avoiding both the
/// both the build and layout phases of the pipeline. /// build and layout phases of the pipeline.
/// ///
/// See also: /// See also:
/// ///
/// * [Wrap], which provides the layout model that some other frameworks call
/// "flow", and is otherwise unrelated to [Flow].
/// * [FlowDelegate], which controls the visual presentation of the children. /// * [FlowDelegate], which controls the visual presentation of the children.
/// * [Stack], which arranges children relative to the edges of the container. /// * [Stack], which arranges children relative to the edges of the container.
/// * [CustomSingleChildLayout], which uses a delegate to control the layout of /// * [CustomSingleChildLayout], which uses a delegate to control the layout of
......
...@@ -123,16 +123,68 @@ class DecoratedBox extends SingleChildRenderObjectWidget { ...@@ -123,16 +123,68 @@ class DecoratedBox extends SingleChildRenderObjectWidget {
/// possible. Containers with children size themselves to their children. The /// possible. Containers with children size themselves to their children. The
/// `width`, `height`, and [constraints] arguments to the constructor override /// `width`, `height`, and [constraints] arguments to the constructor override
/// this. /// this.
///
/// ## Sample code
///
/// This example shows a 48x48 green square (placed inside a [Center] widget in
/// case the parent widget has its own opinions regarding the size that the
/// [Container] should take), with a margin so that it stays away from
/// neighboring widgets:
///
/// ```dart
/// new Center(
/// child: new Container(
/// margin: const EdgeInsets.all(10.0),
/// color: const Color(0xFF00FF00),
/// width: 48.0,
/// height: 48.0,
/// ),
/// )
/// ```
///
/// This example shows how to use many of the features of [Container] at once.
/// The [constraints] are set to fit the font size plus ample headroom
/// vertically, while expanding horizontally to fit the parent. The [padding] is
/// used to make sure there is space between the contents and the text. The
/// [color] makes the box teal. The [alignment] causes the [child] to be
/// centered in the box. The [foregroundDecoration] overlays a nine-patch image
/// onto the text. Finally, the [transform] applies a slight rotation to the
/// entire contraption to complete the effect.
///
/// ```dart
/// new Container(
/// constraints: new BoxConstraints.expand(
/// height: Theme.of(context).textTheme.display1.fontSize * 1.1 + 200.0,
/// ),
/// padding: const EdgeInsets.all(8.0),
/// color: Colors.teal.shade700,
/// alignment: FractionalOffset.center,
/// child: new Text('Hello World', style: Theme.of(context).textTheme.display1.copyWith(color: Colors.white)),
/// foregroundDecoration: new BoxDecoration(
/// image: new DecorationImage(
/// image: new NetworkImage('https://www.example.com/images/frame.png'),
/// centerSlice: new Rect.fromLTRB(270.0, 180.0, 1360.0, 730.0),
/// ),
/// ),
/// transform: new Matrix4.rotationZ(0.1),
/// )
/// ```
///
/// See also:
///
/// * [AnimatedContainer], a variant that smoothly animates the properties when
/// they change.
/// * [Border], which has a sample which uses [Container] heavily.
class Container extends StatelessWidget { class Container extends StatelessWidget {
/// Creates a widget that combines common painting, positioning, and sizing widgets. /// Creates a widget that combines common painting, positioning, and sizing widgets.
/// ///
/// The `height` and `width` values include the padding. /// The `height` and `width` values include the padding.
/// ///
/// The `color` argument is a shorthand for /// The `color` argument is a shorthand for `decoration: new
/// `decoration: new BoxDecoration(backgroundColor: color)`, which means you /// BoxDecoration(color: color)`, which means you cannot supply both a `color`
/// cannot supply both a `color` and a `decoration` argument. If you want to /// and a `decoration` argument. If you want to have both a `color` and a
/// have both a `color` and a `decoration`, you can pass the color as the /// `decoration`, you can pass the color as the `color` argument to the
/// `backgroundColor` argument to the `BoxDecoration`. /// `BoxDecoration`.
Container({ Container({
Key key, Key key,
this.alignment, this.alignment,
...@@ -159,7 +211,7 @@ class Container extends StatelessWidget { ...@@ -159,7 +211,7 @@ class Container extends StatelessWidget {
assert(constraints == null || constraints.debugAssertIsValid()); assert(constraints == null || constraints.debugAssertIsValid());
assert(color == null || decoration == null, assert(color == null || decoration == null,
'Cannot provide both a color and a decoration\n' 'Cannot provide both a color and a decoration\n'
'The color argument is just a shorthand for "decoration: new BoxDecoration(backgroundColor: color)".' 'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'
); );
} }
......
...@@ -61,6 +61,10 @@ class TextEditingController extends ValueNotifier<TextEditingValue> { ...@@ -61,6 +61,10 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
/// The current string the user is editing. /// The current string the user is editing.
String get text => value.text; String get text => value.text;
/// Setting this will notify all the listeners of this [TextEditingController]
/// that they need to update (it calls [notifyListeners]). For this reason,
/// this value should only be set between frames, e.g. in response to user
/// actions, not during the build, layout, or paint phases.
set text(String newText) { set text(String newText) {
value = value.copyWith(text: newText, composing: TextRange.empty); value = value.copyWith(text: newText, composing: TextRange.empty);
} }
...@@ -70,6 +74,10 @@ class TextEditingController extends ValueNotifier<TextEditingValue> { ...@@ -70,6 +74,10 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
/// If the selection is collapsed, then this property gives the offset of the /// If the selection is collapsed, then this property gives the offset of the
/// cursor within the text. /// cursor within the text.
TextSelection get selection => value.selection; TextSelection get selection => value.selection;
/// Setting this will notify all the listeners of this [TextEditingController]
/// that they need to update (it calls [notifyListeners]). For this reason,
/// this value should only be set between frames, e.g. in response to user
/// actions, not during the build, layout, or paint phases.
set selection(TextSelection newSelection) { set selection(TextSelection newSelection) {
value = value.copyWith(selection: newSelection, composing: TextRange.empty); value = value.copyWith(selection: newSelection, composing: TextRange.empty);
} }
...@@ -78,6 +86,11 @@ class TextEditingController extends ValueNotifier<TextEditingValue> { ...@@ -78,6 +86,11 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
/// ///
/// After calling this function, [text] will be the empty string and the /// After calling this function, [text] will be the empty string and the
/// selection will be invalid. /// selection will be invalid.
///
/// Calling this will notify all the listeners of this [TextEditingController]
/// that they need to update (it calls [notifyListeners]). For this reason,
/// this method should only be called between frames, e.g. in response to user
/// actions, not during the build, layout, or paint phases.
void clear() { void clear() {
value = TextEditingValue.empty; value = TextEditingValue.empty;
} }
...@@ -87,14 +100,14 @@ class TextEditingController extends ValueNotifier<TextEditingValue> { ...@@ -87,14 +100,14 @@ class TextEditingController extends ValueNotifier<TextEditingValue> {
/// The composing region is the range of text that is still being composed. /// The composing region is the range of text that is still being composed.
/// Calling this function indicates that the user is done composing that /// Calling this function indicates that the user is done composing that
/// region. /// region.
///
/// Calling this will notify all the listeners of this [TextEditingController]
/// that they need to update (it calls [notifyListeners]). For this reason,
/// this method should only be called between frames, e.g. in response to user
/// actions, not during the build, layout, or paint phases.
void clearComposing() { void clearComposing() {
value = value.copyWith(composing: TextRange.empty); value = value.copyWith(composing: TextRange.empty);
} }
@override
String toString() {
return '$runtimeType#$hashCode($value)';
}
} }
/// A basic text input field. /// A basic text input field.
......
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