Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
1
1weather
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
1weather
Commits
ac44ac1a
Commit
ac44ac1a
authored
Jun 22, 2021
by
Dmitriy Stepanets
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added analytics to the shorts
parent
7b560730
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
135 additions
and
65 deletions
+135
-65
1Weather.xcodeproj/xcuserdata/dstepanets.xcuserdatad/xcschemes/xcschememanagement.plist
+1
-1
1Weather/Network/PushNotificationsManager.swift
+1
-1
1Weather/Network/ShortsManager.swift
+0
-1
1Weather/UI/Helpers/ForecastTimePeriod/ForecastTimePeriodView.swift
+0
-1
1Weather/UI/View controllers/Shorts/ShortsViewController.swift
+68
-3
1Weather/UI/View controllers/Today/Cells/TodayCellFactory.swift
+2
-0
1Weather/UI/View controllers/Today/Shorts/ShortsView.swift
+13
-0
1Weather/ViewModels/ShortsViewModel.swift
+6
-0
1Weather/ViewModels/TodayViewModel.swift
+26
-14
OneWeatherAnalytics/OneWeatherAnalytics/AnalyticsEvent.swift
+8
-0
OneWeatherAnalytics/OneWeatherAnalytics/AnalyticsParameter.swift
+5
-1
OneWeatherAnalytics/OneWeatherAnalytics/Services/AppsFlyerAnalyticsService.swift
+5
-1
PG.playground/Contents.swift
+0
-42
No files found.
1Weather.xcodeproj/xcuserdata/dstepanets.xcuserdatad/xcschemes/xcschememanagement.plist
View file @
ac44ac1a
...
...
@@ -12,7 +12,7 @@
<
k
e
y
>
OneWeatherNotificationServiceExtension.xcscheme_
^#
shared
#^
_
<
/k
e
y
>
<
d
i
c
t
>
<
k
e
y
>
orderHint
<
/k
e
y
>
<
int
e
g
e
r
>
59
<
/int
e
g
e
r
>
<
int
e
g
e
r
>
61
<
/int
e
g
e
r
>
<
/
d
i
c
t
>
<
k
e
y
>
PG
(
Playground
)
1.xcscheme
<
/k
e
y
>
<
d
i
c
t
>
...
...
1Weather/Network/PushNotificationsManager.swift
View file @
ac44ac1a
...
...
@@ -236,7 +236,7 @@ extension PushNotificationsManager: UNUserNotificationCenterDelegate {
if
UIApplication
.
shared
.
applicationState
==
.
active
{
analytics
(
log
:
.
ANALYTICS_PUSH_RECEIVED
)
}
else
{
analytics
(
log
:
.
ANALYTICS_PUSH_SELECTED
,
params
:
[
.
ANALYTICS_KEY_
PUSH_NOTIFICATION_
SOURCE
:
"background"
])
analytics
(
log
:
.
ANALYTICS_PUSH_SELECTED
,
params
:
[
.
ANALYTICS_KEY_SOURCE
:
"background"
])
}
log
.
debug
(
"Got a push notification:
\(
notification
.
request
.
content
.
userInfo
)
"
);
...
...
1Weather/Network/ShortsManager.swift
View file @
ac44ac1a
...
...
@@ -82,7 +82,6 @@ class ShortsManager {
return
}
print
(
"[ShortsManager] Mark short as viewed at index:
\(
sourceIndex
)
"
)
self
.
shorts
[
sourceIndex
]
.
markAsViewed
()
}
}
1Weather/UI/Helpers/ForecastTimePeriod/ForecastTimePeriodView.swift
View file @
ac44ac1a
...
...
@@ -299,7 +299,6 @@ class ForecastTimePeriodView: UIView {
self
.
graphView
.
drawAdditionalGraph
(
with
:
[
CGPoint
]())
self
.
tintGraphAt
(
button
:
selectedButton
)
}
print
(
"[ForecastTimePeriod] Draw graph"
)
}
...
...
1Weather/UI/View controllers/Shorts/ShortsViewController.swift
View file @
ac44ac1a
...
...
@@ -8,6 +8,7 @@
import
UIKit
import
OneWeatherCore
import
pop
import
OneWeatherAnalytics
private
enum
ScrollDirection
{
case
swipeDown
//toTop
...
...
@@ -26,9 +27,19 @@ class ShortsViewController: UIViewController {
private
var
lastOffset
:
CGFloat
=
0
private
var
itemIndexToScroll
:
Int
?
private
var
swipeUpCounter
=
0
private
var
source
:
String
?
private
var
shortsViewTimeSpentSec
=
0
private
lazy
var
viewScheduler
:
Scheduler
=
{
let
scheduler
=
Scheduler
(
with
:
"shorts_binge_view_time"
,
interval
:
1
,
onlyCountTimeWhenRunning
:
true
,
action
:
{
self
.
shortsViewTimeSpentSec
+=
1
})
return
scheduler
}()
deinit
{
print
(
"[ShortsViewController] deinit"
)
coordinator
.
viewControllerDidEnd
(
controller
:
self
)
}
...
...
@@ -54,26 +65,43 @@ class ShortsViewController: UIViewController {
super
.
viewWillAppear
(
animated
)
guard
!
viewModel
.
shorts
.
isEmpty
else
{
return
}
viewScheduler
.
start
()
if
let
indexToScroll
=
itemIndexToScroll
{
source
=
"card_click"
tableView
.
scrollToRow
(
at
:
[
0
,
indexToScroll
],
at
:
.
top
,
animated
:
false
)
lastOffset
=
tableView
.
contentOffset
.
y
updateView
(
basedOnDirection
:
.
swipeUp
,
willShowRowAt
:
indexToScroll
)
itemIndexToScroll
=
nil
viewModel
.
markAsViewed
(
atIndex
:
indexToScroll
)
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_VIEW_SHORTS
,
params
:
[
.
ANALYTICS_KEY_SHORTS_CARD_ID
:
viewModel
.
shorts
[
indexToScroll
]
.
id
,
.
ANALYTICS_KEY_SOURCE
:
"card_click"
])
}
else
{
source
=
"others"
lastOffset
=
0
updateView
(
basedOnDirection
:
.
swipeDown
,
willShowRowAt
:
0
)
tableView
.
scrollToRow
(
at
:
[
0
,
0
],
at
:
.
top
,
animated
:
false
)
viewModel
.
markAsViewed
(
atIndex
:
0
)
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_VIEW_SHORTS
,
params
:
[
.
ANALYTICS_KEY_SHORTS_CARD_ID
:
viewModel
.
shorts
[
0
]
.
id
,
.
ANALYTICS_KEY_SOURCE
:
"others"
])
}
}
override
func
viewWillDisappear
(
_
animated
:
Bool
)
{
super
.
viewWillDisappear
(
animated
)
source
=
nil
viewScheduler
.
stop
()
viewScheduler
.
resetTimer
()
shortsViewTimeSpentSec
=
0
swipeUpCounter
=
0
ShortsManager
.
shared
.
reordering
()
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_EXIT_SHORTS_VIEW
,
params
:
nil
)
}
private
func
scrollTo
(
newOffset
:
CGPoint
,
velocity
:
CGPoint
)
{
...
...
@@ -145,6 +173,40 @@ class ShortsViewController: UIViewController {
}
private
func
logShortsAnalytics
(
source
:
String
,
forRowAtIndex
index
:
Int
)
{
let
shortsSource
:
String
if
self
.
source
!=
nil
{
shortsSource
=
self
.
source
!
self
.
source
=
nil
}
else
{
shortsSource
=
source
}
var
timeSpent
:
String
switch
shortsViewTimeSpentSec
{
case
0
...
2
:
timeSpent
=
"<3s"
case
3
...
4
:
timeSpent
=
"3-5s"
case
5
...
9
:
timeSpent
=
"5-10s"
case
10
...
19
:
timeSpent
=
"10-20s"
case
20
...
Int
.
max
:
timeSpent
=
">20s"
default
:
timeSpent
=
""
}
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_CARD_BINGE_VIEW
,
params
:
[
.
ANALYTICS_KEY_SHORTS_CARD_ID
:
viewModel
.
shorts
[
index
]
.
id
,
.
ANALYTICS_KEY_SOURCE
:
shortsSource
,
.
ANALYTICS_KEY_SHORTS_TIME_SPENT
:
timeSpent
])
viewScheduler
.
resetTimer
()
shortsViewTimeSpentSec
=
0
}
func
set
(
indexToScroll
:
Int
)
{
itemIndexToScroll
=
indexToScroll
}
...
...
@@ -251,13 +313,15 @@ extension ShortsViewController: UITableViewDelegate {
updateView
(
basedOnDirection
:
direction
,
willShowRowAt
:
nextRowIndexPath
.
row
)
let
offset
=
tableView
.
contentSize
.
height
-
tableView
.
frame
.
height
self
.
scrollTo
(
newOffset
:
.
init
(
x
:
tableView
.
contentOffset
.
x
,
y
:
offset
),
velocity
:
velocity
)
self
.
logShortsAnalytics
(
source
:
"swipe_up"
,
forRowAtIndex
:
rowsCount
-
1
)
return
}
case
.
swipeDown
:
if
topRowIndexPath
.
row
==
0
{
self
.
scrollTo
(
newOffset
:
.
zero
,
velocity
:
velocity
)
viewModel
.
markAsViewed
(
atIndex
:
0
)
updateView
(
basedOnDirection
:
direction
,
willShowRowAt
:
0
)
self
.
scrollTo
(
newOffset
:
.
zero
,
velocity
:
velocity
)
self
.
logShortsAnalytics
(
source
:
"swipe_down"
,
forRowAtIndex
:
0
)
return
}
...
...
@@ -268,6 +332,7 @@ extension ShortsViewController: UITableViewDelegate {
viewModel
.
markAsViewed
(
atIndex
:
nextRowIndexPath
.
row
)
updateView
(
basedOnDirection
:
direction
,
willShowRowAt
:
nextRowIndexPath
.
row
)
self
.
scrollTo
(
newOffset
:
nextRowRect
.
origin
,
velocity
:
velocity
)
self
.
logShortsAnalytics
(
source
:
direction
==
.
swipeDown
?
"swipe_down"
:
"swipe_up"
,
forRowAtIndex
:
nextRowIndexPath
.
row
-
1
)
}
func
scrollViewWillBeginDragging
(
_
scrollView
:
UIScrollView
)
{
...
...
1Weather/UI/View controllers/Today/Cells/TodayCellFactory.swift
View file @
ac44ac1a
...
...
@@ -184,6 +184,8 @@ class TodayCellFactory: CellFactoryProtocol {
moonCell
.
updateMoonPosition
()
case
let
adCell
as
AdCell
:
adCell
.
adView
?
.
start
()
case
cell
as
TodayShortsCell
:
todayViewModel
.
logShortsSectionEvent
()
default
:
break
}
...
...
1Weather/UI/View controllers/Today/Shorts/ShortsView.swift
View file @
ac44ac1a
...
...
@@ -7,6 +7,7 @@
import
UIKit
import
OneWeatherCore
import
OneWeatherAnalytics
protocol
ShortsViewDelegate
:
AnyObject
{
func
didSelectShort
(
at
index
:
Int
)
...
...
@@ -87,8 +88,20 @@ extension ShortsView: UICollectionViewDelegate {
}
}
func
scrollViewDidEndDecelerating
(
_
scrollView
:
UIScrollView
)
{
let
visiblePath
=
(
collectionView
.
indexPathsForVisibleItems
.
sorted
{
$0
.
row
<
$1
.
row
})
guard
let
lastVisibleRow
=
visiblePath
.
last
?
.
row
else
{
return
}
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_CARD_VIEW
,
params
:
[
.
ANALYTICS_KEY_SHORTS_CARD_POSITION
:
lastVisibleRow
])
}
func
collectionView
(
_
collectionView
:
UICollectionView
,
didSelectItemAt
indexPath
:
IndexPath
)
{
delegate
?
.
didSelectShort
(
at
:
indexPath
.
row
)
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_CARD_CLICK
,
params
:
[
.
ANALYTICS_KEY_SHORTS_CARD_ID
:
shorts
[
indexPath
.
row
]
.
id
,
.
ANALYTICS_KEY_SHORTS_CARD_POSITION
:
indexPath
.
row
,
.
ANALYTICS_KEY_SOURCE
:
"TODAY"
])
}
}
...
...
1Weather/ViewModels/ShortsViewModel.swift
View file @
ac44ac1a
...
...
@@ -7,6 +7,7 @@
import
UIKit
import
OneWeatherCore
import
OneWeatherAnalytics
class
ShortsViewModel
:
ViewModelProtocol
{
//Private
...
...
@@ -30,12 +31,17 @@ class ShortsViewModel: ViewModelProtocol {
func
like
(
item
:
ShortsItem
)
{
shortsManager
.
like
(
item
:
item
)
self
.
delegate
?
.
viewModelDidChange
(
model
:
self
)
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_LIKE_BUTTON_CLICK
,
params
:
[
.
ANALYTICS_KEY_SHORTS_CARD_ID
:
item
.
id
])
}
func
openMore
(
item
:
ShortsItem
)
{
guard
let
url
=
item
.
ctaURL
else
{
return
}
if
UIApplication
.
shared
.
canOpenURL
(
url
)
{
UIApplication
.
shared
.
open
(
url
,
options
:
[:])
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_READ_MORE_CLICK
,
params
:
[
.
ANALYTICS_KEY_SHORTS_CARD_ID
:
item
.
id
,
.
ANALYTICS_KEY_SHORTS_READ_MORE_VIEW
:
"external browser"
])
}
}
...
...
1Weather/ViewModels/TodayViewModel.swift
View file @
ac44ac1a
...
...
@@ -20,9 +20,12 @@ class TodayViewModel: ViewModelProtocol {
public
weak
var
delegate
:
TodayViewModelDelegate
?
//Private
private
let
kAnalyticsThresholdSec
=
2.5
private
let
log
=
Logger
(
componentName
:
"TodayViewModel"
)
private
let
locationManager
:
LocationManager
!
=
LocationManager
.
shared
private
var
ccpaHelper
=
CCPAHelper
.
shared
private
let
shortsManager
:
ShortsManager
private
var
canSendAnalytics
=
true
private(set)
var
location
:
Location
?
var
shorts
:
[
ShortsItem
]
{
return
ShortsManager
.
shared
.
shorts
...
...
@@ -47,6 +50,7 @@ class TodayViewModel: ViewModelProtocol {
NotificationCenter
.
default
.
addObserver
(
self
,
selector
:
#selector(
handlePremiumStateChange
)
,
name
:
Notification
.
Name
(
rawValue
:
kEventInAppPurchasedCompleted
),
object
:
nil
)
}
//MARK: Private
@objc
private
func
handlePremiumStateChange
()
{
onMain
{
...
...
@@ -55,13 +59,6 @@ class TodayViewModel: ViewModelProtocol {
}
}
public
func
updateWeather
()
{
locationManager
.
updateEverythingIfNeeded
()
if
shortsManager
.
shortsAvailable
{
shortsManager
.
refreshShorts
()
}
}
private
func
initializeAllAdsIfNeeded
()
{
onMain
{
let
adManager
=
AdManager
.
shared
...
...
@@ -83,7 +80,19 @@ class TodayViewModel: ViewModelProtocol {
}
}
private
var
ccpaHelper
=
CCPAHelper
.
shared
private
func
onboardingFlowCompleted
()
{
self
.
initializeAllAdsIfNeeded
()
PushNotificationsManager
.
shared
.
registerForRemoteNotifications
()
analytics
(
log
:
.
ANALYTICS_VIEW_TODAY
)
}
//MARK: Public
public
func
updateWeather
()
{
locationManager
.
updateEverythingIfNeeded
()
if
shortsManager
.
shortsAvailable
{
shortsManager
.
refreshShorts
()
}
}
public
func
privacyPolicyHasBeenViewed
()
{
ccpaHelper
.
shownPrivacyNoticeBefore
=
true
...
...
@@ -112,14 +121,17 @@ class TodayViewModel: ViewModelProtocol {
}
}
public
func
openShorts
(
atIndex
index
:
Int
)
{
AppCoordinator
.
instance
.
openShorts
(
atIndex
:
index
)
public
func
logShortsSectionEvent
()
{
guard
canSendAnalytics
else
{
return
}
canSendAnalytics
=
false
AppAnalytics
.
shared
.
log
(
event
:
.
ANALYTICS_SHORTS_SECTION_VIEW
,
params
:
nil
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
kAnalyticsThresholdSec
)
{
self
.
canSendAnalytics
=
true
}
}
private
func
onboardingFlowCompleted
()
{
self
.
initializeAllAdsIfNeeded
()
PushNotificationsManager
.
shared
.
registerForRemoteNotifications
()
analytics
(
log
:
.
ANALYTICS_VIEW_TODAY
)
public
func
openShorts
(
atIndex
index
:
Int
)
{
AppCoordinator
.
instance
.
openShorts
(
atIndex
:
index
)
}
}
...
...
OneWeatherAnalytics/OneWeatherAnalytics/AnalyticsEvent.swift
View file @
ac44ac1a
...
...
@@ -89,6 +89,14 @@ public enum AnalyticsEvent: String {
case
ANALYTICS_A9_BID_RECEIVED
=
"A9_BID_RECEIVED"
case
ANALYTICS_AD_CLICKED
=
"AD_CLICKED"
case
ANALYTICS_APP_OPEN
=
"APP_OPEN"
case
ANALYTICS_SHORTS_SECTION_VIEW
=
"SHORTS_SECTION_VIEW"
case
ANALYTICS_SHORTS_CARD_VIEW
=
"SHORTS_CARD_VIEW"
case
ANALYTICS_SHORTS_CARD_CLICK
=
"SHORTS_CARD_CLICK"
case
ANALYTICS_SHORTS_CARD_BINGE_VIEW
=
"SHORTS_CARD_BINGE_VIEW"
case
ANALYTICS_SHORTS_VIEW_SHORTS
=
"VIEW_SHORTS"
case
ANALYTICS_SHORTS_READ_MORE_CLICK
=
"READ_MORE_CLICK"
case
ANALYTICS_SHORTS_LIKE_BUTTON_CLICK
=
"LIKE_BUTTON_CLICK"
case
ANALYTICS_SHORTS_EXIT_SHORTS_VIEW
=
"EXIT_SHORTS_VIEW"
/// FTUE Funnel: User has saved his first city after installing the app.
case
ANALYTICS_USER_QUALIFIED
=
"USER_QUALIFIED"
...
...
OneWeatherAnalytics/OneWeatherAnalytics/AnalyticsParameter.swift
View file @
ac44ac1a
...
...
@@ -9,6 +9,7 @@ import Foundation
public
enum
AnalyticsParameter
:
String
{
//TODO: rename to Swifty names. This is a legacy from the old app.
case
ANALYTICS_KEY_SOURCE
=
"source"
case
ANALYTICS_KEY_CCPA_TODAY_DISMISS_ACTION
=
"ACTION"
case
ANALYTICS_KEY_CCPA_TODAY_CCPA_EXPT_3_VERSION
=
"CCPA_EXPT_3_VERSION"
case
ANALYTICS_KEY_LAST_SEEN_CITY_CITY_ID
=
"cityId"
...
...
@@ -18,7 +19,10 @@ public enum AnalyticsParameter: String {
case
ANALYTICS_KEY_PLACEMENT_NAME
=
"placement_name"
case
ANALYTICS_KEY_AD_UNIT_ID
=
"AD_PLACEMENT_ID"
case
ANALYTICS_KEY_AD_ADAPTER
=
"AD_ADAPTER"
case
ANALYTICS_KEY_PUSH_NOTIFICATION_SOURCE
=
"source"
case
ANALYTICS_KEY_THEME_CHANGE_NAME
=
"themeName"
case
ANALYTICS_KEY_FIST_OPEN_SOURCE
=
"Source"
case
ANALYTICS_KEY_SHORTS_CARD_POSITION
=
"position"
case
ANALYTICS_KEY_SHORTS_CARD_ID
=
"card_id"
case
ANALYTICS_KEY_SHORTS_TIME_SPENT
=
"time_spent"
case
ANALYTICS_KEY_SHORTS_READ_MORE_VIEW
=
"view"
}
OneWeatherAnalytics/OneWeatherAnalytics/Services/AppsFlyerAnalyticsService.swift
View file @
ac44ac1a
...
...
@@ -10,7 +10,11 @@ import AppsFlyerLib
internal
struct
AppsFlyerAnalyticsService
:
AnalyticsService
{
public
let
name
:
String
=
"AppsFlyer"
let
eventsWhitelist
:
Set
<
AnalyticsEvent
>
?
=
[
.
ANALYTICS_APP_OPEN
,
.
ANALYTICS_USER_QUALIFIED
,
.
ANALYTICS_D3_RETAINED
,
.
ANALYTICS_FIRST_OPEN
]
let
eventsWhitelist
:
Set
<
AnalyticsEvent
>
?
=
[
.
ANALYTICS_APP_OPEN
,
.
ANALYTICS_USER_QUALIFIED
,
.
ANALYTICS_D3_RETAINED
,
.
ANALYTICS_FIRST_OPEN
,
.
ANALYTICS_SHORTS_CARD_BINGE_VIEW
]
let
attributesWhitelist
:
Set
<
AnalyticsAttribute
>
?
=
[]
// block all
func
log
(
event
:
AnalyticsEvent
,
params
:
[
AnalyticsParameter
:
Any
]?)
{
...
...
PG.playground/Contents.swift
View file @
ac44ac1a
import
Foundation
import
UIKit
extension
Array
{
mutating
func
move
(
from
oldIndex
:
Index
,
to
newIndex
:
Index
)
{
// Don't work for free and use swap when indices are next to each other - this
// won't rebuild array and will be super efficient.
if
oldIndex
==
newIndex
{
return
}
if
abs
(
newIndex
-
oldIndex
)
==
1
{
return
self
.
swapAt
(
oldIndex
,
newIndex
)
}
self
.
insert
(
self
.
remove
(
at
:
oldIndex
),
at
:
newIndex
)
}
}
struct
Element
{
let
isViewed
:
Bool
let
value
:
String
}
var
arr
:[
Element
]
=
[
.
init
(
isViewed
:
true
,
value
:
"a"
),
.
init
(
isViewed
:
true
,
value
:
"b"
),
.
init
(
isViewed
:
true
,
value
:
"c"
),
.
init
(
isViewed
:
false
,
value
:
"d"
),
.
init
(
isViewed
:
false
,
value
:
"e"
),
.
init
(
isViewed
:
false
,
value
:
"f"
),
.
init
(
isViewed
:
false
,
value
:
"g"
),
.
init
(
isViewed
:
false
,
value
:
"h"
),
.
init
(
isViewed
:
false
,
value
:
"i"
)]
var
indexesToReorder
=
[
Int
]()
for
(
index
,
element
)
in
arr
.
enumerated
()
{
if
element
.
isViewed
{
indexesToReorder
.
append
(
index
)
}
}
arr
=
arr
.
filter
{
!
$0
.
isViewed
}
+
arr
.
filter
{
$0
.
isViewed
}
//for index in indexesToReorder {
// arr.move(from: index, to: arr.count - 1)
//}
print
(
arr
.
map
{
$0
.
value
})
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