Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
9c8badd1
Unverified
Commit
9c8badd1
authored
Jul 26, 2019
by
LongCatIsLooong
Committed by
GitHub
Jul 26, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add SliverLayoutBuilder (#35941)
parent
63992e4f
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
692 additions
and
61 deletions
+692
-61
layout_builder.dart
packages/flutter/lib/src/widgets/layout_builder.dart
+89
-59
sliver_layout_builder.dart
packages/flutter/lib/src/widgets/sliver_layout_builder.dart
+80
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
layout_builder_and_global_keys_test.dart
...ter/test/widgets/layout_builder_and_global_keys_test.dart
+44
-0
layout_builder_mutations_test.dart
...s/flutter/test/widgets/layout_builder_mutations_test.dart
+62
-0
layout_builder_test.dart
packages/flutter/test/widgets/layout_builder_test.dart
+416
-2
No files found.
packages/flutter/lib/src/widgets/layout_builder.dart
View file @
9c8badd1
...
@@ -11,56 +11,42 @@ import 'framework.dart';
...
@@ -11,56 +11,42 @@ import 'framework.dart';
/// The signature of the [LayoutBuilder] builder function.
/// The signature of the [LayoutBuilder] builder function.
typedef
LayoutWidgetBuilder
=
Widget
Function
(
BuildContext
context
,
BoxConstraints
constraints
);
typedef
LayoutWidgetBuilder
=
Widget
Function
(
BuildContext
context
,
BoxConstraints
constraints
);
///
Builds a widget tree that can depend on the parent widget's size
.
///
An abstract superclass for widgets that defer their building until layout
.
///
///
/// Similar to the [Builder] widget except that the framework calls the [builder]
/// Similar to the [Builder] widget except that the framework calls the [builder]
/// function at layout time and provides the parent widget's constraints. This
/// function at layout time and provides the constraints that this widget should
/// is useful when the parent constrains the child's size and doesn't depend on
/// adhere to. This is useful when the parent constrains the child's size and layout,
/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
/// and doesn't depend on the child's intrinsic size.
/// child's size.
abstract
class
ConstrainedLayoutBuilder
<
ConstraintType
extends
Constraints
>
extends
RenderObjectWidget
{
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw}
///
/// If the child should be smaller than the parent, consider wrapping the child
/// in an [Align] widget. If the child might want to be bigger, consider
/// wrapping it in a [SingleChildScrollView].
///
/// See also:
///
/// * [Builder], which calls a `builder` function at build time.
/// * [StatefulBuilder], which passes its `builder` function a `setState` callback.
/// * [CustomSingleChildLayout], which positions its child during layout.
class
LayoutBuilder
extends
RenderObjectWidget
{
/// Creates a widget that defers its building until layout.
/// Creates a widget that defers its building until layout.
///
///
/// The [builder] argument must not be null.
/// The [builder] argument must not be null, and the returned widget should not
const
LayoutBuilder
({
/// be null.
const
ConstrainedLayoutBuilder
({
Key
key
,
Key
key
,
@required
this
.
builder
,
@required
this
.
builder
,
})
:
assert
(
builder
!=
null
),
})
:
assert
(
builder
!=
null
),
super
(
key:
key
);
super
(
key:
key
);
/// Called at layout time to construct the widget tree. The builder must not
/// return null.
final
LayoutWidgetBuilder
builder
;
@override
@override
_LayoutBuilderElement
createElement
()
=>
_LayoutBuilderElement
(
this
);
_LayoutBuilderElement
<
ConstraintType
>
createElement
()
=>
_LayoutBuilderElement
<
ConstraintType
>
(
this
);
@override
/// Called at layout time to construct the widget tree.
_RenderLayoutBuilder
createRenderObject
(
BuildContext
context
)
=>
_RenderLayoutBuilder
();
///
/// The builder must not return null.
final
Widget
Function
(
BuildContext
,
ConstraintType
)
builder
;
// updateRenderObject is redundant with the logic in the LayoutBuilderElement below.
// updateRenderObject is redundant with the logic in the LayoutBuilderElement below.
}
}
class
_LayoutBuilderElement
extends
RenderObjectElement
{
class
_LayoutBuilderElement
<
ConstraintType
extends
Constraints
>
extends
RenderObjectElement
{
_LayoutBuilderElement
(
LayoutBuilder
widget
)
:
super
(
widget
);
_LayoutBuilderElement
(
ConstrainedLayoutBuilder
<
ConstraintType
>
widget
)
:
super
(
widget
);
@override
@override
LayoutBuilder
get
widget
=>
super
.
widget
;
ConstrainedLayoutBuilder
<
ConstraintType
>
get
widget
=>
super
.
widget
;
@override
@override
_RenderLayoutBuilder
get
renderObject
=>
super
.
renderObject
;
RenderConstrainedLayoutBuilder
<
ConstraintType
,
RenderObject
>
get
renderObject
=>
super
.
renderObject
;
Element
_child
;
Element
_child
;
...
@@ -79,15 +65,15 @@ class _LayoutBuilderElement extends RenderObjectElement {
...
@@ -79,15 +65,15 @@ class _LayoutBuilderElement extends RenderObjectElement {
@override
@override
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
void
mount
(
Element
parent
,
dynamic
newSlot
)
{
super
.
mount
(
parent
,
newSlot
);
// Creates the renderObject.
super
.
mount
(
parent
,
newSlot
);
// Creates the renderObject.
renderObject
.
callback
=
_layout
;
renderObject
.
updateCallback
(
_layout
)
;
}
}
@override
@override
void
update
(
LayoutBuilder
newWidget
)
{
void
update
(
ConstrainedLayoutBuilder
<
ConstraintType
>
newWidget
)
{
assert
(
widget
!=
newWidget
);
assert
(
widget
!=
newWidget
);
super
.
update
(
newWidget
);
super
.
update
(
newWidget
);
assert
(
widget
==
newWidget
);
assert
(
widget
==
newWidget
);
renderObject
.
callback
=
_layout
;
renderObject
.
updateCallback
(
_layout
)
;
renderObject
.
markNeedsLayout
();
renderObject
.
markNeedsLayout
();
}
}
...
@@ -101,11 +87,11 @@ class _LayoutBuilderElement extends RenderObjectElement {
...
@@ -101,11 +87,11 @@ class _LayoutBuilderElement extends RenderObjectElement {
@override
@override
void
unmount
()
{
void
unmount
()
{
renderObject
.
callback
=
null
;
renderObject
.
updateCallback
(
null
)
;
super
.
unmount
();
super
.
unmount
();
}
}
void
_layout
(
BoxConstraints
constraints
)
{
void
_layout
(
ConstraintType
constraints
)
{
owner
.
buildScope
(
this
,
()
{
owner
.
buildScope
(
this
,
()
{
Widget
built
;
Widget
built
;
if
(
widget
.
builder
!=
null
)
{
if
(
widget
.
builder
!=
null
)
{
...
@@ -160,41 +146,71 @@ class _LayoutBuilderElement extends RenderObjectElement {
...
@@ -160,41 +146,71 @@ class _LayoutBuilderElement extends RenderObjectElement {
@override
@override
void
removeChildRenderObject
(
RenderObject
child
)
{
void
removeChildRenderObject
(
RenderObject
child
)
{
final
_RenderLayoutBuilder
renderObject
=
this
.
renderObject
;
final
RenderConstrainedLayoutBuilder
<
ConstraintType
,
RenderObject
>
renderObject
=
this
.
renderObject
;
assert
(
renderObject
.
child
==
child
);
assert
(
renderObject
.
child
==
child
);
renderObject
.
child
=
null
;
renderObject
.
child
=
null
;
assert
(
renderObject
==
this
.
renderObject
);
assert
(
renderObject
==
this
.
renderObject
);
}
}
}
}
class
_RenderLayoutBuilder
extends
RenderBox
with
RenderObjectWithChildMixin
<
RenderBox
>
{
/// Generic mixin for [RenderObject]s created by [ConstrainedLayoutBuilder].
_RenderLayoutBuilder
({
///
LayoutCallback
<
BoxConstraints
>
callback
,
/// Provides a callback that should be called at layout time, typically in
})
:
_callback
=
callback
;
/// [RenderObject.performLayout].
mixin
RenderConstrainedLayoutBuilder
<
ConstraintType
extends
Constraints
,
ChildType
extends
RenderObject
>
on
RenderObjectWithChildMixin
<
ChildType
>
{
LayoutCallback
<
BoxConstraints
>
get
callback
=
>
_callback
;
LayoutCallback
<
ConstraintType
>
_callback
;
LayoutCallback
<
BoxConstraints
>
_callback
;
/// Change the layout callback.
set
callback
(
LayoutCallback
<
BoxConstraints
>
value
)
{
void
updateCallback
(
LayoutCallback
<
ConstraintType
>
value
)
{
if
(
value
==
_callback
)
if
(
value
==
_callback
)
return
;
return
;
_callback
=
value
;
_callback
=
value
;
markNeedsLayout
();
markNeedsLayout
();
}
}
bool
_debugThrowIfNotCheckingIntrinsics
()
{
/// Invoke the layout callback.
assert
(()
{
void
layoutAndBuildChild
()
{
if
(!
RenderObject
.
debugCheckingIntrinsics
)
{
assert
(
_callback
!=
null
);
throw
FlutterError
(
invokeLayoutCallback
(
_callback
);
'LayoutBuilder does not support returning intrinsic dimensions.
\n
'
'Calculating the intrinsic dimensions would require running the layout '
'callback speculatively, which might mutate the live render object tree.'
);
}
return
true
;
}());
return
true
;
}
}
}
/// Builds a widget tree that can depend on the parent widget's size.
///
/// Similar to the [Builder] widget except that the framework calls the [builder]
/// function at layout time and provides the parent widget's constraints. This
/// is useful when the parent constrains the child's size and doesn't depend on
/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its
/// child's size.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw}
///
/// If the child should be smaller than the parent, consider wrapping the child
/// in an [Align] widget. If the child might want to be bigger, consider
/// wrapping it in a [SingleChildScrollView].
///
/// See also:
///
/// * [SliverLayoutBuilder], the sliver counterpart of this widget.
/// * [Builder], which calls a `builder` function at build time.
/// * [StatefulBuilder], which passes its `builder` function a `setState` callback.
/// * [CustomSingleChildLayout], which positions its child during layout.
class
LayoutBuilder
extends
ConstrainedLayoutBuilder
<
BoxConstraints
>
{
/// Creates a widget that defers its building until layout.
///
/// The [builder] argument must not be null.
const
LayoutBuilder
({
Key
key
,
LayoutWidgetBuilder
builder
,
})
:
super
(
key:
key
,
builder:
builder
);
@override
LayoutWidgetBuilder
get
builder
=>
super
.
builder
;
@override
_RenderLayoutBuilder
createRenderObject
(
BuildContext
context
)
=>
_RenderLayoutBuilder
();
}
class
_RenderLayoutBuilder
extends
RenderBox
with
RenderObjectWithChildMixin
<
RenderBox
>,
RenderConstrainedLayoutBuilder
<
BoxConstraints
,
RenderBox
>
{
@override
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
double
computeMinIntrinsicWidth
(
double
height
)
{
assert
(
_debugThrowIfNotCheckingIntrinsics
());
assert
(
_debugThrowIfNotCheckingIntrinsics
());
...
@@ -221,8 +237,7 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
...
@@ -221,8 +237,7 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
@override
@override
void
performLayout
()
{
void
performLayout
()
{
assert
(
callback
!=
null
);
layoutAndBuildChild
();
invokeLayoutCallback
(
callback
);
if
(
child
!=
null
)
{
if
(
child
!=
null
)
{
child
.
layout
(
constraints
,
parentUsesSize:
true
);
child
.
layout
(
constraints
,
parentUsesSize:
true
);
size
=
constraints
.
constrain
(
child
.
size
);
size
=
constraints
.
constrain
(
child
.
size
);
...
@@ -241,6 +256,21 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
...
@@ -241,6 +256,21 @@ class _RenderLayoutBuilder extends RenderBox with RenderObjectWithChildMixin<Ren
if
(
child
!=
null
)
if
(
child
!=
null
)
context
.
paintChild
(
child
,
offset
);
context
.
paintChild
(
child
,
offset
);
}
}
bool
_debugThrowIfNotCheckingIntrinsics
()
{
assert
(()
{
if
(!
RenderObject
.
debugCheckingIntrinsics
)
{
throw
FlutterError
(
'LayoutBuilder does not support returning intrinsic dimensions.
\n
'
'Calculating the intrinsic dimensions would require running the layout '
'callback speculatively, which might mutate the live render object tree.'
);
}
return
true
;
}());
return
true
;
}
}
}
FlutterErrorDetails
_debugReportException
(
FlutterErrorDetails
_debugReportException
(
...
...
packages/flutter/lib/src/widgets/sliver_layout_builder.dart
0 → 100644
View file @
9c8badd1
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'framework.dart'
;
import
'layout_builder.dart'
;
/// The signature of the [SliverLayoutBuilder] builder function.
typedef
SliverLayoutWidgetBuilder
=
Widget
Function
(
BuildContext
context
,
SliverConstraints
constraints
);
/// Builds a sliver widget tree that can depend on its own [SliverConstraints].
///
/// Similar to the [LayoutBuilder] widget except its builder should return a sliver
/// widget, and [SliverLayoutBuilder] is itself a sliver. The framework calls the
/// [builder] function at layout time and provides the current [SliverConstraints].
/// The [SliverLayoutBuilder]'s final [SliverGeometry] will match the [SliverGeometry]
/// of its child.
///
///
/// See also:
///
/// * [LayoutBuilder], the non-sliver version of this widget.
class
SliverLayoutBuilder
extends
ConstrainedLayoutBuilder
<
SliverConstraints
>
{
/// Creates a sliver widget that defers its building until layout.
///
/// The [builder] argument must not be null.
const
SliverLayoutBuilder
({
Key
key
,
SliverLayoutWidgetBuilder
builder
,
})
:
super
(
key:
key
,
builder:
builder
);
/// Called at layout time to construct the widget tree.
///
/// The builder must return a non-null sliver widget.
@override
SliverLayoutWidgetBuilder
get
builder
=>
super
.
builder
;
@override
_RenderSliverLayoutBuilder
createRenderObject
(
BuildContext
context
)
=>
_RenderSliverLayoutBuilder
();
}
class
_RenderSliverLayoutBuilder
extends
RenderSliver
with
RenderObjectWithChildMixin
<
RenderSliver
>,
RenderConstrainedLayoutBuilder
<
SliverConstraints
,
RenderSliver
>
{
@override
double
childMainAxisPosition
(
RenderObject
child
)
{
assert
(
child
!=
null
);
assert
(
child
==
this
.
child
);
return
0
;
}
@override
void
performLayout
()
{
layoutAndBuildChild
();
child
?.
layout
(
constraints
,
parentUsesSize:
true
);
geometry
=
child
?.
geometry
??
SliverGeometry
.
zero
;
}
@override
void
applyPaintTransform
(
RenderObject
child
,
Matrix4
transform
)
{
assert
(
child
!=
null
);
assert
(
child
==
this
.
child
);
// child's offset is always (0, 0), transform.translate(0, 0) does not mutate the transform.
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
// This renderObject does not introduce additional offset to child's position.
if
(
child
?.
geometry
?.
visible
==
true
)
context
.
paintChild
(
child
,
offset
);
}
@override
bool
hitTestChildren
(
SliverHitTestResult
result
,
{
double
mainAxisPosition
,
double
crossAxisPosition
})
{
return
child
!=
null
&&
child
.
geometry
.
hitTestExtent
>
0
&&
child
.
hitTest
(
result
,
mainAxisPosition:
mainAxisPosition
,
crossAxisPosition:
crossAxisPosition
);
}
}
packages/flutter/lib/widgets.dart
View file @
9c8badd1
...
@@ -93,6 +93,7 @@ export 'src/widgets/shortcuts.dart';
...
@@ -93,6 +93,7 @@ export 'src/widgets/shortcuts.dart';
export
'src/widgets/single_child_scroll_view.dart'
;
export
'src/widgets/single_child_scroll_view.dart'
;
export
'src/widgets/size_changed_layout_notifier.dart'
;
export
'src/widgets/size_changed_layout_notifier.dart'
;
export
'src/widgets/sliver.dart'
;
export
'src/widgets/sliver.dart'
;
export
'src/widgets/sliver_layout_builder.dart'
;
export
'src/widgets/sliver_persistent_header.dart'
;
export
'src/widgets/sliver_persistent_header.dart'
;
export
'src/widgets/sliver_prototype_extent_list.dart'
;
export
'src/widgets/sliver_prototype_extent_list.dart'
;
export
'src/widgets/spacer.dart'
;
export
'src/widgets/spacer.dart'
;
...
...
packages/flutter/test/widgets/layout_builder_and_global_keys_test.dart
View file @
9c8badd1
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter/src/rendering/sliver.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter/widgets.dart'
;
...
@@ -55,5 +56,48 @@ void main() {
...
@@ -55,5 +56,48 @@ void main() {
return
StatefulWrapper
(
key:
key
,
child:
Container
(
height:
100.0
));
return
StatefulWrapper
(
key:
key
,
child:
Container
(
height:
100.0
));
}),
}),
);
);
expect
(
tester
.
takeException
(),
null
);
});
testWidgets
(
'Moving global key inside a SliverLayoutBuilder'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
<
StatefulWrapperState
>
key
=
GlobalKey
<
StatefulWrapperState
>();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverToBoxAdapter
(
child:
Wrapper
(
child:
StatefulWrapper
(
key:
key
,
child:
Container
(
height:
100.0
))),
);
},
),
],
),
),
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
key
.
currentState
.
trigger
();
return
SliverToBoxAdapter
(
child:
StatefulWrapper
(
key:
key
,
child:
Container
(
height:
100.0
)),
);
},
),
],
),
),
);
expect
(
tester
.
takeException
(),
null
);
});
});
}
}
packages/flutter/test/widgets/layout_builder_mutations_test.dart
View file @
9c8badd1
...
@@ -2,9 +2,12 @@
...
@@ -2,9 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// found in the LICENSE file.
import
'package:flutter/src/rendering/sliver.dart'
;
import
'package:flutter/src/widgets/basic.dart'
;
import
'package:flutter/src/widgets/basic.dart'
;
import
'package:flutter/src/widgets/framework.dart'
;
import
'package:flutter/src/widgets/framework.dart'
;
import
'package:flutter/src/widgets/layout_builder.dart'
;
import
'package:flutter/src/widgets/layout_builder.dart'
;
import
'package:flutter/src/widgets/sliver_layout_builder.dart'
;
import
'package:flutter/src/widgets/scroll_view.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
class
Wrapper
extends
StatelessWidget
{
class
Wrapper
extends
StatelessWidget
{
...
@@ -63,5 +66,64 @@ void main() {
...
@@ -63,5 +66,64 @@ void main() {
),
),
],
],
));
));
expect
(
tester
.
takeException
(),
null
);
});
testWidgets
(
'Moving a global key from another SliverLayoutBuilder at layout time'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
victimKey1
=
GlobalKey
();
final
GlobalKey
victimKey2
=
GlobalKey
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverPadding
(
key:
victimKey1
,
padding:
const
EdgeInsets
.
fromLTRB
(
1
,
2
,
3
,
4
));
},
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverPadding
(
key:
victimKey2
,
padding:
const
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
const
SliverPadding
(
padding:
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
],
),
),
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverPadding
(
key:
victimKey2
,
padding:
const
EdgeInsets
.
fromLTRB
(
1
,
2
,
3
,
4
));
},
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
const
SliverPadding
(
padding:
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
SliverLayoutBuilder
(
builder:
(
BuildContext
context
,
SliverConstraints
constraint
)
{
return
SliverPadding
(
key:
victimKey1
,
padding:
const
EdgeInsets
.
fromLTRB
(
5
,
7
,
11
,
13
));
},
),
],
),
),
);
expect
(
tester
.
takeException
(),
null
);
});
});
}
}
packages/flutter/test/widgets/layout_builder_test.dart
View file @
9c8badd1
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment