From 5a3ea0afcf2c767e038b15599abfcd7d79a0f890 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Wed, 15 Jan 2025 16:57:54 -0700 Subject: [PATCH] feat(analytics): other iOS GA updates (#652) * feat(analytics): log searches * feat(analytics): log vehicle taps, separate legacy from new stop details * feat(analytics): log session/user variables * fix iOS tests * use the regular LocationDataManager for HomeMapView avoids creating a new one every time ContentView renders and thereby spamming GA with redundant location permission updates * set distance filter to 1m instead of 100m --- iosApp/iosApp.xcodeproj/project.pbxproj | 16 +++++ iosApp/iosApp/Analytics/MapAnalytics.swift | 27 +++++++++ .../Analytics/NearbyTransitAnalytics.swift | 10 ---- iosApp/iosApp/Analytics/SearchAnalytics.swift | 19 ++++++ .../iosApp/Analytics/SessionAnalytics.swift | 59 +++++++++++++++++++ .../Analytics/StopDetailsAnalytics.swift | 18 ++---- .../Analytics/StopTripDetailsAnalytics.swift | 45 ++++++++++++++ iosApp/iosApp/ContentView.swift | 17 ++++++ iosApp/iosApp/LocationDataManager.swift | 12 +++- .../LegacyStopDetailsView.swift | 2 +- .../StopDetailsFilteredRouteView.swift | 4 +- iosApp/iosApp/Pages/Map/HomeMapView.swift | 4 +- .../Map/HomeMapViewHandlerExtension.swift | 2 + .../StopDetailsFilteredDepartureDetails.swift | 2 +- .../StopDetails/StopDetailsFilteredView.swift | 2 +- .../Pages/StopDetails/StopDetailsPage.swift | 1 - .../StopDetailsUnfilteredView.swift | 2 +- .../Pages/StopDetails/StopDetailsView.swift | 1 - iosApp/iosApp/ProductionAppView.swift | 2 +- .../iosApp/ViewModels/SearchViewModel.swift | 7 ++- .../Mocks/MockLocationFetcher.swift | 3 + .../Pages/Map/HomeMapViewTest.swift | 1 + .../iosAppTests/Views/HomeMapViewTests.swift | 3 + 23 files changed, 221 insertions(+), 38 deletions(-) create mode 100644 iosApp/iosApp/Analytics/MapAnalytics.swift create mode 100644 iosApp/iosApp/Analytics/SearchAnalytics.swift create mode 100644 iosApp/iosApp/Analytics/SessionAnalytics.swift create mode 100644 iosApp/iosApp/Analytics/StopTripDetailsAnalytics.swift diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 0f97e4b9c..2ebc33c0e 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -95,6 +95,10 @@ 8CA606A92CC02FBC0019C448 /* ViewInspectorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA606A82CC02FBC0019C448 /* ViewInspectorExtensions.swift */; }; 8CA7CDA02D359357008EE7D2 /* DestinationRowAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CD9F2D359353008EE7D2 /* DestinationRowAnalytics.swift */; }; 8CA7CDAC2D3722C8008EE7D2 /* CurrentAppVersionRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CDAB2D3722C3008EE7D2 /* CurrentAppVersionRepository.swift */; }; + 8CA7CDA42D35DD98008EE7D2 /* SearchAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CDA32D35DD94008EE7D2 /* SearchAnalytics.swift */; }; + 8CA7CDA62D36C960008EE7D2 /* StopTripDetailsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CDA52D36C95C008EE7D2 /* StopTripDetailsAnalytics.swift */; }; + 8CA7CDA82D36CB14008EE7D2 /* MapAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CDA72D36CB11008EE7D2 /* MapAnalytics.swift */; }; + 8CA7CDAA2D36E214008EE7D2 /* SessionAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CDA92D36E20F008EE7D2 /* SessionAnalytics.swift */; }; 8CB28DB92C2CC5AD0036258E /* MapViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB28DB82C2CC5AD0036258E /* MapViewModel.swift */; }; 8CB823D62BC5E85C002C87E0 /* SheetNavigationStackEntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB823D52BC5E85C002C87E0 /* SheetNavigationStackEntryTests.swift */; }; 8CB823D92BC5EDD2002C87E0 /* StopDetailsRouteViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB823D82BC5EDD2002C87E0 /* StopDetailsRouteViewTests.swift */; }; @@ -400,6 +404,10 @@ 8CA606A82CC02FBC0019C448 /* ViewInspectorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewInspectorExtensions.swift; sourceTree = ""; }; 8CA7CD9F2D359353008EE7D2 /* DestinationRowAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationRowAnalytics.swift; sourceTree = ""; }; 8CA7CDAB2D3722C3008EE7D2 /* CurrentAppVersionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentAppVersionRepository.swift; sourceTree = ""; }; + 8CA7CDA32D35DD94008EE7D2 /* SearchAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchAnalytics.swift; sourceTree = ""; }; + 8CA7CDA52D36C95C008EE7D2 /* StopTripDetailsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopTripDetailsAnalytics.swift; sourceTree = ""; }; + 8CA7CDA72D36CB11008EE7D2 /* MapAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapAnalytics.swift; sourceTree = ""; }; + 8CA7CDA92D36E20F008EE7D2 /* SessionAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionAnalytics.swift; sourceTree = ""; }; 8CB28DB82C2CC5AD0036258E /* MapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewModel.swift; sourceTree = ""; }; 8CB823D52BC5E85C002C87E0 /* SheetNavigationStackEntryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetNavigationStackEntryTests.swift; sourceTree = ""; }; 8CB823D82BC5EDD2002C87E0 /* StopDetailsRouteViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopDetailsRouteViewTests.swift; sourceTree = ""; }; @@ -1168,9 +1176,13 @@ 9A52B3392C6E7C7D0028EEAB /* AlertDetailsAnalytics.swift */, ED24EADD2C1A986900A7BE4D /* AnalyticsProvider.swift */, 8CA7CD9F2D359353008EE7D2 /* DestinationRowAnalytics.swift */, + 8CA7CDA72D36CB11008EE7D2 /* MapAnalytics.swift */, ED24EAD72C1A941E00A7BE4D /* NearbyTransitAnalytics.swift */, EDE92FA52C3DD675007AD2F6 /* ScreenTracker.swift */, + 8CA7CDA32D35DD94008EE7D2 /* SearchAnalytics.swift */, + 8CA7CDA92D36E20F008EE7D2 /* SessionAnalytics.swift */, ED24EADB2C1A95A900A7BE4D /* StopDetailsAnalytics.swift */, + 8CA7CDA52D36C95C008EE7D2 /* StopTripDetailsAnalytics.swift */, 8C2752EE2C6D5CA900F0B0A5 /* TripDetailsAnalytics.swift */, ); path = Analytics; @@ -1553,7 +1565,9 @@ 9AE98DAB2CF4CCAE00EE80AA /* StopDetailsViewModel.swift in Sources */, 8C2752EF2C6D5CA900F0B0A5 /* TripDetailsAnalytics.swift in Sources */, 9A52A2B32CC3035F00CC01D6 /* WithRealtimeIndicator.swift in Sources */, + 8CA7CDA82D36CB14008EE7D2 /* MapAnalytics.swift in Sources */, 9A5B275C2BB237DE009A6FC6 /* RecenterButton.swift in Sources */, + 8CA7CDA62D36C960008EE7D2 /* StopTripDetailsAnalytics.swift in Sources */, 6E99CBB72B9892C80047E78D /* SocketProvider.swift in Sources */, ED5C93F42C496FB90086D017 /* TripDetailsHeader.swift in Sources */, 8C1587042C76524600AB5036 /* AppVariantExtension.swift in Sources */, @@ -1578,6 +1592,7 @@ 6EE76D1B2BF532010051D608 /* RouteIcon.swift in Sources */, ED24EADE2C1A986900A7BE4D /* AnalyticsProvider.swift in Sources */, 6EE7457E2B965ADE0052227E /* Socket.swift in Sources */, + 8CA7CDAA2D36E214008EE7D2 /* SessionAnalytics.swift in Sources */, 9A5B27582BB22BF9009A6FC6 /* MapLayerManager.swift in Sources */, 9A03F3662BA9E68500DA40DC /* Debouncer.swift in Sources */, 8CE014102BBDB8DC00918FAE /* StopDetailsRoutesView.swift in Sources */, @@ -1699,6 +1714,7 @@ 9A18DEAD2D07DDC800DA0A3B /* RouteExtension.swift in Sources */, 6E973DA52C17384C00CBF341 /* SheetHeader.swift in Sources */, 9A9E7DD32C2203BE000DA1FD /* TransitCard.swift in Sources */, + 8CA7CDA42D35DD98008EE7D2 /* SearchAnalytics.swift in Sources */, 9AF29DFA2CF548E5005AA4A3 /* StopDetailsUnfilteredView.swift in Sources */, 8C6A48402BC09A2E0032A554 /* StopDetailsFilteredRouteView.swift in Sources */, 9A6ACA2D2CD00F8200299AF5 /* MoreItem.swift in Sources */, diff --git a/iosApp/iosApp/Analytics/MapAnalytics.swift b/iosApp/iosApp/Analytics/MapAnalytics.swift new file mode 100644 index 000000000..d62aeffd0 --- /dev/null +++ b/iosApp/iosApp/Analytics/MapAnalytics.swift @@ -0,0 +1,27 @@ +// +// MapAnalytics.swift +// iosApp +// +// Created by Horn, Melody on 2025-01-14. +// Copyright © 2025 MBTA. All rights reserved. +// + +protocol MapAnalytics { + func tappedOnStop(stopId: String) + func tappedVehicle(routeId: String) +} + +extension AnalyticsProvider: MapAnalytics { + func tappedOnStop(stopId: String) { + logEvent( + "tapped_on_stop", + parameters: [ + "stop_id": stopId, + ] + ) + } + + func tappedVehicle(routeId: String) { + logEvent("tapped_vehicle", parameters: ["route_id": routeId]) + } +} diff --git a/iosApp/iosApp/Analytics/NearbyTransitAnalytics.swift b/iosApp/iosApp/Analytics/NearbyTransitAnalytics.swift index 3ff40effa..a59e011df 100644 --- a/iosApp/iosApp/Analytics/NearbyTransitAnalytics.swift +++ b/iosApp/iosApp/Analytics/NearbyTransitAnalytics.swift @@ -12,7 +12,6 @@ import Foundation protocol NearbyTransitAnalytics: DestinationRowAnalytics { func toggledPinnedRoute(pinned: Bool, routeId: String) func refetchedNearbyTransit() - func tappedOnStop(stopId: String) } extension AnalyticsProvider: NearbyTransitAnalytics { @@ -28,13 +27,4 @@ extension AnalyticsProvider: NearbyTransitAnalytics { func refetchedNearbyTransit() { logEvent("refetched_nearby_transit") } - - func tappedOnStop(stopId: String) { - logEvent( - "tapped_on_stop", - parameters: [ - "stop_id": stopId, - ] - ) - } } diff --git a/iosApp/iosApp/Analytics/SearchAnalytics.swift b/iosApp/iosApp/Analytics/SearchAnalytics.swift new file mode 100644 index 000000000..0c87a147c --- /dev/null +++ b/iosApp/iosApp/Analytics/SearchAnalytics.swift @@ -0,0 +1,19 @@ +// +// SearchAnalytics.swift +// iosApp +// +// Created by Horn, Melody on 2025-01-13. +// Copyright © 2025 MBTA. All rights reserved. +// + +import FirebaseAnalytics + +protocol SearchAnalytics { + func performedSearch(query: String) +} + +extension AnalyticsProvider: SearchAnalytics { + func performedSearch(query: String) { + logEvent("search", parameters: ["query": query]) + } +} diff --git a/iosApp/iosApp/Analytics/SessionAnalytics.swift b/iosApp/iosApp/Analytics/SessionAnalytics.swift new file mode 100644 index 000000000..fc7796433 --- /dev/null +++ b/iosApp/iosApp/Analytics/SessionAnalytics.swift @@ -0,0 +1,59 @@ +// +// SessionAnalytics.swift +// iosApp +// +// Created by Horn, Melody on 2025-01-14. +// Copyright © 2025 MBTA. All rights reserved. +// + +import CoreLocation +import FirebaseAnalytics +import SwiftUI + +protocol SessionAnalytics { + func recordSession(colorScheme: ColorScheme) + func recordSession(voiceOver: Bool) + func recordSession(hideMaps: Bool) + func recordSession(locationAccess: CLAuthorizationStatus, locationAccuracy: CLAccuracyAuthorization) +} + +extension AnalyticsProvider: SessionAnalytics { + func recordSession(colorScheme: ColorScheme) { + let colorScheme = switch colorScheme { + case .light: "light" + case .dark: "dark" + @unknown default: "unknown" + } + Analytics.setUserProperty(colorScheme, forName: "color_scheme") + } + + func recordSession(voiceOver: Bool) { + let voiceOver = switch voiceOver { + case true: "true" + case false: "false" + } + Analytics.setUserProperty(voiceOver, forName: "screen_reader_on") + } + + func recordSession(hideMaps: Bool) { + let hideMaps = switch hideMaps { + case true: "true" + case false: "false" + } + Analytics.setUserProperty(hideMaps, forName: "hide_maps_on") + } + + func recordSession(locationAccess: CLAuthorizationStatus, locationAccuracy: CLAccuracyAuthorization) { + let locationAllowed = switch locationAccess { + case .authorizedAlways, .authorizedWhenInUse: true + case .notDetermined, .denied, .restricted: false + @unknown default: false + } + let locationAccess = switch (locationAllowed, locationAccuracy) { + case (true, .fullAccuracy): "precise" + case (true, _): "approximate" + case (false, _): "off" + } + Analytics.setUserProperty(locationAccess, forName: "location_access") + } +} diff --git a/iosApp/iosApp/Analytics/StopDetailsAnalytics.swift b/iosApp/iosApp/Analytics/StopDetailsAnalytics.swift index 5f2d9de5e..0ae50d576 100644 --- a/iosApp/iosApp/Analytics/StopDetailsAnalytics.swift +++ b/iosApp/iosApp/Analytics/StopDetailsAnalytics.swift @@ -10,13 +10,12 @@ import FirebaseAnalytics import Foundation protocol StopDetailsAnalytics: DestinationRowAnalytics { - func tappedAlertDetails(routeId: String, stopId: String, alertId: String) - func tappedRouteFilter(routeId: String, stopId: String) - func toggledPinnedRouteAtStop(pinned: Bool, routeId: String) + func tappedAlertDetailsLegacy(routeId: String, stopId: String, alertId: String) + func tappedRouteFilterLegacy(routeId: String, stopId: String) } extension AnalyticsProvider: StopDetailsAnalytics { - func tappedAlertDetails(routeId: String, stopId: String, alertId: String) { + func tappedAlertDetailsLegacy(routeId: String, stopId: String, alertId: String) { logEvent( "tapped_alert_details", parameters: [ @@ -27,7 +26,7 @@ extension AnalyticsProvider: StopDetailsAnalytics { ) } - func tappedRouteFilter(routeId: String, stopId: String) { + func tappedRouteFilterLegacy(routeId: String, stopId: String) { logEvent( "tapped_route_filter", parameters: [ @@ -36,13 +35,4 @@ extension AnalyticsProvider: StopDetailsAnalytics { ] ) } - - func toggledPinnedRouteAtStop(pinned: Bool, routeId: String) { - logEvent( - pinned ? "pin_route" : "unpin_route", - parameters: [ - "route_id": routeId, - ] - ) - } } diff --git a/iosApp/iosApp/Analytics/StopTripDetailsAnalytics.swift b/iosApp/iosApp/Analytics/StopTripDetailsAnalytics.swift new file mode 100644 index 000000000..2bead70ea --- /dev/null +++ b/iosApp/iosApp/Analytics/StopTripDetailsAnalytics.swift @@ -0,0 +1,45 @@ +// +// StopTripDetailsAnalytics.swift +// iosApp +// +// Created by Horn, Melody on 2025-01-14. +// Copyright © 2025 MBTA. All rights reserved. +// + +protocol StopTripDetailsAnalytics: DestinationRowAnalytics { + func tappedAlertDetails(routeId: String, stopId: String, alertId: String) + func tappedRouteFilter(routeId: String, stopId: String) + func toggledPinnedRouteAtStop(pinned: Bool, routeId: String) +} + +extension AnalyticsProvider: StopTripDetailsAnalytics { + func tappedAlertDetails(routeId: String, stopId: String, alertId: String) { + logEvent( + "tapped_alert_details", + parameters: [ + "route_id": routeId, + "stop_id": stopId, + "alertId": alertId, + ] + ) + } + + func tappedRouteFilter(routeId: String, stopId: String) { + logEvent( + "tapped_route_filter", + parameters: [ + "route_id": routeId, + "stop_id": stopId, + ] + ) + } + + func toggledPinnedRouteAtStop(pinned: Bool, routeId: String) { + logEvent( + pinned ? "pin_route" : "unpin_route", + parameters: [ + "route_id": routeId, + ] + ) + } +} diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 4b59370b3..aea3d5f18 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -4,8 +4,11 @@ import shared import SwiftPhoenixClient import SwiftUI +// swiftlint:disable:next type_body_length struct ContentView: View { @Environment(\.scenePhase) private var scenePhase + @Environment(\.colorScheme) var colorScheme + @Environment(\.accessibilityVoiceOverEnabled) var voiceOver let platform = Platform_iosKt.getPlatform().name @StateObject var searchObserver = TextFieldObserver() @@ -25,6 +28,7 @@ struct ContentView: View { let transition: AnyTransition = .asymmetric(insertion: .push(from: .bottom), removal: .opacity) var screenTracker: ScreenTracker = AnalyticsProvider.shared + let analytics: SessionAnalytics = AnalyticsProvider.shared let inspection = Inspection() @@ -54,6 +58,9 @@ struct ContentView: View { Task { await contentVM.loadFeaturePromos() } Task { await contentVM.loadOnboardingScreens() } Task { await nearbyVM.loadDebugSetting() } + analytics.recordSession(colorScheme: colorScheme) + analytics.recordSession(voiceOver: voiceOver) + analytics.recordSession(hideMaps: contentVM.hideMaps) } .task { // We can't set stale caches in ResponseCache on init because of our Koin setup, @@ -74,6 +81,15 @@ struct ContentView: View { socketProvider.socket.detach() } } + .onChange(of: colorScheme) { _ in + analytics.recordSession(colorScheme: colorScheme) + } + .onChange(of: voiceOver) { _ in + analytics.recordSession(voiceOver: voiceOver) + } + .onChange(of: contentVM.hideMaps) { _ in + analytics.recordSession(hideMaps: contentVM.hideMaps) + } .onChange(of: contentVM.configResponse) { response in switch onEnum(of: response) { case let .ok(response): contentVM.configureMapboxToken(token: response.data.mapboxPublicToken) @@ -200,6 +216,7 @@ struct ContentView: View { mapVM: mapVM, nearbyVM: nearbyVM, viewportProvider: viewportProvider, + locationDataManager: locationDataManager, sheetHeight: $sheetHeight ) } diff --git a/iosApp/iosApp/LocationDataManager.swift b/iosApp/iosApp/LocationDataManager.swift index 5a4b440b7..323f99641 100644 --- a/iosApp/iosApp/LocationDataManager.swift +++ b/iosApp/iosApp/LocationDataManager.swift @@ -17,23 +17,30 @@ public class LocationDataManager: NSObject, LocationFetcherDelegate, ObservableO let subscribeToLocations: Bool @Published public var currentLocation: CLLocation? @Published public var authorizationStatus: CLAuthorizationStatus? + let analytics: SessionAnalytics - public init( + init( locationFetcher: LocationFetcher = CLLocationManager(), settingsRepository: ISettingsRepository = RepositoryDI().settings, subscribeToLocations: Bool = true, - distanceFilter: Double = kCLDistanceFilterNone + distanceFilter: Double = kCLDistanceFilterNone, + analytics: SessionAnalytics = AnalyticsProvider.shared ) { self.locationFetcher = locationFetcher self.locationFetcher.distanceFilter = distanceFilter self.settingsRepository = settingsRepository self.subscribeToLocations = subscribeToLocations + self.analytics = analytics super.init() self.locationFetcher.locationFetcherDelegate = self } public func locationFetcherDidChangeAuthorization(_ fetcher: LocationFetcher) { authorizationStatus = fetcher.authorizationStatus + analytics.recordSession( + locationAccess: fetcher.authorizationStatus, + locationAccuracy: fetcher.accuracyAuthorization + ) if subscribeToLocations, fetcher.authorizationStatus == .authorizedWhenInUse || fetcher.authorizationStatus == .authorizedAlways { fetcher.startUpdatingLocation() @@ -63,6 +70,7 @@ extension LocationDataManager: CLLocationManagerDelegate { public protocol LocationFetcher: AnyObject { var locationFetcherDelegate: LocationFetcherDelegate? { get set } var authorizationStatus: CLAuthorizationStatus { get } + var accuracyAuthorization: CLAccuracyAuthorization { get } var distanceFilter: CLLocationDistance { get set } func startUpdatingLocation() func requestWhenInUseAuthorization() diff --git a/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsView.swift b/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsView.swift index 14b9c578b..8f1e2eb52 100644 --- a/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsView.swift +++ b/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsView.swift @@ -154,7 +154,7 @@ struct LegacyStopDetailsView: View { guard let departures else { return } guard let patterns = departures.routes.first(where: { patterns in patterns.routeIdentifier == filterId }) else { return } - analytics.tappedRouteFilter(routeId: patterns.routeIdentifier, stopId: stop.id) + analytics.tappedRouteFilterLegacy(routeId: patterns.routeIdentifier, stopId: stop.id) let defaultDirectionId = patterns.patterns.flatMap { headsign in // RealtimePatterns.patterns is a List but that gets bridged as [Any] for some reason headsign.patterns.compactMap { pattern in (pattern as? RoutePattern)?.directionId } diff --git a/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsFilteredRouteView.swift b/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsFilteredRouteView.swift index ee371bc8b..aa3191d59 100644 --- a/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsFilteredRouteView.swift +++ b/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsFilteredRouteView.swift @@ -179,7 +179,7 @@ struct StopDetailsFilteredRouteView: View { line: patternsByStop.line, routes: patternsByStop.routes )) - analytics.tappedAlertDetails( + analytics.tappedAlertDetailsLegacy( routeId: patternsByStop.routeIdentifier, stopId: patternsByStop.stop.id, alertId: alert.id @@ -199,7 +199,7 @@ struct StopDetailsFilteredRouteView: View { line: patternsByStop.line, routes: patternsByStop.routes )) - analytics.tappedAlertDetails( + analytics.tappedAlertDetailsLegacy( routeId: patternsByStop.routeIdentifier, stopId: patternsByStop.stop.id, alertId: alert.id diff --git a/iosApp/iosApp/Pages/Map/HomeMapView.swift b/iosApp/iosApp/Pages/Map/HomeMapView.swift index 88b0255fc..442c3ec26 100644 --- a/iosApp/iosApp/Pages/Map/HomeMapView.swift +++ b/iosApp/iosApp/Pages/Map/HomeMapView.swift @@ -13,7 +13,7 @@ import shared import SwiftUI struct HomeMapView: View { - var analytics: NearbyTransitAnalytics = AnalyticsProvider.shared + var analytics: MapAnalytics = AnalyticsProvider.shared @ObservedObject var contentVM: ContentViewModel @ObservedObject var mapVM: MapViewModel @ObservedObject var nearbyVM: NearbyViewModel @@ -62,7 +62,7 @@ struct HomeMapView: View { stopRepository: IStopRepository = RepositoryDI().stop, vehiclesData: [Vehicle]? = nil, vehiclesRepository: IVehiclesRepository = RepositoryDI().vehicles, - locationDataManager: LocationDataManager = .init(distanceFilter: 1), + locationDataManager: LocationDataManager, sheetHeight: Binding, globalMapData: GlobalMapData? = nil ) { diff --git a/iosApp/iosApp/Pages/Map/HomeMapViewHandlerExtension.swift b/iosApp/iosApp/Pages/Map/HomeMapViewHandlerExtension.swift index 972d4bc94..28ae2a041 100644 --- a/iosApp/iosApp/Pages/Map/HomeMapViewHandlerExtension.swift +++ b/iosApp/iosApp/Pages/Map/HomeMapViewHandlerExtension.swift @@ -297,6 +297,8 @@ extension HomeMapView { return } + analytics.tappedVehicle(routeId: routeId) + // If we're missing the stop ID or stop sequence, we can still navigate to the trip details // page, but we won't be able to tell what the target stop was. nearbyVM.pushNavEntry(.tripDetails( diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredDepartureDetails.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredDepartureDetails.swift index 1286a570d..41c5c39da 100644 --- a/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredDepartureDetails.swift +++ b/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredDepartureDetails.swift @@ -31,7 +31,7 @@ struct StopDetailsFilteredDepartureDetails: View { @EnvironmentObject var viewportProvider: ViewportProvider - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared + var analytics: StopTripDetailsAnalytics = AnalyticsProvider.shared var showTileHeadsigns: Bool { patternsByStop.line != nil || !tiles.allSatisfy { tile in diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift index 8b606f239..fe7c66f6d 100644 --- a/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift +++ b/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredView.swift @@ -29,7 +29,7 @@ struct StopDetailsFilteredView: View { @ObservedObject var mapVM: MapViewModel @ObservedObject var stopDetailsVM: StopDetailsViewModel - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared + var analytics: StopTripDetailsAnalytics = AnalyticsProvider.shared var tiles: [TileData] = [] var noPredictionsStatus: RealtimePatterns.NoTripsFormat? diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift index be4a05e16..0750f2aa8 100644 --- a/iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift +++ b/iosApp/iosApp/Pages/StopDetails/StopDetailsPage.swift @@ -25,7 +25,6 @@ struct StopDetailsPage: View { @ObservedObject var stopDetailsVM: StopDetailsViewModel @ObservedObject var viewportProvider: ViewportProvider - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared let inspection = Inspection() var stopId: String { filters.stopId } diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsUnfilteredView.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsUnfilteredView.swift index 0df341c91..638630557 100644 --- a/iosApp/iosApp/Pages/StopDetails/StopDetailsUnfilteredView.swift +++ b/iosApp/iosApp/Pages/StopDetails/StopDetailsUnfilteredView.swift @@ -23,7 +23,7 @@ struct StopDetailsUnfilteredView: View { @ObservedObject var nearbyVM: NearbyViewModel @ObservedObject var stopDetailsVM: StopDetailsViewModel - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared + var analytics: StopTripDetailsAnalytics = AnalyticsProvider.shared let inspection = Inspection() init( diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsView.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsView.swift index 5a1f8ffe4..94b90e8e3 100644 --- a/iosApp/iosApp/Pages/StopDetails/StopDetailsView.swift +++ b/iosApp/iosApp/Pages/StopDetails/StopDetailsView.swift @@ -29,7 +29,6 @@ struct StopDetailsView: View { @ObservedObject var mapVM: MapViewModel @ObservedObject var stopDetailsVM: StopDetailsViewModel - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared let inspection = Inspection() init( diff --git a/iosApp/iosApp/ProductionAppView.swift b/iosApp/iosApp/ProductionAppView.swift index 74d4f8a72..37397d929 100644 --- a/iosApp/iosApp/ProductionAppView.swift +++ b/iosApp/iosApp/ProductionAppView.swift @@ -41,7 +41,7 @@ struct ProductionAppView: View { } init(socket: PhoenixSocket) { - _locationDataManager = StateObject(wrappedValue: LocationDataManager(distanceFilter: 100)) + _locationDataManager = StateObject(wrappedValue: LocationDataManager(distanceFilter: 1)) _socketProvider = StateObject(wrappedValue: SocketProvider(socket: socket)) _viewportProvider = StateObject(wrappedValue: ViewportProvider()) } diff --git a/iosApp/iosApp/ViewModels/SearchViewModel.swift b/iosApp/iosApp/ViewModels/SearchViewModel.swift index 3c0ba63ff..721034692 100644 --- a/iosApp/iosApp/ViewModels/SearchViewModel.swift +++ b/iosApp/iosApp/ViewModels/SearchViewModel.swift @@ -32,6 +32,8 @@ class SearchViewModel: ObservableObject { private let searchResultsRepository: ISearchResultRepository private let globalRepository: IGlobalRepository + private let analytics: SearchAnalytics + private var routeResultsEnabled: Bool private var globalResponse: GlobalResponse? private var latestVisits: [Result]? @@ -42,13 +44,15 @@ class SearchViewModel: ObservableObject { settingsRepo: ISettingsRepository = RepositoryDI().settings, globalRepository: IGlobalRepository = RepositoryDI().global, visitHistoryUsecase: VisitHistoryUsecase = UsecaseDI().visitHistoryUsecase, - searchResultsRepository: ISearchResultRepository = RepositoryDI().searchResults + searchResultsRepository: ISearchResultRepository = RepositoryDI().searchResults, + analytics: SearchAnalytics = AnalyticsProvider.shared ) { self.routeResultsEnabled = routeResultsEnabled self.settingsRepo = settingsRepo self.globalRepository = globalRepository self.visitHistoryUsecase = visitHistoryUsecase self.searchResultsRepository = searchResultsRepository + self.analytics = analytics } func getStopFor(id: String) -> Stop? { @@ -56,6 +60,7 @@ class SearchViewModel: ObservableObject { } func determineStateFor(query: String) { + analytics.performedSearch(query: query) if query.isEmpty { fetchResultsTask?.cancel() resultsState = nil diff --git a/iosApp/iosAppTests/Mocks/MockLocationFetcher.swift b/iosApp/iosAppTests/Mocks/MockLocationFetcher.swift index fa4b132e8..140501233 100644 --- a/iosApp/iosAppTests/Mocks/MockLocationFetcher.swift +++ b/iosApp/iosAppTests/Mocks/MockLocationFetcher.swift @@ -20,6 +20,8 @@ class MockLocationFetcher: LocationFetcher { } } + var accuracyAuthorization: CLAccuracyAuthorization = .fullAccuracy + var handleStartUpdatingLocation: (() -> Void)? func startUpdatingLocation() { handleStartUpdatingLocation?() @@ -47,6 +49,7 @@ class MockOnboardingLocationFetcher: LocationFetcher { } var authorizationStatus: CLAuthorizationStatus = .notDetermined + var accuracyAuthorization: CLAccuracyAuthorization = .fullAccuracy var distanceFilter: CLLocationDistance = 0 func startUpdatingLocation() {} diff --git a/iosApp/iosAppTests/Pages/Map/HomeMapViewTest.swift b/iosApp/iosAppTests/Pages/Map/HomeMapViewTest.swift index f6da46eb2..f9b7ae65e 100644 --- a/iosApp/iosAppTests/Pages/Map/HomeMapViewTest.swift +++ b/iosApp/iosAppTests/Pages/Map/HomeMapViewTest.swift @@ -28,6 +28,7 @@ final class HomeMapViewTest: XCTestCase { mapVM: .init(), nearbyVM: .init(), viewportProvider: viewportProvider, + locationDataManager: .init(), sheetHeight: sheetHeight ) diff --git a/iosApp/iosAppTests/Views/HomeMapViewTests.swift b/iosApp/iosAppTests/Views/HomeMapViewTests.swift index ba7a426b9..b1cab7098 100644 --- a/iosApp/iosAppTests/Views/HomeMapViewTests.swift +++ b/iosApp/iosAppTests/Views/HomeMapViewTests.swift @@ -110,6 +110,7 @@ final class HomeMapViewTests: XCTestCase { nearbyVM: .init(), viewportProvider: ViewportProvider(), railRouteShapeRepository: railRouteShapeRepository, + locationDataManager: .init(), sheetHeight: .constant(0) ) let hasAppeared = sut.on(\.didAppear) { _ in } @@ -1053,6 +1054,8 @@ final class HomeMapViewTests: XCTestCase { var authorizationStatus: CLAuthorizationStatus = .notDetermined + var accuracyAuthorization: CLAccuracyAuthorization = .fullAccuracy + var distanceFilter: CLLocationDistance = .zero func startUpdatingLocation() {}