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
e235ccd7
Unverified
Commit
e235ccd7
authored
Aug 23, 2018
by
Jonah Williams
Committed by
GitHub
Aug 23, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support disabled animations (#20354)
parent
1f31c3b3
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
238 additions
and
19 deletions
+238
-19
progress_indicator_demo.dart
...er_gallery/lib/demo/material/progress_indicator_demo.dart
+1
-0
semantics.dart
packages/flutter/lib/semantics.dart
+1
-0
animation_controller.dart
packages/flutter/lib/src/animation/animation_controller.dart
+63
-6
refresh_indicator.dart
packages/flutter/lib/src/material/refresh_indicator.dart
+0
-1
reorderable_list.dart
packages/flutter/lib/src/material/reorderable_list.dart
+2
-0
binding.dart
packages/flutter/lib/src/rendering/binding.dart
+2
-8
ticker.dart
packages/flutter/lib/src/scheduler/ticker.dart
+0
-1
binding.dart
packages/flutter/lib/src/semantics/binding.dart
+49
-0
binding.dart
packages/flutter/lib/src/widgets/binding.dart
+1
-1
animation_controller_test.dart
...ges/flutter/test/animation/animation_controller_test.dart
+80
-0
service_extensions_test.dart
...ages/flutter/test/foundation/service_extensions_test.dart
+1
-0
rendering_tester.dart
packages/flutter/test/rendering/rendering_tester.dart
+1
-1
ticker_test.dart
packages/flutter/test/scheduler/ticker_test.dart
+32
-0
extension.dart
packages/flutter_driver/lib/src/extension/extension.dart
+1
-1
binding.dart
packages/flutter_test/lib/src/binding.dart
+4
-0
No files found.
examples/flutter_gallery/lib/demo/material/progress_indicator_demo.dart
View file @
e235ccd7
...
...
@@ -21,6 +21,7 @@ class _ProgressIndicatorDemoState extends State<ProgressIndicatorDemo> with Sing
_controller
=
new
AnimationController
(
duration:
const
Duration
(
milliseconds:
1500
),
vsync:
this
,
animationBehavior:
AnimationBehavior
.
preserve
,
)..
forward
();
_animation
=
new
CurvedAnimation
(
...
...
packages/flutter/lib/semantics.dart
View file @
e235ccd7
...
...
@@ -13,5 +13,6 @@
/// and is used by the platform-specific accessibility services.
library
semantics
;
export
'src/semantics/binding.dart'
;
export
'src/semantics/semantics.dart'
;
export
'src/semantics/semantics_service.dart'
;
packages/flutter/lib/src/animation/animation_controller.dart
View file @
e235ccd7
...
...
@@ -7,6 +7,7 @@ import 'dart:ui' as ui show lerpDouble;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/physics.dart'
;
import
'package:flutter/semantics.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'animation.dart'
;
...
...
@@ -38,6 +39,30 @@ const Tolerance _kFlingTolerance = Tolerance(
distance:
0.01
,
);
/// Configures how an [AnimationController] behaves when animations are disabled.
///
/// When [AccessibilityFeatures.disableAnimations] is true, the device is asking
/// flutter to reduce or disable animations as much as possible. To honor this,
/// we reduce the duration and the corresponding number of frames for animations.
/// This enum is used to allow certain [AnimationControllers] to opt out of this
/// behavior.
///
/// For example, the [AnimationController] which controls the physics simulation
/// for a scrollable list will have [AnimationBehavior.preserve] so that when
/// a user attempts to scroll it does not jump to the end/beginning too quickly.
enum
AnimationBehavior
{
/// The [AnimationController] will reduce its duration when
/// [AccessibilityFeatures.disableAnimations] is true.
normal
,
/// The [AnimationController] will preserve its behavior.
///
/// This is the default for repeating animations in order to prevent them from
/// flashing rapidly on the screen if the widget does not take the
/// [AccessibilityFeatures.disableAnimations] flag into account.
preserve
,
}
/// A controller for an animation.
///
/// This class lets you perform tasks such as:
...
...
@@ -120,6 +145,7 @@ class AnimationController extends Animation<double>
this
.
debugLabel
,
this
.
lowerBound
=
0.0
,
this
.
upperBound
=
1.0
,
this
.
animationBehavior
=
AnimationBehavior
.
normal
,
@required
TickerProvider
vsync
,
})
:
assert
(
lowerBound
!=
null
),
assert
(
upperBound
!=
null
),
...
...
@@ -151,6 +177,7 @@ class AnimationController extends Animation<double>
this
.
duration
,
this
.
debugLabel
,
@required
TickerProvider
vsync
,
this
.
animationBehavior
=
AnimationBehavior
.
preserve
,
})
:
assert
(
value
!=
null
),
assert
(
vsync
!=
null
),
lowerBound
=
double
.
negativeInfinity
,
...
...
@@ -170,6 +197,14 @@ class AnimationController extends Animation<double>
/// identifying animation controller instances in debug output.
final
String
debugLabel
;
/// The behavior of the controller when [AccessibilityFeatures.disableAnimations]
/// is true.
///
/// Defaults to [AnimationBehavior.normal] for the [new AnimationBehavior]
/// constructor, and [AnimationBehavior.preserve] for the
/// [new AnimationBehavior.unbounded] constructor.
final
AnimationBehavior
animationBehavior
;
/// Returns an [Animation<double>] for this animation controller, so that a
/// pointer to this object can be passed around without allowing users of that
/// pointer to mutate the [AnimationController] state.
...
...
@@ -363,7 +398,18 @@ class AnimationController extends Animation<double>
return
_animateToInternal
(
target
,
duration:
duration
,
curve:
curve
);
}
TickerFuture
_animateToInternal
(
double
target
,
{
Duration
duration
,
Curve
curve
=
Curves
.
linear
})
{
TickerFuture
_animateToInternal
(
double
target
,
{
Duration
duration
,
Curve
curve
=
Curves
.
linear
,
AnimationBehavior
animationBehavior
})
{
final
AnimationBehavior
behavior
=
animationBehavior
??
this
.
animationBehavior
;
double
scale
=
1.0
;
if
(
SemanticsBinding
.
instance
.
disableAnimations
)
{
switch
(
behavior
)
{
case
AnimationBehavior
.
normal
:
scale
=
0.05
;
break
;
case
AnimationBehavior
.
preserve
:
break
;
}
}
Duration
simulationDuration
=
duration
;
if
(
simulationDuration
==
null
)
{
assert
(()
{
...
...
@@ -398,7 +444,7 @@ class AnimationController extends Animation<double>
}
assert
(
simulationDuration
>
Duration
.
zero
);
assert
(!
isAnimating
);
return
_startSimulation
(
new
_InterpolationSimulation
(
_value
,
target
,
simulationDuration
,
curve
));
return
_startSimulation
(
new
_InterpolationSimulation
(
_value
,
target
,
simulationDuration
,
curve
,
scale
));
}
/// Starts running this animation in the forward direction, and
...
...
@@ -441,11 +487,22 @@ class AnimationController extends Animation<double>
/// The most recently returned [TickerFuture], if any, is marked as having been
/// canceled, meaning the future never completes and its [TickerFuture.orCancel]
/// derivative future completes with a [TickerCanceled] error.
TickerFuture
fling
({
double
velocity
=
1.0
})
{
TickerFuture
fling
({
double
velocity
=
1.0
,
AnimationBehavior
animationBehavior
})
{
_direction
=
velocity
<
0.0
?
_AnimationDirection
.
reverse
:
_AnimationDirection
.
forward
;
final
double
target
=
velocity
<
0.0
?
lowerBound
-
_kFlingTolerance
.
distance
:
upperBound
+
_kFlingTolerance
.
distance
;
final
Simulation
simulation
=
new
SpringSimulation
(
_kFlingSpringDescription
,
value
,
target
,
velocity
)
double
scale
=
1.0
;
final
AnimationBehavior
behavior
=
animationBehavior
??
this
.
animationBehavior
;
if
(
SemanticsBinding
.
instance
.
disableAnimations
)
{
switch
(
behavior
)
{
case
AnimationBehavior
.
normal
:
scale
=
200.0
;
break
;
case
AnimationBehavior
.
preserve
:
break
;
}
}
final
Simulation
simulation
=
new
SpringSimulation
(
_kFlingSpringDescription
,
value
,
target
,
velocity
*
scale
)
..
tolerance
=
_kFlingTolerance
;
return
animateWith
(
simulation
);
}
...
...
@@ -558,11 +615,11 @@ class AnimationController extends Animation<double>
}
class
_InterpolationSimulation
extends
Simulation
{
_InterpolationSimulation
(
this
.
_begin
,
this
.
_end
,
Duration
duration
,
this
.
_curve
)
_InterpolationSimulation
(
this
.
_begin
,
this
.
_end
,
Duration
duration
,
this
.
_curve
,
double
scale
)
:
assert
(
_begin
!=
null
),
assert
(
_end
!=
null
),
assert
(
duration
!=
null
&&
duration
.
inMicroseconds
>
0
),
_durationInSeconds
=
duration
.
inMicroseconds
/
Duration
.
microsecondsPerSecond
;
_durationInSeconds
=
(
duration
.
inMicroseconds
*
scale
)
/
Duration
.
microsecondsPerSecond
;
final
double
_durationInSeconds
;
final
double
_begin
;
...
...
packages/flutter/lib/src/material/refresh_indicator.dart
View file @
e235ccd7
...
...
@@ -153,7 +153,6 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
@override
void
initState
()
{
super
.
initState
();
_positionController
=
new
AnimationController
(
vsync:
this
);
_positionFactor
=
new
Tween
<
double
>(
begin:
0.0
,
...
...
packages/flutter/lib/src/material/reorderable_list.dart
View file @
e235ccd7
...
...
@@ -154,6 +154,7 @@ class _ReorderableListContent extends StatefulWidget {
}
class
_ReorderableListContentState
extends
State
<
_ReorderableListContent
>
with
TickerProviderStateMixin
{
// The extent along the [widget.scrollDirection] axis to allow a child to
// drop into when the user reorders list children.
//
...
...
@@ -280,6 +281,7 @@ class _ReorderableListContentState extends State<_ReorderableListContent> with T
viewport
.
getOffsetToReveal
(
contextObject
,
1.0
).
offset
+
margin
,
);
final
bool
onScreen
=
scrollOffset
<=
topOffset
&&
scrollOffset
>=
bottomOffset
;
// If the context is off screen, then we request a scroll to make it visible.
if
(!
onScreen
)
{
_scrolling
=
true
;
...
...
packages/flutter/lib/src/rendering/binding.dart
View file @
e235ccd7
...
...
@@ -21,7 +21,7 @@ import 'view.dart';
export
'package:flutter/gestures.dart'
show
HitTestResult
;
/// The glue between the render tree and the Flutter engine.
abstract
class
RendererBinding
extends
BindingBase
with
ServicesBinding
,
SchedulerBinding
,
HitTestable
{
abstract
class
RendererBinding
extends
BindingBase
with
ServicesBinding
,
SchedulerBinding
,
SemanticsBinding
,
HitTestable
{
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory
RendererBinding
.
_
()
=>
null
;
...
...
@@ -153,12 +153,6 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
@protected
void
handleTextScaleFactorChanged
()
{
}
/// Called when the platform accessibility features change.
///
/// See [Window.onAccessibilityFeaturesChanged].
@protected
void
handleAccessibilityFeaturesChanged
()
{}
/// Returns a [ViewConfiguration] configured for the [RenderView] based on the
/// current environment.
///
...
...
@@ -341,7 +335,7 @@ void debugDumpSemanticsTree(DebugSemanticsDumpOrder childOrder) {
/// that layer's binding.
///
/// See also [BindingBase].
class
RenderingFlutterBinding
extends
BindingBase
with
GestureBinding
,
ServicesBinding
,
SchedulerBinding
,
RendererBinding
{
class
RenderingFlutterBinding
extends
BindingBase
with
GestureBinding
,
ServicesBinding
,
SchedulerBinding
,
SemanticsBinding
,
RendererBinding
{
/// Creates a binding for the rendering layer.
///
/// The `root` render box is attached directly to the [renderView] and is
...
...
packages/flutter/lib/src/scheduler/ticker.dart
View file @
e235ccd7
...
...
@@ -225,7 +225,6 @@ class Ticker {
_animationId
=
null
;
_startTime
??=
timeStamp
;
_onTick
(
timeStamp
-
_startTime
);
// The onTick callback may have scheduled another tick already, for
...
...
packages/flutter/lib/src/semantics/binding.dart
0 → 100644
View file @
e235ccd7
// Copyright 2018 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
'dart:ui'
as
ui
show
AccessibilityFeatures
,
window
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
/// The glue between the semantics layer and the Flutter engine.
// TODO(jonahwilliams): move the remaining semantic related bindings here.
class
SemanticsBinding
extends
BindingBase
with
ServicesBinding
{
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory
SemanticsBinding
.
_
()
=>
null
;
/// The current [SemanticsBinding], if one has been created.
static
SemanticsBinding
get
instance
=>
_instance
;
static
SemanticsBinding
_instance
;
@override
void
initInstances
()
{
super
.
initInstances
();
_instance
=
this
;
_accessibilityFeatures
=
ui
.
window
.
accessibilityFeatures
;
}
/// Called when the platform accessibility features change.
///
/// See [Window.onAccessibilityFeaturesChanged].
@protected
void
handleAccessibilityFeaturesChanged
()
{
_accessibilityFeatures
=
ui
.
window
.
accessibilityFeatures
;
}
/// The currently active set of [AccessibilityFeatures].
///
/// This is initialized the first time [runApp] is called and updated whenever
/// a flag is changed.
///
/// To listen to changes to accessibility features, create a
/// [WidgetsBindingObserver] and listen to [didChangeAccessibilityFeatures].
ui
.
AccessibilityFeatures
get
accessibilityFeatures
=>
_accessibilityFeatures
;
ui
.
AccessibilityFeatures
_accessibilityFeatures
;
/// Whether the device is requesting that animations be disabled.
bool
get
disableAnimations
=>
accessibilityFeatures
.
disableAnimations
;
}
\ No newline at end of file
packages/flutter/lib/src/widgets/binding.dart
View file @
e235ccd7
...
...
@@ -917,7 +917,7 @@ class RenderObjectToWidgetElement<T extends RenderObject> extends RootRenderObje
/// A concrete binding for applications based on the Widgets framework.
/// This is the glue that binds the framework to the Flutter engine.
class
WidgetsFlutterBinding
extends
BindingBase
with
GestureBinding
,
ServicesBinding
,
SchedulerBinding
,
PaintingBinding
,
RendererBinding
,
WidgetsBinding
{
class
WidgetsFlutterBinding
extends
BindingBase
with
GestureBinding
,
ServicesBinding
,
SchedulerBinding
,
PaintingBinding
,
SemanticsBinding
,
RendererBinding
,
WidgetsBinding
{
/// Returns an instance of the [WidgetsBinding], creating and
/// initializing it if necessary. If one is created, it will be a
...
...
packages/flutter/test/animation/animation_controller_test.dart
View file @
e235ccd7
...
...
@@ -578,4 +578,84 @@ void main() {
expect
(
statusLog
,
equals
(<
AnimationStatus
>[
AnimationStatus
.
forward
,
AnimationStatus
.
completed
]));
statusLog
.
clear
();
});
group
(
'AnimationBehavior'
,
()
{
test
(
'Default values for constructor'
,
()
{
final
AnimationController
controller
=
new
AnimationController
(
vsync:
const
TestVSync
());
expect
(
controller
.
animationBehavior
,
AnimationBehavior
.
normal
);
final
AnimationController
repeating
=
new
AnimationController
.
unbounded
(
vsync:
const
TestVSync
());
expect
(
repeating
.
animationBehavior
,
AnimationBehavior
.
preserve
);
});
testWidgets
(
'AnimationBehavior.preserve runs at normal speed when animatingTo'
,
(
WidgetTester
tester
)
async
{
tester
.
binding
.
disableAnimations
=
true
;
final
AnimationController
controller
=
new
AnimationController
(
vsync:
const
TestVSync
(),
animationBehavior:
AnimationBehavior
.
preserve
,
);
expect
(
controller
.
value
,
0.0
);
expect
(
controller
.
status
,
AnimationStatus
.
dismissed
);
controller
.
animateTo
(
1.0
,
duration:
const
Duration
(
milliseconds:
100
));
tick
(
const
Duration
(
milliseconds:
0
));
tick
(
const
Duration
(
milliseconds:
50
));
expect
(
controller
.
value
,
0.5
);
expect
(
controller
.
status
,
AnimationStatus
.
forward
);
tick
(
const
Duration
(
milliseconds:
0
));
tick
(
const
Duration
(
milliseconds:
150
));
expect
(
controller
.
value
,
1.0
);
expect
(
controller
.
status
,
AnimationStatus
.
completed
);
tester
.
binding
.
disableAnimations
=
false
;
});
testWidgets
(
'AnimationBehavior.normal runs at 20x speed when animatingTo'
,
(
WidgetTester
tester
)
async
{
tester
.
binding
.
disableAnimations
=
true
;
final
AnimationController
controller
=
new
AnimationController
(
vsync:
const
TestVSync
(),
animationBehavior:
AnimationBehavior
.
normal
,
);
expect
(
controller
.
value
,
0.0
);
expect
(
controller
.
status
,
AnimationStatus
.
dismissed
);
controller
.
animateTo
(
1.0
,
duration:
const
Duration
(
milliseconds:
100
));
tick
(
const
Duration
(
milliseconds:
0
));
tick
(
const
Duration
(
microseconds:
2500
));
expect
(
controller
.
value
,
0.5
);
expect
(
controller
.
status
,
AnimationStatus
.
forward
);
tick
(
const
Duration
(
milliseconds:
0
));
tick
(
const
Duration
(
milliseconds:
5
,
microseconds:
1000
));
expect
(
controller
.
value
,
1.0
);
expect
(
controller
.
status
,
AnimationStatus
.
completed
);
tester
.
binding
.
disableAnimations
=
false
;
});
testWidgets
(
'AnimationBehavior.normal runs "faster" whan AnimationBehavior.preserve'
,
(
WidgetTester
tester
)
async
{
tester
.
binding
.
disableAnimations
=
true
;
final
AnimationController
controller
=
new
AnimationController
(
vsync:
const
TestVSync
(),
);
final
AnimationController
fastController
=
new
AnimationController
(
vsync:
const
TestVSync
(),
);
controller
.
fling
(
velocity:
1.0
,
animationBehavior:
AnimationBehavior
.
preserve
);
fastController
.
fling
(
velocity:
1.0
,
animationBehavior:
AnimationBehavior
.
normal
);
tick
(
const
Duration
(
milliseconds:
0
));
tick
(
const
Duration
(
milliseconds:
50
));
// We don't assert a specific faction that normal animation.
expect
(
controller
.
value
<
fastController
.
value
,
true
);
tester
.
binding
.
disableAnimations
=
false
;
});
});
}
packages/flutter/test/foundation/service_extensions_test.dart
View file @
e235ccd7
...
...
@@ -20,6 +20,7 @@ class TestServiceExtensionsBinding extends BindingBase
GestureBinding
,
SchedulerBinding
,
PaintingBinding
,
SemanticsBinding
,
RendererBinding
,
WidgetsBinding
{
...
...
packages/flutter/test/rendering/rendering_tester.dart
View file @
e235ccd7
...
...
@@ -11,7 +11,7 @@ import 'package:flutter/services.dart';
import
'package:flutter_test/flutter_test.dart'
show
EnginePhase
;
export
'package:flutter_test/flutter_test.dart'
show
EnginePhase
;
class
TestRenderingFlutterBinding
extends
BindingBase
with
ServicesBinding
,
GestureBinding
,
SchedulerBinding
,
PaintingBinding
,
RendererBinding
{
class
TestRenderingFlutterBinding
extends
BindingBase
with
ServicesBinding
,
GestureBinding
,
SchedulerBinding
,
PaintingBinding
,
SemanticsBinding
,
RendererBinding
{
EnginePhase
phase
=
EnginePhase
.
composite
;
@override
...
...
packages/flutter/test/scheduler/ticker_test.dart
View file @
e235ccd7
...
...
@@ -83,6 +83,38 @@ void main() {
expect
(
ticker
.
toString
(
debugIncludeStack:
true
),
contains
(
'testFunction'
));
});
testWidgets
(
'Ticker can be sped up with time dilation'
,
(
WidgetTester
tester
)
async
{
timeDilation
=
0.5
;
// Move twice as fast.
Duration
lastDuration
;
void
handleTick
(
Duration
duration
)
{
lastDuration
=
duration
;
}
final
Ticker
ticker
=
new
Ticker
(
handleTick
);
ticker
.
start
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
10
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
10
));
expect
(
lastDuration
,
const
Duration
(
milliseconds:
20
));
ticker
.
dispose
();
});
testWidgets
(
'Ticker can be slowed down with time dilation'
,
(
WidgetTester
tester
)
async
{
timeDilation
=
2.0
;
// Move half as fast.
Duration
lastDuration
;
void
handleTick
(
Duration
duration
)
{
lastDuration
=
duration
;
}
final
Ticker
ticker
=
new
Ticker
(
handleTick
);
ticker
.
start
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
10
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
10
));
expect
(
lastDuration
,
const
Duration
(
milliseconds:
5
));
ticker
.
dispose
();
});
testWidgets
(
'Ticker stops ticking when application is paused'
,
(
WidgetTester
tester
)
async
{
int
tickCount
=
0
;
void
handleTick
(
Duration
duration
)
{
...
...
packages/flutter_driver/lib/src/extension/extension.dart
View file @
e235ccd7
...
...
@@ -36,7 +36,7 @@ const String _extensionMethod = 'ext.flutter.$_extensionMethodName';
/// eventually completes to a string response.
typedef
Future
<
String
>
DataHandler
(
String
message
);
class
_DriverBinding
extends
BindingBase
with
ServicesBinding
,
SchedulerBinding
,
GestureBinding
,
PaintingBinding
,
RendererBinding
,
WidgetsBinding
{
class
_DriverBinding
extends
BindingBase
with
ServicesBinding
,
SchedulerBinding
,
GestureBinding
,
PaintingBinding
,
SemanticsBinding
,
RendererBinding
,
WidgetsBinding
{
_DriverBinding
(
this
.
_handler
,
this
.
_silenceErrors
);
final
DataHandler
_handler
;
...
...
packages/flutter_test/lib/src/binding.dart
View file @
e235ccd7
...
...
@@ -86,6 +86,7 @@ const Size _kDefaultTestViewportSize = Size(800.0, 600.0);
abstract
class
TestWidgetsFlutterBinding
extends
BindingBase
with
SchedulerBinding
,
GestureBinding
,
SemanticsBinding
,
RendererBinding
,
ServicesBinding
,
PaintingBinding
,
...
...
@@ -127,6 +128,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
@protected
bool
get
checkIntrinsicSizes
=>
false
;
@override
bool
disableAnimations
=
false
;
/// Creates and initializes the binding. This function is
/// idempotent; calling it a second time will just return the
/// previously-created instance.
...
...
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