diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 2a943e49c..30e20ce84 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -72,7 +72,6 @@ 8C1587042C76524600AB5036 /* AppVariantExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1587032C76524600AB5036 /* AppVariantExtension.swift */; }; 8C2106F52CA6159600228287 /* ErrorBannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2106F42CA6159600228287 /* ErrorBannerTests.swift */; }; 8C255B992D388299003C4E3F /* AlertCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C255B982D388299003C4E3F /* AlertCard.swift */; }; - 8C2752EF2C6D5CA900F0B0A5 /* TripDetailsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2752EE2C6D5CA900F0B0A5 /* TripDetailsAnalytics.swift */; }; 8C2F26962CDD709700386D4F /* NoNearbyStopsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2F26952CDD709700386D4F /* NoNearbyStopsView.swift */; }; 8C2F26982CDE7B3600386D4F /* NoNearbyStopsViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2F26972CDE7B3600386D4F /* NoNearbyStopsViewTests.swift */; }; 8C3D64102BE19DBA0026DD53 /* MorePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C3D640F2BE19DBA0026DD53 /* MorePage.swift */; }; @@ -95,12 +94,8 @@ 8CA1FB772BF813F500384658 /* TripDetailsStopListSplitViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA1FB762BF813F500384658 /* TripDetailsStopListSplitViewTests.swift */; }; 8CA485B82BDC679A00E84E1F /* VehicleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA485B72BDC679A00E84E1F /* VehicleExtension.swift */; }; 8CA606A92CC02FBC0019C448 /* ViewInspectorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA606A82CC02FBC0019C448 /* ViewInspectorExtensions.swift */; }; - 8CA7CDA02D359357008EE7D2 /* DestinationRowAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CD9F2D359353008EE7D2 /* DestinationRowAnalytics.swift */; }; + 8CA7CDAA2D36E214008EE7D2 /* AnalyticsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CA7CDA92D36E20F008EE7D2 /* AnalyticsExtensions.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 */; }; @@ -158,7 +153,6 @@ 9A52A2B32CC3035F00CC01D6 /* WithRealtimeIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A52A2B22CC3035F00CC01D6 /* WithRealtimeIndicator.swift */; }; 9A52B3362C6A93390028EEAB /* AlertDetailsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A52B3352C6A93390028EEAB /* AlertDetailsTests.swift */; }; 9A52B3382C6A99790028EEAB /* AlertDetailsPageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A52B3372C6A99790028EEAB /* AlertDetailsPageTests.swift */; }; - 9A52B33A2C6E7C7D0028EEAB /* AlertDetailsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A52B3392C6E7C7D0028EEAB /* AlertDetailsAnalytics.swift */; }; 9A56407B2C6B83F2004761C0 /* PredictionTextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A56407A2C6B83F1004761C0 /* PredictionTextTests.swift */; }; 9A5830562BA3A2CE0039876E /* ViewportExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5830552BA3A2CE0039876E /* ViewportExtension.swift */; }; 9A5830582BA4A1A30039876E /* ViewportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5830572BA4A1A30039876E /* ViewportProvider.swift */; }; @@ -240,8 +234,6 @@ 9AF88E052B48913C00E08C7C /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9AF88E042B48913C00E08C7C /* Localizable.xcstrings */; }; A430D45FE0676C73075AB85B /* Pods_iosAppTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED1649D982654BA7A4D2F2DC /* Pods_iosAppTests.framework */; }; A55C5596CDC797ED68F79279 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C6BD892027AC258EE8F408D /* Pods_iosApp.framework */; }; - ED24EAD82C1A941E00A7BE4D /* NearbyTransitAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED24EAD72C1A941E00A7BE4D /* NearbyTransitAnalytics.swift */; }; - ED24EADC2C1A95A900A7BE4D /* StopDetailsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED24EADB2C1A95A900A7BE4D /* StopDetailsAnalytics.swift */; }; ED24EADE2C1A986900A7BE4D /* AnalyticsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED24EADD2C1A986900A7BE4D /* AnalyticsProvider.swift */; }; ED3581682BB470C4005DDC34 /* PresentationDetentExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED3581672BB470C4005DDC34 /* PresentationDetentExtension.swift */; }; ED5C93F42C496FB90086D017 /* TripDetailsHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5C93F32C496FB90086D017 /* TripDetailsHeader.swift */; }; @@ -258,7 +250,6 @@ EDC768C72BCF6FD6008BAE58 /* RouteCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDC768C62BCF6FD6008BAE58 /* RouteCard.swift */; }; EDDE72EF2CBF19EF0000180D /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDDE72EE2CBF19EF0000180D /* ArrayExtension.swift */; }; EDE692382CD038990081A4D2 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EDE4D3B32CD0255F00D3F517 /* Launch Screen.storyboard */; }; - EDE92FA62C3DD675007AD2F6 /* ScreenTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92FA52C3DD675007AD2F6 /* ScreenTracker.swift */; }; EDE92FA82C408A32007AD2F6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92FA72C408A32007AD2F6 /* SettingsViewModel.swift */; }; EDE92FAA2C408A50007AD2F6 /* MoreSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE92FA92C408A50007AD2F6 /* MoreSection.swift */; }; /* End PBXBuildFile section */ @@ -380,7 +371,6 @@ 8C1587032C76524600AB5036 /* AppVariantExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVariantExtension.swift; sourceTree = ""; }; 8C2106F42CA6159600228287 /* ErrorBannerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorBannerTests.swift; sourceTree = ""; }; 8C255B982D388299003C4E3F /* AlertCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertCard.swift; sourceTree = ""; }; - 8C2752EE2C6D5CA900F0B0A5 /* TripDetailsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripDetailsAnalytics.swift; sourceTree = ""; }; 8C2F26952CDD709700386D4F /* NoNearbyStopsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoNearbyStopsView.swift; sourceTree = ""; }; 8C2F26972CDE7B3600386D4F /* NoNearbyStopsViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoNearbyStopsViewTests.swift; sourceTree = ""; }; 8C304C652B69C0C300263886 /* .swift-version */ = {isa = PBXFileReference; lastKnownFileType = text; path = ".swift-version"; sourceTree = ""; }; @@ -406,12 +396,8 @@ 8CA1FB762BF813F500384658 /* TripDetailsStopListSplitViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripDetailsStopListSplitViewTests.swift; sourceTree = ""; }; 8CA485B72BDC679A00E84E1F /* VehicleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleExtension.swift; sourceTree = ""; }; 8CA606A82CC02FBC0019C448 /* ViewInspectorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewInspectorExtensions.swift; sourceTree = ""; }; - 8CA7CD9F2D359353008EE7D2 /* DestinationRowAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestinationRowAnalytics.swift; sourceTree = ""; }; + 8CA7CDA92D36E20F008EE7D2 /* AnalyticsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsExtensions.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 = ""; }; @@ -469,7 +455,6 @@ 9A52A2B22CC3035F00CC01D6 /* WithRealtimeIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithRealtimeIndicator.swift; sourceTree = ""; }; 9A52B3352C6A93390028EEAB /* AlertDetailsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDetailsTests.swift; sourceTree = ""; }; 9A52B3372C6A99790028EEAB /* AlertDetailsPageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDetailsPageTests.swift; sourceTree = ""; }; - 9A52B3392C6E7C7D0028EEAB /* AlertDetailsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertDetailsAnalytics.swift; sourceTree = ""; }; 9A56407A2C6B83F1004761C0 /* PredictionTextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PredictionTextTests.swift; sourceTree = ""; }; 9A5830552BA3A2CE0039876E /* ViewportExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewportExtension.swift; sourceTree = ""; }; 9A5830572BA4A1A30039876E /* ViewportProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewportProvider.swift; sourceTree = ""; }; @@ -553,8 +538,6 @@ D1B30C8C9F419DC14D8EFE9E /* Pods-iosAppTests.prod debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosAppTests.prod debug.xcconfig"; path = "Target Support Files/Pods-iosAppTests/Pods-iosAppTests.prod debug.xcconfig"; sourceTree = ""; }; D67CE13D30C9CBC403262442 /* Pods-iosApp.staging release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.staging release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.staging release.xcconfig"; sourceTree = ""; }; ED1649D982654BA7A4D2F2DC /* Pods_iosAppTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosAppTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - ED24EAD72C1A941E00A7BE4D /* NearbyTransitAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyTransitAnalytics.swift; sourceTree = ""; }; - ED24EADB2C1A95A900A7BE4D /* StopDetailsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopDetailsAnalytics.swift; sourceTree = ""; }; ED24EADD2C1A986900A7BE4D /* AnalyticsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsProvider.swift; sourceTree = ""; }; ED3581672BB470C4005DDC34 /* PresentationDetentExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationDetentExtension.swift; sourceTree = ""; }; ED5C93F32C496FB90086D017 /* TripDetailsHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripDetailsHeader.swift; sourceTree = ""; }; @@ -571,7 +554,6 @@ EDC768C62BCF6FD6008BAE58 /* RouteCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteCard.swift; sourceTree = ""; }; EDDE72EE2CBF19EF0000180D /* ArrayExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = ""; }; EDE4D3B32CD0255F00D3F517 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; - EDE92FA52C3DD675007AD2F6 /* ScreenTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenTracker.swift; sourceTree = ""; }; EDE92FA72C408A32007AD2F6 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; EDE92FA92C408A50007AD2F6 /* MoreSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreSection.swift; sourceTree = ""; }; F35CF19A11F0511AA7B2E080 /* Pods-iosApp.stagingdebug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.stagingdebug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.stagingdebug.xcconfig"; sourceTree = ""; }; @@ -1179,17 +1161,8 @@ ED24EAD62C1A940700A7BE4D /* Analytics */ = { isa = PBXGroup; children = ( - 9A52B3392C6E7C7D0028EEAB /* AlertDetailsAnalytics.swift */, + 8CA7CDA92D36E20F008EE7D2 /* AnalyticsExtensions.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; sourceTree = ""; @@ -1569,11 +1542,8 @@ 9AF29DF82CF5454E005AA4A3 /* StopDetailsFilteredView.swift in Sources */, 8C7726152BE58FE30088D423 /* TripDetailsPage.swift in Sources */, 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 */, @@ -1599,7 +1569,7 @@ 6EE76D1B2BF532010051D608 /* RouteIcon.swift in Sources */, ED24EADE2C1A986900A7BE4D /* AnalyticsProvider.swift in Sources */, 6EE7457E2B965ADE0052227E /* Socket.swift in Sources */, - 8CA7CDAA2D36E214008EE7D2 /* SessionAnalytics.swift in Sources */, + 8CA7CDAA2D36E214008EE7D2 /* AnalyticsExtensions.swift in Sources */, 9A5B27582BB22BF9009A6FC6 /* MapLayerManager.swift in Sources */, 9A03F3662BA9E68500DA40DC /* Debouncer.swift in Sources */, 8CE014102BBDB8DC00918FAE /* StopDetailsRoutesView.swift in Sources */, @@ -1623,7 +1593,6 @@ 9A9E7DD52C2203E8000DA1FD /* PinButton.swift in Sources */, ED93D6002CB6C077003B1C12 /* ResultContainer.swift in Sources */, 6EA00B6A2C5137A800B1334C /* MockSocket.swift in Sources */, - ED24EADC2C1A95A900A7BE4D /* StopDetailsAnalytics.swift in Sources */, 9A4D0ADB2D00A860009C1054 /* TripStopRow.swift in Sources */, 9A4E8E592B7EC4B90066B936 /* RoutePill.swift in Sources */, 9AD039362CCFC5E8008D9318 /* LocationAuthButton.swift in Sources */, @@ -1637,7 +1606,6 @@ 8C05C5812CD568DE000381E8 /* MoreNavLink.swift in Sources */, 9A7F12172CCFEFAA0042B0F1 /* MoreLink.swift in Sources */, 9A4092E92D0248880026EB01 /* ColoredRouteLine.swift in Sources */, - 9A52B33A2C6E7C7D0028EEAB /* AlertDetailsAnalytics.swift in Sources */, 6E04E32F2BE95A8D006F8131 /* NearbyViewModel.swift in Sources */, 9A88AAC12BD0680C00A5BF88 /* StopDetailsFilterPills.swift in Sources */, 8C0923A72C210C8C00813454 /* Typography.swift in Sources */, @@ -1646,7 +1614,6 @@ 9A7AC5C22BE139FB0036126F /* AnnotatedMap.swift in Sources */, 8C5054582BB5EB6C00C6A51C /* LegacyStopDetailsPage.swift in Sources */, 8C05C57D2CD5659A000381E8 /* MoreNavTarget.swift in Sources */, - ED24EAD82C1A941E00A7BE4D /* NearbyTransitAnalytics.swift in Sources */, 9AA379A32C2A314F00278541 /* DirectionLabel.swift in Sources */, ED3581682BB470C4005DDC34 /* PresentationDetentExtension.swift in Sources */, 9A3B09362B967CEC00691427 /* NonNilModifier.swift in Sources */, @@ -1659,7 +1626,6 @@ 8C815AF32BF5257B009BB51C /* TripDetailsStopListView.swift in Sources */, 9A719E172CD1725E007377F7 /* UIWindowExtension.swift in Sources */, 8C3D64102BE19DBA0026DD53 /* MorePage.swift in Sources */, - EDE92FA62C3DD675007AD2F6 /* ScreenTracker.swift in Sources */, 9A4850572CB56A1600F50EE7 /* RouteTypeExtension.swift in Sources */, 8C97F7D02CCC01E200A06B34 /* OnboardingScreenView.swift in Sources */, EDE92FA82C408A32007AD2F6 /* SettingsViewModel.swift in Sources */, @@ -1672,7 +1638,6 @@ 6E35D4D02B72C7B700A2BF95 /* HomeMapView.swift in Sources */, 9ACE4FD02CE6707900FEB006 /* StopDetailsPage.swift in Sources */, 9A4DB77F2CA4A32800E8755B /* SearchOverlay.swift in Sources */, - 8CA7CDA02D359357008EE7D2 /* DestinationRowAnalytics.swift in Sources */, 8CC1BB402B59D1F6005386FE /* LocationDataManager.swift in Sources */, 9A6ACA2B2CD0096A00299AF5 /* MoreSectionView.swift in Sources */, 6E3C8D7E2C11FDA80059C28C /* ActionButton.swift in Sources */, @@ -1722,7 +1687,6 @@ 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/AlertDetailsAnalytics.swift b/iosApp/iosApp/Analytics/AlertDetailsAnalytics.swift deleted file mode 100644 index 075210438..000000000 --- a/iosApp/iosApp/Analytics/AlertDetailsAnalytics.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// AlertDetailsAnalytics.swift -// iosApp -// -// Created by Simon, Emma on 8/15/24. -// Copyright © 2024 MBTA. All rights reserved. -// - -import FirebaseAnalytics -import Foundation - -protocol AlertDetailsAnalytics { - func tappedAffectedStops(routeId: String, stopId: String, alertId: String) - func tappedTripPlanner(routeId: String, stopId: String, alertId: String) -} - -extension AnalyticsProvider: AlertDetailsAnalytics { - func tappedAffectedStops(routeId: String, stopId: String, alertId: String) { - logEvent( - "tapped_affected_stops", - parameters: [ - "route_id": routeId, - "stop_id": stopId, - "alert_id": alertId, - ] - ) - } - - func tappedTripPlanner(routeId: String, stopId: String, alertId: String) { - logEvent( - "tapped_trip_planner", - parameters: [ - "route_id": routeId, - "stop_id": stopId, - "alert_id": alertId, - ] - ) - } -} diff --git a/iosApp/iosApp/Analytics/AnalyticsExtensions.swift b/iosApp/iosApp/Analytics/AnalyticsExtensions.swift new file mode 100644 index 000000000..ae7bfe4e9 --- /dev/null +++ b/iosApp/iosApp/Analytics/AnalyticsExtensions.swift @@ -0,0 +1,36 @@ +// +// AnalyticsExtensions.swift +// iosApp +// +// Created by Horn, Melody on 2025-01-14. +// Copyright © 2025 MBTA. All rights reserved. +// + +import CoreLocation +import shared +import SwiftUI + +extension Analytics { + func recordSession(colorScheme: ColorScheme) { + let analyticsColorScheme: AnalyticsColorScheme = switch colorScheme { + case .light: .light + case .dark: .dark + @unknown default: .unknown + } + recordSession(colorScheme: analyticsColorScheme) + } + + func recordSession(locationAccess: CLAuthorizationStatus, locationAccuracy: CLAccuracyAuthorization) { + let locationAllowed = switch locationAccess { + case .authorizedAlways, .authorizedWhenInUse: true + case .notDetermined, .denied, .restricted: false + @unknown default: false + } + let locationAccess: AnalyticsLocationAccess = switch (locationAllowed, locationAccuracy) { + case (true, .fullAccuracy): .precise + case (true, _): .approximate + case (false, _): .off + } + recordSession(locationAccess: locationAccess) + } +} diff --git a/iosApp/iosApp/Analytics/AnalyticsProvider.swift b/iosApp/iosApp/Analytics/AnalyticsProvider.swift index 3d7d3fca1..b3df5554c 100644 --- a/iosApp/iosApp/Analytics/AnalyticsProvider.swift +++ b/iosApp/iosApp/Analytics/AnalyticsProvider.swift @@ -8,17 +8,16 @@ import FirebaseAnalytics import Foundation +import shared -class AnalyticsProvider { +class AnalyticsProvider: shared.Analytics { static let shared = AnalyticsProvider() - /** - * The `file` param is automatically populated with the call sites file path which we parse the class name from. - * e.g.: `NearbyTransitAnalytics` - */ - func logEvent(_ name: String, parameters: [String: Any] = [:], file: String = #file) { - var params = parameters - params["class_name"] = URL(string: file)?.deletingPathExtension().lastPathComponent - Analytics.logEvent(name, parameters: params) + override func logEvent(name: String, parameters: [String: String]) { + FirebaseAnalytics.Analytics.logEvent(name, parameters: parameters) + } + + override func setUserProperty(name: String, value: String) { + FirebaseAnalytics.Analytics.setUserProperty(value, forName: name) } } diff --git a/iosApp/iosApp/Analytics/DestinationRowAnalytics.swift b/iosApp/iosApp/Analytics/DestinationRowAnalytics.swift deleted file mode 100644 index 89b2a1ed0..000000000 --- a/iosApp/iosApp/Analytics/DestinationRowAnalytics.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// DestinationRowAnalytics.swift -// iosApp -// -// Created by Horn, Melody on 2025-01-13. -// Copyright © 2025 MBTA. All rights reserved. -// - -import FirebaseAnalytics -import shared - -protocol DestinationRowAnalytics { - func tappedDeparture( - routeId: String, - stopId: String, - pinned: Bool, - alert: Bool, - routeType: RouteType, - noTrips: RealtimePatterns.NoTripsFormat? - ) -} - -extension AnalyticsProvider: DestinationRowAnalytics { - func tappedDeparture( - routeId: String, - stopId: String, - pinned: Bool, - alert: Bool, - routeType: RouteType, - noTrips: RealtimePatterns.NoTripsFormat? - ) { - let mode = switch routeType { - case .bus: "bus" - case .commuterRail: "commuter rail" - case .ferry: "ferry" - case .heavyRail: "subway" - case .lightRail: "subway" - } - let noTrips = switch onEnum(of: noTrips) { - case .noSchedulesToday: "no service today" - case .predictionsUnavailable: "predictions unavailable" - case .serviceEndedToday: "service ended" - case nil: "" - } - logEvent( - "tapped_departure", - parameters: [ - "route_id": routeId, - "stop_id": stopId, - "pinned": pinned ? "true" : "false", - "alert": alert ? "true" : "false", - "mode": mode, - "no_trips": noTrips, - ] - ) - } -} diff --git a/iosApp/iosApp/Analytics/MapAnalytics.swift b/iosApp/iosApp/Analytics/MapAnalytics.swift deleted file mode 100644 index d62aeffd0..000000000 --- a/iosApp/iosApp/Analytics/MapAnalytics.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// 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 deleted file mode 100644 index a59e011df..000000000 --- a/iosApp/iosApp/Analytics/NearbyTransitAnalytics.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// NearbyTransitAnalytics.swift -// iosApp -// -// Created by Brandon Rodriguez on 6/12/24. -// Copyright © 2024 MBTA. All rights reserved. -// - -import FirebaseAnalytics -import Foundation - -protocol NearbyTransitAnalytics: DestinationRowAnalytics { - func toggledPinnedRoute(pinned: Bool, routeId: String) - func refetchedNearbyTransit() -} - -extension AnalyticsProvider: NearbyTransitAnalytics { - func toggledPinnedRoute(pinned: Bool, routeId: String) { - logEvent( - pinned ? "pin_route" : "unpin_route", - parameters: [ - "route_id": routeId, - ] - ) - } - - func refetchedNearbyTransit() { - logEvent("refetched_nearby_transit") - } -} diff --git a/iosApp/iosApp/Analytics/ScreenTracker.swift b/iosApp/iosApp/Analytics/ScreenTracker.swift deleted file mode 100644 index 5cd0f1349..000000000 --- a/iosApp/iosApp/Analytics/ScreenTracker.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// ScreenTracker.swift -// iosApp -// -// Created by Brandon Rodriguez on 7/9/24. -// Copyright © 2024 MBTA. All rights reserved. -// - -import FirebaseAnalytics -import Foundation - -enum AnalyticsScreen: String { - case nearbyTransit = "NearbyTransitPage" - case tripDetails = "TripDetailsPage" - case stopDetailsFiltered = "StopDetailsFilteredPage" - case stopDetailsUnfiltered = "StopDetailsUnfilteredPage" - case settings = "SettingsPage" -} - -protocol ScreenTracker { - func track(screen: AnalyticsScreen) -} - -extension AnalyticsProvider: ScreenTracker { - func track(screen: AnalyticsScreen) { - Analytics.logEvent( - AnalyticsEventScreenView, - parameters: [ - AnalyticsParameterScreenName: screen.rawValue, - ] - ) - } -} diff --git a/iosApp/iosApp/Analytics/SearchAnalytics.swift b/iosApp/iosApp/Analytics/SearchAnalytics.swift deleted file mode 100644 index 0c87a147c..000000000 --- a/iosApp/iosApp/Analytics/SearchAnalytics.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// 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 deleted file mode 100644 index fc7796433..000000000 --- a/iosApp/iosApp/Analytics/SessionAnalytics.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// 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 deleted file mode 100644 index 0ae50d576..000000000 --- a/iosApp/iosApp/Analytics/StopDetailsAnalytics.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// StopDetailsAnalytics.swift -// iosApp -// -// Created by Brandon Rodriguez on 6/12/24. -// Copyright © 2024 MBTA. All rights reserved. -// - -import FirebaseAnalytics -import Foundation - -protocol StopDetailsAnalytics: DestinationRowAnalytics { - func tappedAlertDetailsLegacy(routeId: String, stopId: String, alertId: String) - func tappedRouteFilterLegacy(routeId: String, stopId: String) -} - -extension AnalyticsProvider: StopDetailsAnalytics { - func tappedAlertDetailsLegacy(routeId: String, stopId: String, alertId: String) { - logEvent( - "tapped_alert_details", - parameters: [ - "route_id": routeId, - "stop_id": stopId, - "alertId": alertId, - ] - ) - } - - func tappedRouteFilterLegacy(routeId: String, stopId: String) { - logEvent( - "tapped_route_filter", - parameters: [ - "route_id": routeId, - "stop_id": stopId, - ] - ) - } -} diff --git a/iosApp/iosApp/Analytics/StopTripDetailsAnalytics.swift b/iosApp/iosApp/Analytics/StopTripDetailsAnalytics.swift deleted file mode 100644 index 2bead70ea..000000000 --- a/iosApp/iosApp/Analytics/StopTripDetailsAnalytics.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// 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/Analytics/TripDetailsAnalytics.swift b/iosApp/iosApp/Analytics/TripDetailsAnalytics.swift deleted file mode 100644 index b4e5647c1..000000000 --- a/iosApp/iosApp/Analytics/TripDetailsAnalytics.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// TripDetailsAnalytics.swift -// iosApp -// -// Created by Horn, Melody on 2024-08-14. -// Copyright © 2024 MBTA. All rights reserved. -// - -import FirebaseAnalytics -import Foundation - -protocol TripDetailsAnalytics { - func tappedDownstreamStop(routeId: String, stopId: String, tripId: String, connectingRouteId: String?) -} - -extension AnalyticsProvider: TripDetailsAnalytics { - func tappedDownstreamStop(routeId: String, stopId: String, tripId: String, connectingRouteId: String?) { - logEvent("tapped_downstream_stop", parameters: [ - "route_id": routeId, - "stop_id": stopId, - "trip_id": tripId, - "connecting_route_id": connectingRouteId ?? "", - ]) - } -} diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index aea3d5f18..60cd0f381 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -27,8 +27,7 @@ struct ContentView: View { @StateObject var stopDetailsVM = StopDetailsViewModel() let transition: AnyTransition = .asymmetric(insertion: .push(from: .bottom), removal: .opacity) - var screenTracker: ScreenTracker = AnalyticsProvider.shared - let analytics: SessionAnalytics = AnalyticsProvider.shared + let analytics: Analytics = AnalyticsProvider.shared let inspection = Inspection() @@ -116,7 +115,7 @@ struct ContentView: View { MorePage(viewModel: settingsVM) .tag(SelectedTab.more) .tabItem { TabLabel(tabText(.more), image: .tabIconMore) } - .onAppear { screenTracker.track(screen: .settings) } + .onAppear { analytics.track(screen: .settings) } } } else { nearbyTab @@ -300,7 +299,7 @@ struct ContentView: View { viewportProvider: viewportProvider ) .onAppear { - screenTracker.track( + analytics.track( screen: stopFilter != nil ? .stopDetailsFiltered : .stopDetailsUnfiltered ) } @@ -327,7 +326,7 @@ struct ContentView: View { .toolbar(.hidden, for: .tabBar) .onAppear { let filtered = filter != nil - screenTracker.track( + analytics.track( screen: filtered ? .stopDetailsFiltered : .stopDetailsUnfiltered ) } @@ -354,14 +353,14 @@ struct ContentView: View { nearbyVM: nearbyVM, mapVM: mapVM ).toolbar(.hidden, for: .tabBar) - .onAppear { screenTracker.track(screen: .tripDetails) } + .onAppear { analytics.track(screen: .tripDetails) } } .transition(transition) case .nearby: nearbySheetContents .transition(transition) - .onAppear { screenTracker.track(screen: .nearbyTransit) } + .onAppear { analytics.track(screen: .nearbyTransit) } default: EmptyView() } diff --git a/iosApp/iosApp/LocationDataManager.swift b/iosApp/iosApp/LocationDataManager.swift index 323f99641..dd24c60a9 100644 --- a/iosApp/iosApp/LocationDataManager.swift +++ b/iosApp/iosApp/LocationDataManager.swift @@ -17,14 +17,14 @@ public class LocationDataManager: NSObject, LocationFetcherDelegate, ObservableO let subscribeToLocations: Bool @Published public var currentLocation: CLLocation? @Published public var authorizationStatus: CLAuthorizationStatus? - let analytics: SessionAnalytics + let analytics: Analytics init( locationFetcher: LocationFetcher = CLLocationManager(), settingsRepository: ISettingsRepository = RepositoryDI().settings, subscribeToLocations: Bool = true, distanceFilter: Double = kCLDistanceFilterNone, - analytics: SessionAnalytics = AnalyticsProvider.shared + analytics: Analytics = AnalyticsProvider.shared ) { self.locationFetcher = locationFetcher self.locationFetcher.distanceFilter = distanceFilter diff --git a/iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift b/iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift index d6773f5e5..f9d2ab8e3 100644 --- a/iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift +++ b/iosApp/iosApp/Pages/AlertDetails/AlertDetails.swift @@ -10,7 +10,7 @@ import shared import SwiftUI struct AlertDetails: View { - var analytics: AlertDetailsAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared var alert: shared.Alert var line: Line? var routes: [Route]? diff --git a/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsPage.swift b/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsPage.swift index 68c9a156d..eb6ec8d61 100644 --- a/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsPage.swift +++ b/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsPage.swift @@ -11,7 +11,7 @@ import SwiftPhoenixClient import SwiftUI struct LegacyStopDetailsPage: View { - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared let globalRepository: IGlobalRepository @State var globalResponse: GlobalResponse? @ObservedObject var viewportProvider: ViewportProvider diff --git a/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsView.swift b/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsView.swift index 8f1e2eb52..a6334faf7 100644 --- a/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsView.swift +++ b/iosApp/iosApp/Pages/LegacyStopDetails/LegacyStopDetailsView.swift @@ -14,7 +14,7 @@ import SwiftPhoenixClient import SwiftUI struct LegacyStopDetailsView: View { - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared let globalRepository: IGlobalRepository @State var globalResponse: GlobalResponse? var stop: Stop diff --git a/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsFilteredRouteView.swift b/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsFilteredRouteView.swift index aa3191d59..ef4bcbb87 100644 --- a/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsFilteredRouteView.swift +++ b/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsFilteredRouteView.swift @@ -11,7 +11,7 @@ import shared import SwiftUI struct StopDetailsFilteredRouteView: View { - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared let patternsByStop: PatternsByStop? let alerts: [shared.Alert] let downstreamAlerts: [shared.Alert] diff --git a/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsRouteView.swift b/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsRouteView.swift index 47da59e27..d36106f0e 100644 --- a/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsRouteView.swift +++ b/iosApp/iosApp/Pages/LegacyStopDetails/StopDetailsRouteView.swift @@ -11,7 +11,7 @@ import shared import SwiftUI struct StopDetailsRouteView: View { - var analytics: StopDetailsAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared let patternsByStop: PatternsByStop let now: Instant let pushNavEntry: (SheetNavigationStackEntry) -> Void diff --git a/iosApp/iosApp/Pages/Map/HomeMapView.swift b/iosApp/iosApp/Pages/Map/HomeMapView.swift index 442c3ec26..db25cace8 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: MapAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared @ObservedObject var contentVM: ContentViewModel @ObservedObject var mapVM: MapViewModel @ObservedObject var nearbyVM: NearbyViewModel diff --git a/iosApp/iosApp/Pages/NearbyTransit/DestinationRowView.swift b/iosApp/iosApp/Pages/NearbyTransit/DestinationRowView.swift index 1bc1be65c..5e0ce9fbd 100644 --- a/iosApp/iosApp/Pages/NearbyTransit/DestinationRowView.swift +++ b/iosApp/iosApp/Pages/NearbyTransit/DestinationRowView.swift @@ -17,7 +17,7 @@ struct DestinationRowView: View { let now: Instant let context: TripInstantDisplay.Context let pushNavEntry: (SheetNavigationStackEntry) -> Void - let analytics: DestinationRowAnalytics + let analytics: Analytics let pinned: Bool let routeType: RouteType @@ -29,7 +29,7 @@ struct DestinationRowView: View { context: TripInstantDisplay.Context, condenseHeadsignPredictions: Bool = false, pushNavEntry: @escaping (SheetNavigationStackEntry) -> Void, - analytics: DestinationRowAnalytics, + analytics: Analytics, pinned: Bool, routeType: RouteType ) { diff --git a/iosApp/iosApp/Pages/NearbyTransit/NearbyStopView.swift b/iosApp/iosApp/Pages/NearbyTransit/NearbyStopView.swift index ad9f6cd31..54f8a9cff 100644 --- a/iosApp/iosApp/Pages/NearbyTransit/NearbyStopView.swift +++ b/iosApp/iosApp/Pages/NearbyTransit/NearbyStopView.swift @@ -10,7 +10,7 @@ import shared import SwiftUI struct NearbyStopView: View { - var analytics: NearbyTransitAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared let patternsAtStop: PatternsByStop let condenseHeadsignPredictions: Bool let now: Instant diff --git a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift index 54005b577..0d4eb1403 100644 --- a/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift +++ b/iosApp/iosApp/Pages/NearbyTransit/NearbyTransitView.swift @@ -8,14 +8,13 @@ import Combine import CoreLocation -import FirebaseAnalytics @_spi(Experimental) import MapboxMaps import os import shared import SwiftUI struct NearbyTransitView: View { - var analytics: NearbyTransitAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared var togglePinnedUsecase = UsecaseDI().toggledPinnedRouteUsecase var pinnedRouteRepository = RepositoryDI().pinnedRoutes @State var predictionsRepository = RepositoryDI().predictions diff --git a/iosApp/iosApp/Pages/NearbyTransit/StopDeparturesSummaryList.swift b/iosApp/iosApp/Pages/NearbyTransit/StopDeparturesSummaryList.swift index d9f5b0ce8..1de2e70a5 100644 --- a/iosApp/iosApp/Pages/NearbyTransit/StopDeparturesSummaryList.swift +++ b/iosApp/iosApp/Pages/NearbyTransit/StopDeparturesSummaryList.swift @@ -16,7 +16,7 @@ struct StopDeparturesSummaryList: View { let now: Instant let context: TripInstantDisplay.Context let pushNavEntry: (SheetNavigationStackEntry) -> Void - let analytics: any DestinationRowAnalytics + let analytics: Analytics let pinned: Bool var body: some View { diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredDepartureDetails.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsFilteredDepartureDetails.swift index ab287f3ff..6f18318ac 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: StopTripDetailsAnalytics = AnalyticsProvider.shared + var analytics: Analytics = 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 fe7c66f6d..4b69c2c91 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: StopTripDetailsAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared var tiles: [TileData] = [] var noPredictionsStatus: RealtimePatterns.NoTripsFormat? diff --git a/iosApp/iosApp/Pages/StopDetails/StopDetailsUnfilteredView.swift b/iosApp/iosApp/Pages/StopDetails/StopDetailsUnfilteredView.swift index 638630557..58f082c81 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: StopTripDetailsAnalytics = AnalyticsProvider.shared + var analytics: Analytics = AnalyticsProvider.shared let inspection = Inspection() init( diff --git a/iosApp/iosApp/Pages/StopDetails/TripDetailsView.swift b/iosApp/iosApp/Pages/StopDetails/TripDetailsView.swift index 7c265ade5..75c902cff 100644 --- a/iosApp/iosApp/Pages/StopDetails/TripDetailsView.swift +++ b/iosApp/iosApp/Pages/StopDetails/TripDetailsView.swift @@ -22,7 +22,7 @@ struct TripDetailsView: View { @ObservedObject var mapVM: MapViewModel @ObservedObject var stopDetailsVM: StopDetailsViewModel - let analytics: TripDetailsAnalytics + let analytics: Analytics let inspection = Inspection() init( @@ -33,8 +33,7 @@ struct TripDetailsView: View { nearbyVM: NearbyViewModel, mapVM: MapViewModel, stopDetailsVM: StopDetailsViewModel, - - analytics: TripDetailsAnalytics = AnalyticsProvider.shared + analytics: Analytics = AnalyticsProvider.shared ) { self.tripFilter = tripFilter self.stopId = stopId diff --git a/iosApp/iosApp/Pages/TripDetails/TripDetailsPage.swift b/iosApp/iosApp/Pages/TripDetails/TripDetailsPage.swift index 24f353743..c617dacca 100644 --- a/iosApp/iosApp/Pages/TripDetails/TripDetailsPage.swift +++ b/iosApp/iosApp/Pages/TripDetails/TripDetailsPage.swift @@ -31,7 +31,7 @@ struct TripDetailsPage: View { @State var vehicleRepository: IVehicleRepository @State var vehicleResponse: VehicleStreamDataResponse? - let analytics: TripDetailsAnalytics + let analytics: Analytics @State var now = Date.now.toKotlinInstant() @@ -53,7 +53,7 @@ struct TripDetailsPage: View { tripPredictionsRepository: ITripPredictionsRepository = RepositoryDI().tripPredictions, tripRepository: ITripRepository = RepositoryDI().trip, vehicleRepository: IVehicleRepository = RepositoryDI().vehicle, - analytics: TripDetailsAnalytics = AnalyticsProvider.shared + analytics: Analytics = AnalyticsProvider.shared ) { self.tripId = tripId self.vehicleId = vehicleId diff --git a/iosApp/iosApp/ViewModels/NearbyViewModel.swift b/iosApp/iosApp/ViewModels/NearbyViewModel.swift index d0ed8ac60..a6283b12d 100644 --- a/iosApp/iosApp/ViewModels/NearbyViewModel.swift +++ b/iosApp/iosApp/ViewModels/NearbyViewModel.swift @@ -52,7 +52,7 @@ class NearbyViewModel: ObservableObject { private let nearbyRepository: INearbyRepository private let visitHistoryUsecase: VisitHistoryUsecase private var fetchNearbyTask: Task? - private var analytics: NearbyTransitAnalytics + private var analytics: Analytics private let settingsRepository: ISettingsRepository init( @@ -64,7 +64,7 @@ class NearbyViewModel: ObservableObject { errorBannerRepository: IErrorBannerStateRepository = RepositoryDI().errorBanner, nearbyRepository: INearbyRepository = RepositoryDI().nearby, visitHistoryUsecase: VisitHistoryUsecase = UsecaseDI().visitHistoryUsecase, - analytics: NearbyTransitAnalytics = AnalyticsProvider.shared, + analytics: Analytics = AnalyticsProvider.shared, settingsRepository: ISettingsRepository = RepositoryDI().settings ) { self.departures = departures diff --git a/iosApp/iosApp/ViewModels/SearchViewModel.swift b/iosApp/iosApp/ViewModels/SearchViewModel.swift index 721034692..35be60d5b 100644 --- a/iosApp/iosApp/ViewModels/SearchViewModel.swift +++ b/iosApp/iosApp/ViewModels/SearchViewModel.swift @@ -32,7 +32,7 @@ class SearchViewModel: ObservableObject { private let searchResultsRepository: ISearchResultRepository private let globalRepository: IGlobalRepository - private let analytics: SearchAnalytics + private let analytics: Analytics private var routeResultsEnabled: Bool private var globalResponse: GlobalResponse? @@ -45,7 +45,7 @@ class SearchViewModel: ObservableObject { globalRepository: IGlobalRepository = RepositoryDI().global, visitHistoryUsecase: VisitHistoryUsecase = UsecaseDI().visitHistoryUsecase, searchResultsRepository: ISearchResultRepository = RepositoryDI().searchResults, - analytics: SearchAnalytics = AnalyticsProvider.shared + analytics: Analytics = AnalyticsProvider.shared ) { self.routeResultsEnabled = routeResultsEnabled self.settingsRepo = settingsRepo diff --git a/iosApp/iosAppTests/Pages/TripDetails/TripDetailsPageTests.swift b/iosApp/iosAppTests/Pages/TripDetails/TripDetailsPageTests.swift index 013f194ec..0a59f624c 100644 --- a/iosApp/iosAppTests/Pages/TripDetails/TripDetailsPageTests.swift +++ b/iosApp/iosAppTests/Pages/TripDetails/TripDetailsPageTests.swift @@ -489,14 +489,6 @@ final class TripDetailsPageTests: XCTestCase { $0.parentStationId = parentStop.id } - struct FakeAnalytics: TripDetailsAnalytics { - let onTappedDownstreamStop: (String, String, String, String?) -> Void - - func tappedDownstreamStop(routeId: String, stopId: String, tripId: String, connectingRouteId: String?) { - onTappedDownstreamStop(routeId, stopId, tripId, connectingRouteId) - } - } - let tripLoaded = PassthroughSubject() let analyticsExp = expectation(description: "sends analytics event") @@ -515,13 +507,19 @@ final class TripDetailsPageTests: XCTestCase { scheduleResponse: .Unknown.shared, onGetTrip: { tripLoaded.send() } ), - analytics: FakeAnalytics { routeId, stopId, tripId, connectingRouteId in - XCTAssertEqual(routeId, route.id) - XCTAssertEqual(stopId, childStop.id) - XCTAssertEqual(tripId, trip.id) - XCTAssertEqual(connectingRouteId, "connectingRoute") + analytics: MockAnalytics(onLogEvent: { name, parameters in + XCTAssertEqual(name, "tapped_downstream_stop") + XCTAssertEqual( + parameters, + [ + "route_id": route.id, + "stop_id": childStop.id, + "trip_id": trip.id, + "connecting_route_id": "connectingRoute", + ] + ) analyticsExp.fulfill() - } + }) ) ViewHosting.host(view: sut) diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/Analytics.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/Analytics.kt new file mode 100644 index 000000000..2c042a2c6 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/Analytics.kt @@ -0,0 +1,183 @@ +package com.mbta.tid.mbta_app.analytics + +import com.mbta.tid.mbta_app.model.RealtimePatterns +import com.mbta.tid.mbta_app.model.RouteType +import kotlin.experimental.ExperimentalObjCName +import kotlin.native.ObjCName + +abstract class Analytics { + protected abstract fun logEvent(name: String, parameters: Map) + + protected abstract fun setUserProperty(name: String, value: String) + + private fun logEvent(name: String, vararg parameters: Pair) { + val paramsMap = mutableMapOf(*parameters) + logEvent(name, paramsMap) + } + + fun performedSearch(query: String) { + logEvent("search", "query" to query) + } + + fun recordSession(colorScheme: AnalyticsColorScheme) { + setUserProperty("color_scheme", colorScheme.recordedValue) + } + + fun recordSession(locationAccess: AnalyticsLocationAccess) { + setUserProperty("location_access", locationAccess.recordedValue) + } + + @OptIn(ExperimentalObjCName::class) + @ObjCName(swiftName = "recordSession") + fun recordSessionHideMaps(hideMaps: Boolean) { + setUserProperty("hide_maps_on", hideMaps.toString()) + } + + @OptIn(ExperimentalObjCName::class) + @ObjCName(swiftName = "recordSession") + fun recordSessionVoiceOver(voiceOver: Boolean) { + setUserProperty("screen_reader_on", voiceOver.toString()) + } + + fun refetchedNearbyTransit() { + logEvent("refetched_nearby_transit") + } + + fun tappedAffectedStops(routeId: String, stopId: String, alertId: String) { + logEvent( + "tapped_affected_stops", + "route_id" to routeId, + "stop_id" to stopId, + "alert_id" to alertId, + ) + } + + fun tappedAlertDetails(routeId: String, stopId: String, alertId: String) { + logEvent( + "tapped_alert_details", + "route_id" to routeId, + "stop_id" to stopId, + "alertId" to alertId, + ) + } + + fun tappedAlertDetailsLegacy(routeId: String, stopId: String, alertId: String) { + logEvent( + "tapped_alert_details", + "route_id" to routeId, + "stop_id" to stopId, + "alertId" to alertId, + ) + } + + fun tappedDeparture( + routeId: String, + stopId: String, + pinned: Boolean, + alert: Boolean, + routeType: RouteType, + noTrips: RealtimePatterns.NoTripsFormat? + ) { + val mode = + when (routeType) { + RouteType.BUS -> "bus" + RouteType.COMMUTER_RAIL -> "commuter rail" + RouteType.FERRY -> "ferry" + RouteType.HEAVY_RAIL -> "subway" + RouteType.LIGHT_RAIL -> "subway" + } + val noTrips = + when (noTrips) { + RealtimePatterns.NoTripsFormat.NoSchedulesToday -> "no service today" + RealtimePatterns.NoTripsFormat.PredictionsUnavailable -> "predictions unavailable" + RealtimePatterns.NoTripsFormat.ServiceEndedToday -> "service ended" + null -> "" + } + logEvent( + "tapped_departure", + "route_id" to routeId, + "stop_id" to stopId, + "pinned" to pinned.toString(), + "alert" to alert.toString(), + "mode" to mode, + "no_trips" to noTrips, + ) + } + + fun tappedDownstreamStop( + routeId: String, + stopId: String, + tripId: String, + connectingRouteId: String? + ) { + logEvent( + "tapped_downstream_stop", + "route_id" to routeId, + "stop_id" to stopId, + "trip_id" to tripId, + "connecting_route_id" to (connectingRouteId ?: ""), + ) + } + + fun tappedOnStop(stopId: String) { + logEvent( + "tapped_on_stop", + "stop_id" to stopId, + ) + } + + fun tappedRouteFilter(routeId: String, stopId: String) { + logEvent( + "tapped_route_filter", + "route_id" to routeId, + "stop_id" to stopId, + ) + } + + fun tappedRouteFilterLegacy(routeId: String, stopId: String) { + logEvent( + "tapped_route_filter", + "route_id" to routeId, + "stop_id" to stopId, + ) + } + + fun tappedTripPlanner(routeId: String, stopId: String, alertId: String) { + logEvent( + "tapped_trip_planner", + "route_id" to routeId, + "stop_id" to stopId, + "alert_id" to alertId, + ) + } + + fun tappedVehicle(routeId: String) { + logEvent("tapped_vehicle", "route_id" to routeId) + } + + fun toggledPinnedRoute(pinned: Boolean, routeId: String) { + logEvent( + if (pinned) "pin_route" else "unpin_route", + "route_id" to routeId, + ) + } + + fun toggledPinnedRouteAtStop(pinned: Boolean, routeId: String) { + logEvent( + if (pinned) "pin_route" else "unpin_route", + "route_id" to routeId, + ) + } + + fun track(screen: AnalyticsScreen) { + logEvent( + ANALYTICS_EVENT_SCREEN_VIEW, + ANALYTICS_PARAMETER_SCREEN_NAME to screen.pageName, + ) + } + + companion object { + const val ANALYTICS_EVENT_SCREEN_VIEW = "screen_view" + const val ANALYTICS_PARAMETER_SCREEN_NAME = "screen_name" + } +} diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsColorScheme.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsColorScheme.kt new file mode 100644 index 000000000..b99cd1d78 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsColorScheme.kt @@ -0,0 +1,12 @@ +package com.mbta.tid.mbta_app.analytics + +enum class AnalyticsColorScheme(val recordedValue: String) { + Light("light"), + Dark("dark"), + /** + * Swift requires forwards compatibility for the SwiftUI ColorScheme enum; presumably Apple is + * planning to implement an [Aing-Tii](https://starwars.fandom.com/wiki/Aing-Tii/Legends) color + * scheme in iOS 50. + */ + Unknown("unknown") +} diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsLocationAccess.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsLocationAccess.kt new file mode 100644 index 000000000..9bfeacd70 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsLocationAccess.kt @@ -0,0 +1,7 @@ +package com.mbta.tid.mbta_app.analytics + +enum class AnalyticsLocationAccess(val recordedValue: String) { + Precise("precise"), + Approximate("approximate"), + Off("off") +} diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsScreen.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsScreen.kt new file mode 100644 index 000000000..b9e50ed01 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/AnalyticsScreen.kt @@ -0,0 +1,9 @@ +package com.mbta.tid.mbta_app.analytics + +enum class AnalyticsScreen(val pageName: String) { + NearbyTransit("NearbyTransitPage"), + TripDetails("TripDetailsPage"), + StopDetailsFiltered("StopDetailsFilteredPage"), + StopDetailsUnfiltered("StopDetailsUnfilteredPage"), + Settings("SettingsPage"), +} diff --git a/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/MockAnalytics.kt b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/MockAnalytics.kt new file mode 100644 index 000000000..8a3ffc96c --- /dev/null +++ b/shared/src/commonMain/kotlin/com/mbta/tid/mbta_app/analytics/MockAnalytics.kt @@ -0,0 +1,18 @@ +package com.mbta.tid.mbta_app.analytics + +import co.touchlab.skie.configuration.annotations.DefaultArgumentInterop + +class MockAnalytics +@DefaultArgumentInterop.Enabled +constructor( + val onLogEvent: (String, Map) -> Unit = { _, _ -> }, + val onSetUserProperty: (String, String) -> Unit = { _, _ -> } +) : Analytics() { + override fun logEvent(name: String, parameters: Map) { + onLogEvent(name, parameters) + } + + override fun setUserProperty(name: String, value: String) { + onSetUserProperty(name, value) + } +}