Skip to content

Commit

Permalink
Add VisionOS events and entities (#857)
Browse files Browse the repository at this point in the history
* Add new VisionOS classes

* Add tests

* Update demo

* Standardise ID capitalisation

* Automatically track window group entity with OpenWindow and DismissWindow events

* Make string IDs the main property

* Add ImmersiveSpaceStateMachine

* Update demo app

* Update following merge

* Add immersive space entity to open event when autotracking is off

* Small review changes

* Always update state on open event

* Correct visionOS capitalisation

* Add immersive space entity by default

* Generate viewId UUID for immersive space
  • Loading branch information
mscwilson authored Jan 19, 2024
1 parent 4831e4d commit 43f6453
Show file tree
Hide file tree
Showing 19 changed files with 1,104 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Examples
Submodule Examples updated 148 files
5 changes: 5 additions & 0 deletions Sources/Core/InternalQueue/TrackerControllerIQWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ class TrackerControllerIQWrapper: TrackerController {
get { return InternalQueue.sync { controller.userAnonymisation } }
set { InternalQueue.sync { controller.userAnonymisation = newValue } }
}

var immersiveSpaceContext: Bool {
get { return InternalQueue.sync { controller.immersiveSpaceContext } }
set { InternalQueue.sync { controller.immersiveSpaceContext = newValue } }
}

var advertisingIdentifierRetriever: (() -> UUID?)? {
get { return InternalQueue.sync { controller.advertisingIdentifierRetriever } }
Expand Down
30 changes: 30 additions & 0 deletions Sources/Core/StateMachine/ImmersiveSpaceState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2013-2023 Snowplow Analytics Ltd. All rights reserved.
//
// This program is licensed to you under the Apache License Version 2.0,
// and you may not use this file except in compliance with the Apache License
// Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
// http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the Apache License Version 2.0 is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the Apache License Version 2.0 for the specific
// language governing permissions and limitations there under.

import Foundation

class ImmersiveSpaceState: State {
var dismissEventTracked = false

var id: String
var viewId: UUID?
var immersionStyle: ImmersionStyle?
var upperLimbVisibility: UpperLimbVisibility?

init(id: String, viewId: UUID? = nil, immersionStyle: ImmersionStyle? = nil, upperLimbVisibility: UpperLimbVisibility? = nil) {
self.id = id
self.viewId = viewId
self.immersionStyle = immersionStyle
self.upperLimbVisibility = upperLimbVisibility
}
}
107 changes: 107 additions & 0 deletions Sources/Core/StateMachine/ImmersiveSpaceStateMachine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright (c) 2013-2023 Snowplow Analytics Ltd. All rights reserved.
//
// This program is licensed to you under the Apache License Version 2.0,
// and you may not use this file except in compliance with the Apache License
// Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
// http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the Apache License Version 2.0 is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the Apache License Version 2.0 for the specific
// language governing permissions and limitations there under.

import Foundation

class ImmersiveSpaceStateMachine: StateMachineProtocol {

static var identifier: String { return "ImmersiveSpace" }
var identifier: String { return ImmersiveSpaceStateMachine.identifier }

var subscribedEventSchemasForEventsBefore: [String] {
return []
}

var subscribedEventSchemasForTransitions: [String] {
return [swiftuiOpenImmersiveSpaceSchema, swiftuiDismissImmersiveSpaceSchema]
}

var subscribedEventSchemasForEntitiesGeneration: [String] {
return ["*"]
}

var subscribedEventSchemasForPayloadUpdating: [String] {
return []
}

var subscribedEventSchemasForAfterTrackCallback: [String] {
return []
}

var subscribedEventSchemasForFiltering: [String] {
return []
}

func eventsBefore(event: Event) -> [Event]? {
return nil
}

func transition(from event: Event, state: State?) -> State? {
if let e = event as? OpenImmersiveSpaceEvent {
return ImmersiveSpaceState(
id: e.id,
viewId: e.viewId,
immersionStyle: e.immersionStyle,
upperLimbVisibility: e.upperLimbVisibility
)
} else {
if let s = state as? ImmersiveSpaceState {
if s.dismissEventTracked {
return nil
}
// state persists for the first Dismiss event after an Open
let currentState = ImmersiveSpaceState(
id: s.id,
viewId: s.viewId,
immersionStyle: s.immersionStyle,
upperLimbVisibility: s.upperLimbVisibility
)
currentState.dismissEventTracked = true
return currentState
}
}
return nil
}

func entities(from event: InspectableEvent, state: State?) -> [SelfDescribingJson]? {
// the open event already has the entity
if event.schema == swiftuiOpenImmersiveSpaceSchema {
return nil
}

if let s = state as? ImmersiveSpaceState {
if s.dismissEventTracked == true && event.schema != swiftuiDismissImmersiveSpaceSchema {
return nil
}
let entity = ImmersiveSpaceEntity(
id: s.id,
viewId: s.viewId,
immersionStyle: s.immersionStyle,
upperLimbVisibility: s.upperLimbVisibility
)
return [entity]
}
return nil
}

func payloadValues(from event: InspectableEvent, state: State?) -> [String : Any]? {
return nil
}

func afterTrack(event: InspectableEvent) {
}

func filter(event: InspectableEvent, state: State?) -> Bool? {
return nil
}
}
1 change: 1 addition & 0 deletions Sources/Core/Tracker/ServiceProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ class ServiceProvider: NSObject, ServiceProviderProtocol {
tracker.installEvent = trackerConfiguration.installAutotracking
tracker.trackerDiagnostic = trackerConfiguration.diagnosticAutotracking
tracker.userAnonymisation = trackerConfiguration.userAnonymisation
tracker.immersiveSpaceContext = trackerConfiguration.immersiveSpaceContext
tracker.advertisingIdentifierRetriever = trackerConfiguration.advertisingIdentifierRetriever
if gdprConfiguration.sourceConfig != nil {
tracker.gdprContext = GDPRContext(
Expand Down
15 changes: 15 additions & 0 deletions Sources/Core/Tracker/Tracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,21 @@ class Tracker: NSObject {
}
}
}

private var _immersiveSpaceContext = false
var immersiveSpaceContext: Bool {
get {
return _immersiveSpaceContext
}
set(immersiveSpaceContext) {
self._immersiveSpaceContext = immersiveSpaceContext
if immersiveSpaceContext {
self.addOrReplace(stateMachine: ImmersiveSpaceStateMachine())
} else {
_ = self.stateManager.removeStateMachine(ImmersiveSpaceStateMachine.identifier)
}
}
}

/// GDPR context
/// You can enable or disable the context by setting this property
Expand Down
10 changes: 10 additions & 0 deletions Sources/Core/Tracker/TrackerControllerImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,16 @@ class TrackerControllerImpl: Controller, TrackerController {
tracker.userAnonymisation = newValue
}
}

var immersiveSpaceContext: Bool {
get {
return tracker.immersiveSpaceContext
}
set {
dirtyConfig.immersiveSpaceContext = newValue
tracker.immersiveSpaceContext = newValue
}
}

var advertisingIdentifierRetriever: (() -> UUID?)? {
get {
Expand Down
1 change: 1 addition & 0 deletions Sources/Core/Tracker/TrackerDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ class TrackerDefaults {
private(set) static var platformContext = true
private(set) static var geoLocationContext = false
private(set) static var screenEngagementAutotracking = true
private(set) static var immersiveSpaceContext = true
}
8 changes: 8 additions & 0 deletions Sources/Core/TrackerConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ let kSPErrorSchema = "iglu:com.snowplowanalytics.snowplow/application_error/json
let kSPApplicationInstallSchema = "iglu:com.snowplowanalytics.mobile/application_install/jsonschema/1-0-0"
let kSPGdprContextSchema = "iglu:com.snowplowanalytics.snowplow/gdpr/jsonschema/1-0-0"
let kSPDiagnosticErrorSchema = "iglu:com.snowplowanalytics.snowplow/diagnostic_error/jsonschema/1-0-0"

let ecommerceActionSchema = "iglu:com.snowplowanalytics.snowplow.ecommerce/snowplow_ecommerce_action/jsonschema/1-0-2"
let ecommerceProductSchema = "iglu:com.snowplowanalytics.snowplow.ecommerce/product/jsonschema/1-0-0"
let ecommerceCartSchema = "iglu:com.snowplowanalytics.snowplow.ecommerce/cart/jsonschema/1-0-0"
Expand All @@ -75,6 +76,13 @@ let kSPScreenSummarySchema = "iglu:com.snowplowanalytics.mobile/screen_summary/j
let kSPListItemViewSchema = "iglu:com.snowplowanalytics.mobile/list_item_view/jsonschema/1-0-0"
let kSPScrollChangedSchema = "iglu:com.snowplowanalytics.mobile/scroll_changed/jsonschema/1-0-0"

let swiftuiOpenWindowSchema = "iglu:com.apple.swiftui/open_window/jsonschema/1-0-0"
let swiftuiDismissWindowSchema = "iglu:com.apple.swiftui/dismiss_window/jsonschema/1-0-0"
let swiftuiOpenImmersiveSpaceSchema = "iglu:com.apple.swiftui/open_immersive_space/jsonschema/1-0-0"
let swiftuiDismissImmersiveSpaceSchema = "iglu:com.apple.swiftui/dismiss_immersive_space/jsonschema/1-0-0"
let swiftuiWindowGroupSchema = "iglu:com.apple.swiftui/window_group/jsonschema/1-0-0"
let swiftuiImmersiveSpaceSchema = "iglu:com.apple.swiftui/immersive_space/jsonschema/1-0-0"

// --- Event Keys
let kSPEventPageView = "pv"
let kSPEventStructured = "se"
Expand Down
Loading

0 comments on commit 43f6453

Please sign in to comment.