Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
Motion
Overview
Overview
Details
Activity
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
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Dmitriy Stepanets
Motion
Commits
3bc0ad78
Commit
3bc0ad78
authored
Dec 13, 2017
by
Daniel Dahan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
updating can animate checks
parent
fdf4c631
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
113 additions
and
78 deletions
+113
-78
Motion.xcodeproj/project.pbxproj
+1
-1
Sources/Animator/MotionTransitionAnimator.swift
+1
-0
Sources/Extensions/Motion+UIViewController.swift
+5
-5
Sources/Motion+Animate.swift
+32
-28
Sources/Motion+Complete.swift
+2
-1
Sources/Motion+Start.swift
+25
-19
Sources/Motion.swift
+11
-4
Sources/Preprocessors/CascadePreprocessor.swift
+33
-19
Sources/Preprocessors/SourcePreprocessor.swift
+3
-1
No files found.
Motion.xcodeproj/project.pbxproj
View file @
3bc0ad78
...
@@ -207,11 +207,11 @@
...
@@ -207,11 +207,11 @@
96E4095D1F24F7370015A2B5
/* Preprocessors */
=
{
96E4095D1F24F7370015A2B5
/* Preprocessors */
=
{
isa
=
PBXGroup
;
isa
=
PBXGroup
;
children
=
(
children
=
(
96E409621F24F7370015A2B5
/* MotionPreprocessor.swift */
,
965FE96A1FDDA4EA0098BDD0
/* MotionCorePreprocessor.swift */
,
965FE96A1FDDA4EA0098BDD0
/* MotionCorePreprocessor.swift */
,
96E4095E1F24F7370015A2B5
/* CascadePreprocessor.swift */
,
96E4095E1F24F7370015A2B5
/* CascadePreprocessor.swift */
,
96E409601F24F7370015A2B5
/* IgnoreSubviewModifiersPreprocessor.swift */
,
96E409601F24F7370015A2B5
/* IgnoreSubviewModifiersPreprocessor.swift */
,
96E409611F24F7370015A2B5
/* MatchPreprocessor.swift */
,
96E409611F24F7370015A2B5
/* MatchPreprocessor.swift */
,
96E409621F24F7370015A2B5
/* MotionPreprocessor.swift */
,
96E409631F24F7370015A2B5
/* SourcePreprocessor.swift */
,
96E409631F24F7370015A2B5
/* SourcePreprocessor.swift */
,
96E409641F24F7370015A2B5
/* TransitionPreprocessor.swift */
,
96E409641F24F7370015A2B5
/* TransitionPreprocessor.swift */
,
965FE9761FE0976F0098BDD0
/* ConditionalPreprocessor.swift */
,
965FE9761FE0976F0098BDD0
/* ConditionalPreprocessor.swift */
,
...
...
Sources/Animator/MotionTransitionAnimator.swift
View file @
3bc0ad78
...
@@ -123,6 +123,7 @@ internal class MotionTransitionAnimator<T: MotionAnimatorViewContext>: MotionCor
...
@@ -123,6 +123,7 @@ internal class MotionTransitionAnimator<T: MotionAnimatorViewContext>: MotionCor
if
nil
==
v
.
targetState
.
duration
{
if
nil
==
v
.
targetState
.
duration
{
v
.
duration
=
max
(
v
.
duration
,
v
.
snapshot
.
optimizedDuration
(
targetState
:
v
.
targetState
)
+
elapsedTime
)
v
.
duration
=
max
(
v
.
duration
,
v
.
snapshot
.
optimizedDuration
(
targetState
:
v
.
targetState
)
+
elapsedTime
)
}
}
duration
=
max
(
duration
,
v
.
resume
(
at
:
elapsedTime
,
isReversed
:
isReversed
))
duration
=
max
(
duration
,
v
.
resume
(
at
:
elapsedTime
,
isReversed
:
isReversed
))
}
}
...
...
Sources/Extensions/Motion+UIViewController.swift
View file @
3bc0ad78
...
@@ -126,19 +126,19 @@ extension UIViewController {
...
@@ -126,19 +126,19 @@ extension UIViewController {
get
{
get
{
return
transitioningDelegate
is
Motion
return
transitioningDelegate
is
Motion
}
}
set
(
value
)
{
set
(
value
)
{
guard
value
!=
isMotionEnabled
else
{
guard
value
!=
isMotionEnabled
else
{
return
return
}
}
if
value
{
if
value
{
transitioningDelegate
=
Motion
.
shared
transitioningDelegate
=
Motion
.
shared
if
let
v
=
self
as?
UINavigationController
{
if
let
v
=
self
as?
UINavigationController
{
previousNavigationDelegate
=
v
.
delegate
previousNavigationDelegate
=
v
.
delegate
v
.
delegate
=
Motion
.
shared
v
.
delegate
=
Motion
.
shared
}
}
if
let
v
=
self
as?
UITabBarController
{
if
let
v
=
self
as?
UITabBarController
{
previousTabBarDelegate
=
v
.
delegate
previousTabBarDelegate
=
v
.
delegate
v
.
delegate
=
Motion
.
shared
v
.
delegate
=
Motion
.
shared
...
@@ -146,11 +146,11 @@ extension UIViewController {
...
@@ -146,11 +146,11 @@ extension UIViewController {
}
else
{
}
else
{
transitioningDelegate
=
nil
transitioningDelegate
=
nil
if
let
v
=
self
as?
UINavigationController
,
v
.
delegate
is
Motion
{
if
let
v
=
self
as?
UINavigationController
,
v
.
delegate
is
Motion
{
v
.
delegate
=
previousNavigationDelegate
v
.
delegate
=
previousNavigationDelegate
}
}
if
let
v
=
self
as?
UITabBarController
,
v
.
delegate
is
Motion
{
if
let
v
=
self
as?
UITabBarController
,
v
.
delegate
is
Motion
{
v
.
delegate
=
previousTabBarDelegate
v
.
delegate
=
previousTabBarDelegate
}
}
...
...
Sources/Motion+Animate.swift
View file @
3bc0ad78
...
@@ -41,38 +41,33 @@ extension Motion {
...
@@ -41,38 +41,33 @@ extension Motion {
context
.
unhide
(
view
:
tv
)
context
.
unhide
(
view
:
tv
)
}
}
for
(
fv
,
tv
)
in
transitionPairs
{
for
v
in
animatingFromViews
{
for
v
in
fv
{
context
.
hide
(
view
:
v
)
context
.
hide
(
view
:
v
)
}
}
for
v
in
animatingToViews
{
for
v
in
tv
{
context
.
hide
(
view
:
v
)
context
.
hide
(
view
:
v
)
}
}
}
var
t
:
TimeInterval
=
0
var
t
:
TimeInterval
=
0
var
animatorWantsInteractive
=
false
var
animatorWantsInteractive
=
false
if
context
.
insertToViewFirst
{
if
context
.
insertToViewFirst
{
for
(
fv
,
tv
)
in
transitionPairs
{
for
v
in
animatingToViews
{
for
v
in
tv
{
context
.
snapshotView
(
for
:
v
)
context
.
snapshotView
(
for
:
v
)
}
for
v
in
fv
{
context
.
snapshotView
(
for
:
v
)
}
}
}
for
v
in
animatingFromViews
{
context
.
snapshotView
(
for
:
v
)
}
}
else
{
}
else
{
for
(
fv
,
tv
)
in
transitionPairs
{
for
v
in
animatingFromViews
{
for
v
in
fv
{
context
.
snapshotView
(
for
:
v
)
context
.
snapshotView
(
for
:
v
)
}
}
for
v
in
animatingToViews
{
for
v
in
tv
{
context
.
snapshotView
(
for
:
v
)
context
.
snapshotView
(
for
:
v
)
}
}
}
}
}
...
@@ -81,18 +76,27 @@ extension Motion {
...
@@ -81,18 +76,27 @@ extension Motion {
// Therefore we kick off the layout beforehand
// Therefore we kick off the layout beforehand
fromView
?
.
layoutIfNeeded
()
fromView
?
.
layoutIfNeeded
()
for
(
i
,
a
)
in
animators
.
enumerated
()
{
for
animator
in
animators
{
let
d
=
a
.
animate
(
fromViews
:
transitionPairs
[
i
]
.
0
,
toViews
:
transitionPairs
[
i
]
.
1
)
let
duration
=
animator
.
animate
(
fromViews
:
animatingFromViews
.
filter
{
print
(
animator
.
canAnimate
(
view
:
$0
,
isAppearing
:
false
))
return
animator
.
canAnimate
(
view
:
$0
,
isAppearing
:
false
)
},
toViews
:
animatingToViews
.
filter
{
print
(
animator
.
canAnimate
(
view
:
$0
,
isAppearing
:
false
))
return
animator
.
canAnimate
(
view
:
$0
,
isAppearing
:
true
)
})
if
.
infinity
==
d
{
print
(
"T"
,
t
,
"DURATION"
,
duration
)
if
.
infinity
==
duration
{
animatorWantsInteractive
=
true
animatorWantsInteractive
=
true
}
else
{
}
else
{
t
=
max
(
t
,
d
)
t
=
max
(
t
,
d
uration
)
}
}
}
}
totalDuration
=
t
totalDuration
=
t
print
(
"T"
,
t
)
if
let
forceFinishing
=
forceFinishing
{
if
let
forceFinishing
=
forceFinishing
{
complete
(
isFinishing
:
forceFinishing
)
complete
(
isFinishing
:
forceFinishing
)
}
else
if
let
startingProgress
=
startingProgress
{
}
else
if
let
startingProgress
=
startingProgress
{
...
...
Sources/Motion+Complete.swift
View file @
3bc0ad78
...
@@ -72,7 +72,8 @@ extension Motion {
...
@@ -72,7 +72,8 @@ extension Motion {
isNavigationController
=
false
isNavigationController
=
false
isTabBarController
=
false
isTabBarController
=
false
forceNonInteractive
=
false
forceNonInteractive
=
false
transitionPairs
.
removeAll
()
animatingToViews
.
removeAll
()
animatingFromViews
.
removeAll
()
transitionObservers
=
nil
transitionObservers
=
nil
transitionContainer
=
nil
transitionContainer
=
nil
completionCallback
=
nil
completionCallback
=
nil
...
...
Sources/Motion+Start.swift
View file @
3bc0ad78
...
@@ -37,7 +37,7 @@ extension Motion {
...
@@ -37,7 +37,7 @@ extension Motion {
state
=
.
starting
state
=
.
starting
prepare
ToView
()
prepare
ViewFrame
()
prepareViewControllers
()
prepareViewControllers
()
prepareSnapshotView
()
prepareSnapshotView
()
preparePreprocessors
()
preparePreprocessors
()
...
@@ -47,7 +47,8 @@ extension Motion {
...
@@ -47,7 +47,8 @@ extension Motion {
prepareContainer
()
prepareContainer
()
prepareContext
()
prepareContext
()
prepareViewHierarchy
()
prepareViewHierarchy
()
prepareTransitionPairs
()
prepareAnimatingViews
()
prepareToView
()
processPreprocessors
()
processPreprocessors
()
processAnimation
()
processAnimation
()
...
@@ -55,8 +56,8 @@ extension Motion {
...
@@ -55,8 +56,8 @@ extension Motion {
}
}
fileprivate
extension
Motion
{
fileprivate
extension
Motion
{
/// Prepares the
toView instance
.
/// Prepares the
views frames
.
func
prepare
ToView
()
{
func
prepare
ViewFrame
()
{
guard
let
fv
=
fromView
else
{
guard
let
fv
=
fromView
else
{
return
return
}
}
...
@@ -87,7 +88,7 @@ fileprivate extension Motion {
...
@@ -87,7 +88,7 @@ fileprivate extension Motion {
}
}
fullScreenSnapshot
=
v
.
window
?
.
snapshotView
(
afterScreenUpdates
:
false
)
??
fromView
?
.
snapshotView
(
afterScreenUpdates
:
false
)
fullScreenSnapshot
=
v
.
window
?
.
snapshotView
(
afterScreenUpdates
:
false
)
??
fromView
?
.
snapshotView
(
afterScreenUpdates
:
false
)
(
v
.
window
??
transitionContainer
)?
.
addSubview
(
fullScreenSnapshot
)
(
v
.
window
??
v
)?
.
addSubview
(
fullScreenSnapshot
)
if
let
v
=
fromViewController
?
.
motionStoredSnapshot
{
if
let
v
=
fromViewController
?
.
motionStoredSnapshot
{
v
.
removeFromSuperview
()
v
.
removeFromSuperview
()
...
@@ -212,24 +213,28 @@ fileprivate extension Motion {
...
@@ -212,24 +213,28 @@ fileprivate extension Motion {
}
}
/// Prepares the transition fromView & toView pairs.
/// Prepares the transition fromView & toView pairs.
@objc
func
prepareAnimatingViews
()
{
func
prepareTransitionPairs
()
{
animatingFromViews
=
context
.
fromViews
.
filter
{
(
view
)
->
Bool
in
guard
isTransitioning
else
{
for
animator
in
animators
{
return
if
animator
.
canAnimate
(
view
:
view
,
isAppearing
:
false
)
{
return
true
}
}
return
false
}
}
for
a
in
animators
{
animatingToViews
=
context
.
toViews
.
filter
{
(
view
)
->
Bool
in
let
fv
=
context
.
fromViews
.
filter
{
(
view
)
->
Bool
in
for
animator
in
animators
{
return
a
.
canAnimate
(
view
:
view
,
isAppearing
:
false
)
if
animator
.
canAnimate
(
view
:
view
,
isAppearing
:
true
)
{
}
return
true
}
let
tv
=
context
.
toViews
.
filter
{
return
a
.
canAnimate
(
view
:
$0
,
isAppearing
:
true
)
}
}
return
false
transitionPairs
.
append
((
fv
,
tv
))
}
}
}
/// Prepares the to view.
func
prepareToView
()
{
guard
let
tv
=
toView
else
{
guard
let
tv
=
toView
else
{
return
return
}
}
...
@@ -258,6 +263,7 @@ fileprivate extension Motion {
...
@@ -258,6 +263,7 @@ fileprivate extension Motion {
Motion
.
async
{
[
weak
self
]
in
Motion
.
async
{
[
weak
self
]
in
self
?
.
animate
()
self
?
.
animate
()
}
}
}
else
{
}
else
{
animate
()
animate
()
}
}
...
...
Sources/Motion.swift
View file @
3bc0ad78
...
@@ -264,7 +264,8 @@ public class Motion: NSObject, MotionProgressRunnerDelegate {
...
@@ -264,7 +264,8 @@ public class Motion: NSObject, MotionProgressRunnerDelegate {
internal
lazy
var
plugins
=
[
MotionPlugin
]()
internal
lazy
var
plugins
=
[
MotionPlugin
]()
/// The matching fromViews to toViews based on the motionIdentifier value.
/// The matching fromViews to toViews based on the motionIdentifier value.
internal
lazy
var
transitionPairs
=
[(
fromViews
:
[
UIView
],
toViews
:
[
UIView
])]()
internal
var
animatingFromViews
=
[
UIView
]()
internal
var
animatingToViews
=
[
UIView
]()
/// Default animation type.
/// Default animation type.
internal
var
defaultAnimation
=
MotionTransitionType
.
auto
internal
var
defaultAnimation
=
MotionTransitionType
.
auto
...
@@ -363,7 +364,13 @@ public extension Motion {
...
@@ -363,7 +364,13 @@ public extension Motion {
- Parameter elapsedTime t: the current progress, must be between -1...1.
- Parameter elapsedTime t: the current progress, must be between -1...1.
*/
*/
public
func
update
(
elapsedTime
:
TimeInterval
)
{
public
func
update
(
elapsedTime
:
TimeInterval
)
{
self
.
elapsedTime
=
elapsedTime
guard
.
animating
==
state
else
{
startingProgress
=
elapsedTime
return
}
progressRunner
.
stop
()
self
.
elapsedTime
=
Double
(
CGFloat
(
elapsedTime
)
.
clamp
(
0
,
1
))
}
}
/**
/**
...
@@ -818,7 +825,7 @@ extension Motion {
...
@@ -818,7 +825,7 @@ extension Motion {
- Parameter execute: A block that is executed asynchronously on the main thread.
- Parameter execute: A block that is executed asynchronously on the main thread.
*/
*/
public
class
func
async
(
_
execute
:
@escaping
()
->
Void
)
{
public
class
func
async
(
_
execute
:
@escaping
()
->
Void
)
{
Motion
.
delay
(
0
,
execute
:
execute
)
DispatchQueue
.
main
.
async
(
execute
:
execute
)
}
}
/**
/**
...
@@ -833,7 +840,7 @@ extension Motion {
...
@@ -833,7 +840,7 @@ extension Motion {
let
delayed
:
MotionCancelBlock
=
{
let
delayed
:
MotionCancelBlock
=
{
if
!
$0
{
if
!
$0
{
DispatchQueue
.
main
.
async
(
execute
:
execute
)
async
(
execute
)
}
}
cancelable
=
nil
cancelable
=
nil
...
...
Sources/Preprocessors/CascadePreprocessor.swift
View file @
3bc0ad78
...
@@ -33,29 +33,41 @@ public enum CascadeDirection {
...
@@ -33,29 +33,41 @@ public enum CascadeDirection {
case
bottomToTop
case
bottomToTop
case
leftToRight
case
leftToRight
case
rightToLeft
case
rightToLeft
case
radial
(
center
:
CGPoint
)
case
radial
(
center
:
CGPoint
)
case
inverseRadial
(
center
:
CGPoint
)
case
inverseRadial
(
center
:
CGPoint
)
/// Based on the cascade direction a comparator is set.
/// Based on the cascade direction a comparator is set.
var
comparator
:
(
UIView
,
UIView
)
->
Bool
{
var
comparator
:
(
UIView
,
UIView
)
->
Bool
{
switch
self
{
switch
self
{
case
.
topToBottom
:
case
.
topToBottom
:
return
{
return
$0
.
frame
.
minY
<
$1
.
frame
.
minY
}
return
{
return
$0
.
frame
.
minY
<
$1
.
frame
.
minY
}
case
.
bottomToTop
:
case
.
bottomToTop
:
return
{
return
$0
.
frame
.
maxY
==
$1
.
frame
.
maxY
?
$0
.
frame
.
maxX
>
$1
.
frame
.
maxX
:
$0
.
frame
.
maxY
>
$1
.
frame
.
maxY
}
return
{
return
$0
.
frame
.
maxY
==
$1
.
frame
.
maxY
?
$0
.
frame
.
maxX
>
$1
.
frame
.
maxX
:
$0
.
frame
.
maxY
>
$1
.
frame
.
maxY
}
case
.
leftToRight
:
case
.
leftToRight
:
return
{
return
$0
.
frame
.
minX
<
$1
.
frame
.
minX
}
return
{
return
$0
.
frame
.
minX
<
$1
.
frame
.
minX
}
case
.
rightToLeft
:
case
.
rightToLeft
:
return
{
return
$0
.
frame
.
maxX
>
$1
.
frame
.
maxX
}
return
{
return
$0
.
frame
.
maxX
>
$1
.
frame
.
maxX
}
case
.
radial
(
let
center
):
case
.
radial
(
let
center
):
return
{
return
$0
.
center
.
distance
(
center
)
<
$1
.
center
.
distance
(
center
)
}
return
{
return
$0
.
center
.
distance
(
center
)
<
$1
.
center
.
distance
(
center
)
}
case
.
inverseRadial
(
let
center
):
case
.
inverseRadial
(
let
center
):
return
{
return
$0
.
center
.
distance
(
center
)
>
$1
.
center
.
distance
(
center
)
}
return
{
return
$0
.
center
.
distance
(
center
)
>
$1
.
center
.
distance
(
center
)
}
}
}
}
}
}
}
...
@@ -76,24 +88,26 @@ class CascadePreprocessor: MotionCorePreprocessor {
...
@@ -76,24 +88,26 @@ class CascadePreprocessor: MotionCorePreprocessor {
- Parameter views: An Array of UIViews.
- Parameter views: An Array of UIViews.
*/
*/
func
process
(
views
:
[
UIView
])
{
func
process
(
views
:
[
UIView
])
{
for
v
in
views
{
for
view
in
views
{
guard
let
(
deltaTime
,
direction
,
delayMatchedViews
)
=
context
[
v
]?
.
cascade
else
{
guard
let
(
deltaTime
,
direction
,
delayMatchedViews
)
=
context
[
view
]?
.
cascade
else
{
continue
}
continue
var
parentView
=
view
if
view
is
UITableView
,
let
wrapperView
=
view
.
subviews
.
get
(
0
)
{
parentView
=
wrapperView
}
}
let
parentView
=
v
is
UITableView
?
v
.
subviews
.
get
(
0
)
??
v
:
v
let
sortedSubviews
=
parentView
.
subviews
.
sorted
(
by
:
direction
.
comparator
)
let
sortedSubviews
=
parentView
.
subviews
.
sorted
(
by
:
direction
.
comparator
)
let
initialDelay
=
context
[
v
]
!.
delay
let
initialDelay
=
context
[
v
iew
]
!.
delay
let
finalDelay
=
TimeInterval
(
sortedSubviews
.
count
)
*
deltaTime
+
initialDelay
let
finalDelay
=
TimeInterval
(
sortedSubviews
.
count
)
*
deltaTime
+
initialDelay
for
(
i
,
subview
)
in
sortedSubviews
.
enumerated
()
{
for
(
i
,
subview
)
in
sortedSubviews
.
enumerated
()
{
let
delay
=
TimeInterval
(
i
)
*
deltaTime
+
initialDelay
let
delay
=
TimeInterval
(
i
)
*
deltaTime
+
initialDelay
func
applyDelay
(
view
:
UIView
)
{
func
applyDelay
(
view
:
UIView
)
{
if
context
.
pairedView
(
for
:
view
)
==
nil
{
if
context
.
pairedView
(
for
:
view
)
==
nil
{
context
[
view
]?
.
delay
=
delay
context
[
view
]?
.
delay
=
delay
}
else
if
delayMatchedViews
,
let
paired
=
context
.
pairedView
(
for
:
view
)
{
}
else
if
delayMatchedViews
,
let
paired
=
context
.
pairedView
(
for
:
view
)
{
context
[
view
]?
.
delay
=
finalDelay
context
[
view
]?
.
delay
=
finalDelay
context
[
paired
]?
.
delay
=
finalDelay
context
[
paired
]?
.
delay
=
finalDelay
...
@@ -103,7 +117,7 @@ class CascadePreprocessor: MotionCorePreprocessor {
...
@@ -103,7 +117,7 @@ class CascadePreprocessor: MotionCorePreprocessor {
applyDelay
(
view
:
subview
)
applyDelay
(
view
:
subview
)
}
}
}
}
applyDelay
(
view
:
subview
)
applyDelay
(
view
:
subview
)
}
}
}
}
...
...
Sources/Preprocessors/SourcePreprocessor.swift
View file @
3bc0ad78
...
@@ -51,7 +51,9 @@ class SourcePreprocessor: MotionCorePreprocessor {
...
@@ -51,7 +51,9 @@ class SourcePreprocessor: MotionCorePreprocessor {
prepare
(
view
:
tv
,
for
:
fv
)
prepare
(
view
:
tv
,
for
:
fv
)
}
}
}
}
}
fileprivate
extension
SourcePreprocessor
{
/**
/**
Prepares a given view for a target view.
Prepares a given view for a target view.
- Parameter view: A UIView.
- Parameter view: A UIView.
...
@@ -112,7 +114,7 @@ class SourcePreprocessor: MotionCorePreprocessor {
...
@@ -112,7 +114,7 @@ class SourcePreprocessor: MotionCorePreprocessor {
if
view
.
layer
.
contentsScale
!=
targetView
.
layer
.
contentsScale
{
if
view
.
layer
.
contentsScale
!=
targetView
.
layer
.
contentsScale
{
state
.
contentsScale
=
targetView
.
layer
.
contentsScale
state
.
contentsScale
=
targetView
.
layer
.
contentsScale
}
}
context
[
view
]
=
state
context
[
view
]
=
state
}
}
}
}
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