diff --git a/Apps/LekaApp/Sources/Views/AccountCreation/Process/AccountCreationProcess+Step2.swift b/Apps/LekaApp/Sources/Views/AccountCreation/Process/AccountCreationProcess+Step2.swift index e9465bd52d..132f0bdaa9 100644 --- a/Apps/LekaApp/Sources/Views/AccountCreation/Process/AccountCreationProcess+Step2.swift +++ b/Apps/LekaApp/Sources/Views/AccountCreation/Process/AccountCreationProcess+Step2.swift @@ -46,6 +46,7 @@ extension AccountCreationProcess { self.selectedTab = .carereceiverCreation } }) + .logEventScreenView(screenName: "caregiver_create", context: .context("account_creation_sheet")) .navigationBarTitleDisplayMode(.inline) .interactiveDismissDisabled() } diff --git a/Apps/LekaApp/Sources/Views/MainView/MainView.swift b/Apps/LekaApp/Sources/Views/MainView/MainView.swift index 67d42dc0b4..341c855426 100644 --- a/Apps/LekaApp/Sources/Views/MainView/MainView.swift +++ b/Apps/LekaApp/Sources/Views/MainView/MainView.swift @@ -157,69 +157,47 @@ struct MainView: View { switch self.navigation.selectedCategory { case .home: CategoryHome() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_home") - } + .logEventScreenView(screenName: "home", context: .splitView) case .search: CategorySearchView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_search") - } + .logEventScreenView(screenName: "search", context: .splitView) case .resourcesFirstSteps: CategoryResourcesFirstStepsView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_resources_first_steps") - } + .logEventScreenView(screenName: "resources_first_steps", context: .splitView) case .resourcesVideo: CategoryResourcesVideosView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_resources_video") - } + .logEventScreenView(screenName: "resources_video", context: .splitView) case .resourcesDeepDive: CategoryResourcesDeepDiveView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_resources_deep_dive") - } + .logEventScreenView(screenName: "resources_deep_dive", context: .splitView) case .curriculums: CategoryCurriculumsView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_curriculums") - } + .logEventScreenView(screenName: "curriculums", context: .splitView) case .educationalGames: CategoryEducationalGamesView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_educational_games") - } + .logEventScreenView(screenName: "educational_games", context: .splitView) case .stories: CategoryStoriesView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_stories") - } + .logEventScreenView(screenName: "stories", context: .splitView) case .gamepads: CategoryGamepadsView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_gamepads") - } + .logEventScreenView(screenName: "gamepads", context: .splitView) case .caregivers: CaregiverList() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_caregivers") - } + .logEventScreenView(screenName: "caregivers", context: .splitView) case .carereceivers: CarereceiverList() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_carereceivers") - } + .logEventScreenView(screenName: "carereceivers", context: .splitView) // ? DEVELOPER_MODE + TESTFLIGHT_BUILD case .allPublishedActivities: @@ -247,21 +225,15 @@ struct MainView: View { case .libraryCurriculums: CategoryLibraryView(category: .libraryCurriculums) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_library_curriculums") - } + .logEventScreenView(screenName: "library_curriculums", context: .splitView) case .libraryActivities: CategoryLibraryView(category: .libraryActivities) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_library_activities") - } + .logEventScreenView(screenName: "library_activities", context: .splitView) case .libraryStories: CategoryLibraryView(category: .libraryStories) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_category_library_stories") - } + .logEventScreenView(screenName: "library_stories", context: .splitView) case .none: Text(l10n.MainView.Sidebar.CategoryLabel.home) @@ -279,19 +251,15 @@ struct MainView: View { switch content { case .welcomeView: WelcomeView() - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_welcome") - } + .logEventScreenView(screenName: "welcome", context: .fullScreenCover) + case let .activityView(carereceivers): ActivityView(activity: self.navigation.currentActivity!, reinforcer: carereceivers.first?.reinforcer ?? .rainbow) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_activity") - } + .logEventScreenView(screenName: "activity", context: .fullScreenCover) + case .storyView: StoryView(story: self.navigation.currentStory!) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_story") - } + .logEventScreenView(screenName: "story", context: .fullScreenCover) } } } @@ -302,34 +270,28 @@ struct MainView: View { switch content { case .robotConnection: RobotConnectionView(viewModel: RobotConnectionViewModel()) + .logEventScreenView(screenName: "robot_connection", context: .sheet) .navigationBarTitleDisplayMode(.inline) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_robot_connection") - } + case .settings: SettingsView() + .logEventScreenView(screenName: "settings", context: .sheet) .navigationBarTitleDisplayMode(.inline) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_settings") - } + case .editCaregiver: EditCaregiverView(caregiver: self.caregiverManagerViewModel.currentCaregiver!) + .logEventScreenView(screenName: "caregiver_edit", context: .sheet) .navigationBarTitleDisplayMode(.inline) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_edit_caregiver") - } + case .createCaregiver: CreateCaregiverView() + .logEventScreenView(screenName: "caregiver_create", context: .sheet) .navigationBarTitleDisplayMode(.inline) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_create_caregiver") - } + case .caregiverPicker: CaregiverPicker() + .logEventScreenView(screenName: "caregiver_picker", context: .sheet) .navigationBarTitleDisplayMode(.inline) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_caregiver_picker") - } .onDisappear { DispatchQueue.main.asyncAfter(deadline: .now() + 2) { if case .appUpdateAvailable = UpdateManager.shared.appUpdateStatus { @@ -361,10 +323,8 @@ struct MainView: View { self.navigation.fullScreenCoverContent = .storyView(carereceivers: []) } }) + .logEventScreenView(screenName: "carereceiver_picker", context: .sheet) .navigationBarTitleDisplayMode(.inline) - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_carereceiver_picker") - } } } } @@ -374,9 +334,6 @@ struct MainView: View { } self.persistentDataManager.checkInactivity() } - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_main_navigation_split_view") - } .onChange(of: self.scenePhase) { newPhase in guard self.authManagerViewModel.userAuthenticationState == .loggedIn else { return diff --git a/Apps/LekaApp/Sources/Views/Users/CareReceiver/CarereceiverList.swift b/Apps/LekaApp/Sources/Views/Users/CareReceiver/CarereceiverList.swift index 5683915a31..f094a811c4 100644 --- a/Apps/LekaApp/Sources/Views/Users/CareReceiver/CarereceiverList.swift +++ b/Apps/LekaApp/Sources/Views/Users/CareReceiver/CarereceiverList.swift @@ -79,6 +79,7 @@ struct CarereceiverList: View { .sheet(isPresented: self.$isCarereceiverCreationPresented) { NavigationStack { CreateCarereceiverView() + .logEventScreenView(screenName: "carereceiver_create", context: .sheet) .navigationBarTitleDisplayMode(.inline) } } diff --git a/Apps/LekaApp/Sources/Views/Users/CareReceiver/CarereceiverView.swift b/Apps/LekaApp/Sources/Views/Users/CareReceiver/CarereceiverView.swift index 2c2f4863f5..31501aa2f6 100644 --- a/Apps/LekaApp/Sources/Views/Users/CareReceiver/CarereceiverView.swift +++ b/Apps/LekaApp/Sources/Views/Users/CareReceiver/CarereceiverView.swift @@ -59,6 +59,7 @@ struct CarereceiverView: View { .sheet(isPresented: self.$isEditCarereceiverViewPresented) { NavigationStack { EditCarereceiverView(modifiedCarereceiver: self.$carereceiver) + .logEventScreenView(screenName: "carereceiver_edit", context: .sheet) .navigationBarTitleDisplayMode(.inline) } } diff --git a/Apps/LekaApp/Sources/Views/Users/CareReceiver/CreateCarereceiverView.swift b/Apps/LekaApp/Sources/Views/Users/CareReceiver/CreateCarereceiverView.swift index 9d5dc1fa0a..9402d6b30a 100644 --- a/Apps/LekaApp/Sources/Views/Users/CareReceiver/CreateCarereceiverView.swift +++ b/Apps/LekaApp/Sources/Views/Users/CareReceiver/CreateCarereceiverView.swift @@ -189,9 +189,6 @@ extension l10n { CreateCarereceiverView(onClose: { print("Care receiver creation canceled") }) -// , onCreated: { -// print("Carereceiver \($0.username) created") -// }) } } } diff --git a/Apps/LekaApp/Sources/Views/Users/Caregiver/CaregiverList.swift b/Apps/LekaApp/Sources/Views/Users/Caregiver/CaregiverList.swift index a7675cd815..8916760221 100644 --- a/Apps/LekaApp/Sources/Views/Users/Caregiver/CaregiverList.swift +++ b/Apps/LekaApp/Sources/Views/Users/Caregiver/CaregiverList.swift @@ -79,6 +79,7 @@ struct CaregiverList: View { .sheet(isPresented: self.$isCaregiverCreationPresented) { NavigationStack { CreateCaregiverView() + .logEventScreenView(screenName: "caregiver_create", context: .sheet) .navigationBarTitleDisplayMode(.inline) } } diff --git a/Apps/LekaApp/Sources/Views/Users/Caregiver/CaregiverPicker.swift b/Apps/LekaApp/Sources/Views/Users/Caregiver/CaregiverPicker.swift index 0784460a20..cbe75d3167 100644 --- a/Apps/LekaApp/Sources/Views/Users/Caregiver/CaregiverPicker.swift +++ b/Apps/LekaApp/Sources/Views/Users/Caregiver/CaregiverPicker.swift @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 import AccountKit -import AnalyticsKit import DesignKit import LocalizationKit import SwiftUI @@ -35,6 +34,7 @@ struct CaregiverPicker: View { .sheet(isPresented: self.$isCaregiverCreationPresented) { NavigationStack { CreateCaregiverView() + .logEventScreenView(screenName: "caregiver_create", context: .sheet) .navigationBarTitleDisplayMode(.inline) } } diff --git a/Modules/AccountKit/Sources/Authentication/AuthManagerViewModel.swift b/Modules/AccountKit/Sources/Authentication/AuthManagerViewModel.swift index b83e8bac02..452c2bb981 100644 --- a/Modules/AccountKit/Sources/Authentication/AuthManagerViewModel.swift +++ b/Modules/AccountKit/Sources/Authentication/AuthManagerViewModel.swift @@ -2,7 +2,6 @@ // Copyright APF France handicap // SPDX-License-Identifier: Apache-2.0 -import AnalyticsKit import Combine import Foundation import LocalizationKit diff --git a/Modules/AccountKit/Sources/Managers/RootAccounts/RootAccountManager.swift b/Modules/AccountKit/Sources/Managers/RootAccounts/RootAccountManager.swift index 40d349b787..4d1a460e0e 100644 --- a/Modules/AccountKit/Sources/Managers/RootAccounts/RootAccountManager.swift +++ b/Modules/AccountKit/Sources/Managers/RootAccounts/RootAccountManager.swift @@ -2,7 +2,6 @@ // Copyright APF France handicap // SPDX-License-Identifier: Apache-2.0 -import AnalyticsKit import Combine public class RootAccountManager { diff --git a/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager+ScreenView.swift b/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager+ScreenView.swift index 288328614c..54cbabf26b 100644 --- a/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager+ScreenView.swift +++ b/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager+ScreenView.swift @@ -17,18 +17,83 @@ public extension AnalyticsManager { logEvent(.screenView, parameters: params) } -} -public extension View { - func logEventScreenView(screenName: String, screenClass: String? = nil, parameters: [String: Any] = [:]) -> some View { - let screenClass = screenClass ?? String(describing: type(of: self)) - return self.onAppear { - AnalyticsManager.logEventScreenView(screenName: screenName, screenClass: screenClass, parameters: parameters) + enum ScreenViewContext { + case splitView + case sheet + case fullScreenCover + case context(String) + + // MARK: Internal + + var description: String { + switch self { + case .splitView: + "splitview" + case .sheet: + "sheet" + case .fullScreenCover: + "fullscreen" + case let .context(value): + "\(value)" + } } } } -// MARK: - MyCustomView +// MARK: - AnalyticsLogScreenViewViewModifier + +struct AnalyticsLogScreenViewViewModifier: ViewModifier { + // MARK: Lifecycle + + init( + screenClass: String, + screenName: String, + context: AnalyticsManager.ScreenViewContext? = nil, + parameters: [String: Any] = [:] + ) { + self.screenName = screenName + self.screenClass = screenClass + self.context = context + self.parameters = parameters + } + + // MARK: Internal + + let screenClass: String + let screenName: String + let context: AnalyticsManager.ScreenViewContext? + let parameters: [String: Any] + + func body(content: Content) -> some View { + content + .onAppear { + let params: [String: Any] = [ + "lk_context": context?.description ?? NSNull(), + ].merging(self.parameters) { _, new in new } + + AnalyticsManager.logEventScreenView(screenName: self.screenName, screenClass: self.screenClass, parameters: params) + } + } +} + +public extension View { + func logEventScreenView( + screenClass: String? = nil, + screenName: String, + context: AnalyticsManager.ScreenViewContext? = nil, + parameters: [String: Any] = [:] + ) -> some View { + self.modifier( + AnalyticsLogScreenViewViewModifier( + screenClass: screenClass ?? String(describing: type(of: self)), + screenName: screenName, + context: context, + parameters: parameters + ) + ) + } +} #Preview { struct MyCustomView: View { diff --git a/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager+UserProperties.swift b/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager+UserProperties.swift index 368d2de371..5c518b306e 100644 --- a/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager+UserProperties.swift +++ b/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager+UserProperties.swift @@ -7,6 +7,7 @@ import FirebaseAnalytics public extension AnalyticsManager { static func setUserID(_ userID: String?) { Analytics.setUserID(userID) + CrashlyticsManager.setUserID(userID) } static func setUserPropertyUserIsLoggedIn(value: Bool) { diff --git a/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager.swift b/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager.swift index fbac48f79e..2cfafe65ab 100644 --- a/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager.swift +++ b/Modules/AnalyticsKit/Sources/Analytics/AnalyticsManager.swift @@ -4,6 +4,8 @@ import Combine import FirebaseAnalytics +import Foundation +import Logging public class AnalyticsManager { // MARK: Lifecycle @@ -89,11 +91,99 @@ public class AnalyticsManager { } } + struct AnalyticsError { + // MARK: Lifecycle + + init(domain: Domain, code: Int, message: String) { + self.domain = domain + self.code = code + self.message = "\(message)" + } + + // MARK: Internal + + enum Domain: String { + case event + case userProperty + } + + let message: Logger.Message + let domain: Domain + let code: Int + + var error: NSError { + NSError( + domain: "app.leka.error.analytics.\(self.domain)", + code: self.code, + userInfo: [NSLocalizedDescriptionKey: self.message.description] + ) + } + } + static func logEvent(_ event: Event, parameters: [String: Any] = [:]) { + if event.name.isEmpty { + let error = AnalyticsError(domain: .event, code: 0, message: "Event name is empty: \(event)") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + } + + if event.name.count > 40 { + let error = AnalyticsError(domain: .event, code: 1, message: "Event name is too long: \(event) - (\(event.name.count) characters)") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + } + + let kAuthorizedKeys: [String] = ["screen_class", "screen_name", "content_type", "item_id"] + + for (key, value) in parameters { + if key.isEmpty { + let error = AnalyticsError(domain: .event, code: 2, message: "Event parameter key is empty: \(event)") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + } + + if key.count > 40 { + let error = AnalyticsError(domain: .event, code: 3, message: "Event parameter key too long: \(event) - \(key) - (\(key.count) characters)") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + } + + if "\(value)".count > 100 { + let error = AnalyticsError(domain: .event, code: 4, message: "Event parameter value too long: \(event) - \(key) - \(value) - (\("\(value)".count) characters)") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + } + + if !key.starts(with: "lk_"), !kAuthorizedKeys.contains(key) { + let error = AnalyticsError(domain: .event, code: 5, message: "Event parameter key missing prefix 'lk_': \(event) - \(key)") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + CrashlyticsManager.recordError(error.error) + } + } + Analytics.logEvent(event.name, parameters: parameters) } static func setUserProperty(value: String, forName name: String) { + if name.isEmpty { + let error = AnalyticsError(domain: .userProperty, code: 0, message: "User property name is empty") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + } + + if name.count > 24 { + let error = AnalyticsError(domain: .userProperty, code: 1, message: "User property name is too long: \(name) - (\(name.count) characters)") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + } + + if value.count > 36 { + let error = AnalyticsError(domain: .userProperty, code: 2, message: "User property value is too long: \(name) - \(value) - (\(value.count) characters)") + log.error(error.message) + CrashlyticsManager.recordError(error.error) + } + Analytics.setUserProperty(value, forName: name) } diff --git a/Modules/AnalyticsKit/Sources/Crashlytics/CrashlyticsManager.swift b/Modules/AnalyticsKit/Sources/Crashlytics/CrashlyticsManager.swift index 76d167032e..e0a71e2b9b 100644 --- a/Modules/AnalyticsKit/Sources/Crashlytics/CrashlyticsManager.swift +++ b/Modules/AnalyticsKit/Sources/Crashlytics/CrashlyticsManager.swift @@ -13,7 +13,7 @@ public class CrashlyticsManager { Crashlytics.crashlytics().setCustomValue(value, forKey: key) } - public static func setUserID(_ userID: String) { + public static func setUserID(_ userID: String?) { Crashlytics.crashlytics().setUserID(userID) } diff --git a/Modules/ContentKit/Examples/ContentKitExample/Sources/CurriculumListView.swift b/Modules/ContentKit/Examples/ContentKitExample/Sources/CurriculumListView.swift index 975b7222a5..923d61eec1 100644 --- a/Modules/ContentKit/Examples/ContentKitExample/Sources/CurriculumListView.swift +++ b/Modules/ContentKit/Examples/ContentKitExample/Sources/CurriculumListView.swift @@ -2,6 +2,7 @@ // Copyright APF France handicap // SPDX-License-Identifier: Apache-2.0 +import AnalyticsKit import ContentKit import MarkdownUI import SwiftUI @@ -14,13 +15,23 @@ struct CurriculumListView: View { var body: some View { List { ForEach(self.activities) { curriculum in - NavigationLink(destination: CurriculumDetailsView(curriculum: curriculum)) { + NavigationLink(destination: + CurriculumDetailsView(curriculum: curriculum) + ) { Image(uiImage: curriculum.details.iconImage) .resizable() .scaledToFit() .frame(width: 44, height: 44) Text(curriculum.details.title) } + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .curriculum, + id: curriculum.id, + name: curriculum.name, + origin: .generalLibrary + ) + }) } } .navigationTitle("Curriculums") diff --git a/Modules/ContentKit/Sources/Views/ActivityDetailsView.swift b/Modules/ContentKit/Sources/Views/ActivityDetailsView.swift index bbc84b81d8..bb305689d9 100644 --- a/Modules/ContentKit/Sources/Views/ActivityDetailsView.swift +++ b/Modules/ContentKit/Sources/Views/ActivityDetailsView.swift @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 import AccountKit -import AnalyticsKit import DesignKit import Fit import LocalizationKit @@ -117,9 +116,6 @@ public struct ActivityDetailsView: View { .markdownTheme(.gitHub) } } - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_activity_details_view") - } .toolbar { #if DEVELOPER_MODE || TESTFLIGHT_BUILD if let currentCaregiverID = self.caregiverManagerViewModel.currentCaregiver?.id { diff --git a/Modules/ContentKit/Sources/Views/ActivityGridView.swift b/Modules/ContentKit/Sources/Views/ActivityGridView.swift index 0f9fe55893..e4e30c86fd 100644 --- a/Modules/ContentKit/Sources/Views/ActivityGridView.swift +++ b/Modules/ContentKit/Sources/Views/ActivityGridView.swift @@ -24,14 +24,13 @@ public struct ActivityGridView: View { ForEach(self.activities) { activity in NavigationLink(destination: ActivityDetailsView(activity: activity, onStartActivity: self.onStartActivity) - .onAppear { - AnalyticsManager.logEventSelectContent( - type: .educationalGame, - id: activity.id, - name: activity.name, - origin: .generalLibrary - ) - } + .logEventScreenView( + screenName: "activity_details", + context: .splitView, + parameters: [ + "lk_activity_id": "\(activity.name)-\(activity.id)", + ] + ) ) { VStack { Image(uiImage: activity.details.iconImage) @@ -56,6 +55,14 @@ public struct ActivityGridView: View { } .padding(.vertical) } + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .educationalGame, + id: activity.id, + name: activity.name, + origin: .generalLibrary + ) + }) } } .padding() diff --git a/Modules/ContentKit/Sources/Views/ActivityHorizontalListView.swift b/Modules/ContentKit/Sources/Views/ActivityHorizontalListView.swift index 96ba6df2e5..9f5a7d5ffa 100644 --- a/Modules/ContentKit/Sources/Views/ActivityHorizontalListView.swift +++ b/Modules/ContentKit/Sources/Views/ActivityHorizontalListView.swift @@ -2,6 +2,7 @@ // Copyright APF France handicap // SPDX-License-Identifier: Apache-2.0 +import AnalyticsKit import DesignKit import SwiftUI @@ -23,6 +24,13 @@ public struct ActivityHorizontalListView: View { ForEach(self.activities) { activity in NavigationLink(destination: ActivityDetailsView(activity: activity, onStartActivity: self.onActivitySelected) + .logEventScreenView( + screenName: "activity_details", + context: .splitView, + parameters: [ + "lk_activity_id": "\(activity.name)-\(activity.id)", + ] + ) ) { VStack(spacing: 10) { Image(uiImage: activity.details.iconImage) @@ -51,6 +59,14 @@ public struct ActivityHorizontalListView: View { } .frame(width: 280) } + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .activity, + id: activity.id, + name: activity.name, + origin: .generalLibrary + ) + }) } } } diff --git a/Modules/ContentKit/Sources/Views/ActivityListView.swift b/Modules/ContentKit/Sources/Views/ActivityListView.swift index 0b6765fa47..efe638c4e1 100644 --- a/Modules/ContentKit/Sources/Views/ActivityListView.swift +++ b/Modules/ContentKit/Sources/Views/ActivityListView.swift @@ -25,14 +25,14 @@ public struct ActivityListView: View { ForEach(self.activities) { activity in NavigationLink(destination: ActivityDetailsView(activity: activity, onStartActivity: self.onStartActivity) - .onAppear { - AnalyticsManager.logEventSelectContent( - type: .activity, - id: activity.id, - name: activity.name, - origin: .generalLibrary - ) - } + .logEventScreenView( + screenName: "activity_details", + context: .splitView, + parameters: [ + "lk_activity_id": "\(activity.name)-\(activity.id)", + ] + ) + ) { HStack(alignment: .center) { Image(uiImage: activity.details.iconImage) @@ -120,6 +120,14 @@ public struct ActivityListView: View { .frame(maxWidth: .infinity, maxHeight: 120) .contentShape(Rectangle()) } + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .activity, + id: activity.id, + name: activity.name, + origin: .generalLibrary + ) + }) .buttonStyle(.plain) } } diff --git a/Modules/ContentKit/Sources/Views/CurriculumDetailsView.swift b/Modules/ContentKit/Sources/Views/CurriculumDetailsView.swift index bd106cf584..be96c70a88 100644 --- a/Modules/ContentKit/Sources/Views/CurriculumDetailsView.swift +++ b/Modules/ContentKit/Sources/Views/CurriculumDetailsView.swift @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 import AccountKit -import AnalyticsKit import DesignKit import Fit import LocalizationKit @@ -142,9 +141,6 @@ public struct CurriculumDetailsView: View { } } } - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_curriculum_details_view") - } .toolbar { #if DEVELOPER_MODE || TESTFLIGHT_BUILD if let currentCaregiverID = self.caregiverManagerViewModel.currentCaregiver?.id { diff --git a/Modules/ContentKit/Sources/Views/CurriculumGridView.swift b/Modules/ContentKit/Sources/Views/CurriculumGridView.swift index 1f6e2f9137..0b5d074eb2 100644 --- a/Modules/ContentKit/Sources/Views/CurriculumGridView.swift +++ b/Modules/ContentKit/Sources/Views/CurriculumGridView.swift @@ -23,23 +23,26 @@ public struct CurriculumGridView: View { public var body: some View { LazyVGrid(columns: self.columns) { ForEach(self.curriculums) { curriculum in - NavigationLink( - destination: - CurriculumDetailsView( - curriculum: curriculum, - onActivitySelected: self.onActivitySelected - ) - .onAppear { - AnalyticsManager.logEventSelectContent( - type: .curriculum, - id: curriculum.id, - name: curriculum.name, - origin: .personalLibrary + NavigationLink(destination: + CurriculumDetailsView(curriculum: curriculum, onActivitySelected: self.onActivitySelected) + .logEventScreenView( + screenName: "curriculum_details", + context: .splitView, + parameters: [ + "lk_curriculum_id": "\(curriculum.name)-\(curriculum.id)", + ] ) - } ) { CurriculumGroupboxView(curriculum: curriculum) } + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .curriculum, + id: curriculum.id, + name: curriculum.name, + origin: .personalLibrary + ) + }) } } .padding() diff --git a/Modules/ContentKit/Sources/Views/CurriculumHorizontalListView.swift b/Modules/ContentKit/Sources/Views/CurriculumHorizontalListView.swift index 7fcf2b6ceb..ebe6a21946 100644 --- a/Modules/ContentKit/Sources/Views/CurriculumHorizontalListView.swift +++ b/Modules/ContentKit/Sources/Views/CurriculumHorizontalListView.swift @@ -23,23 +23,26 @@ public struct CurriculumHorizontalListView: View { ScrollView(.horizontal) { HStack(alignment: .firstTextBaseline) { ForEach(self.curriculums) { curriculum in - NavigationLink( - destination: - CurriculumDetailsView( - curriculum: curriculum, - onActivitySelected: self.onActivitySelected - ) - .onAppear { - AnalyticsManager.logEventSelectContent( - type: .curriculum, - id: curriculum.id, - name: curriculum.name, - origin: .generalLibrary + NavigationLink(destination: + CurriculumDetailsView(curriculum: curriculum, onActivitySelected: self.onActivitySelected) + .logEventScreenView( + screenName: "curriculum_details", + context: .splitView, + parameters: [ + "lk_curriculum_id": "\(curriculum.name)-\(curriculum.id)", + ] ) - } ) { CurriculumGroupboxView(curriculum: curriculum) } + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .curriculum, + id: curriculum.id, + name: curriculum.name, + origin: .generalLibrary + ) + }) } } } diff --git a/Modules/ContentKit/Sources/Views/GamepadGridView.swift b/Modules/ContentKit/Sources/Views/GamepadGridView.swift index 830d045297..92d7db683a 100644 --- a/Modules/ContentKit/Sources/Views/GamepadGridView.swift +++ b/Modules/ContentKit/Sources/Views/GamepadGridView.swift @@ -23,14 +23,14 @@ public struct GamepadGridView: View { ForEach(self.gamepads) { activity in NavigationLink(destination: ActivityDetailsView(activity: activity, onStartActivity: self.onStartGamepad) - .onAppear { - AnalyticsManager.logEventSelectContent( - type: .gamepad, - id: activity.id, - name: activity.name, - origin: .generalLibrary - ) - } + .logEventScreenView( + screenName: "activity_details", + context: .splitView, + parameters: [ + "lk_activity_id": "\(activity.name)-\(activity.id)", + ] + ) + ) { VStack { Image(uiImage: activity.details.iconImage) @@ -46,6 +46,15 @@ public struct GamepadGridView: View { Spacer() } } + .simultaneousGesture(TapGesture().onEnded { + log.debug("Gamepad selected: \(activity.name)") + AnalyticsManager.logEventSelectContent( + type: .gamepad, + id: activity.id, + name: activity.name, + origin: .generalLibrary + ) + }) } } } diff --git a/Modules/ContentKit/Sources/Views/LibraryActivityListView.swift b/Modules/ContentKit/Sources/Views/LibraryActivityListView.swift index a6957a51c8..5aea2aa729 100644 --- a/Modules/ContentKit/Sources/Views/LibraryActivityListView.swift +++ b/Modules/ContentKit/Sources/Views/LibraryActivityListView.swift @@ -25,14 +25,13 @@ public struct LibraryActivityListView: View { ForEach(self.activities) { activity in NavigationLink(destination: ActivityDetailsView(activity: activity, onStartActivity: self.onStartActivity) - .onAppear { - AnalyticsManager.logEventSelectContent( - type: .activity, - id: activity.id, - name: activity.name, - origin: .personalLibrary - ) - } + .logEventScreenView( + screenName: "activity_details", + context: .splitView, + parameters: [ + "lk_activity_id": "\(activity.name)-\(activity.id)", + ] + ) ) { HStack(alignment: .center) { Image(uiImage: activity.details.iconImage) @@ -121,6 +120,14 @@ public struct LibraryActivityListView: View { .contentShape(Rectangle()) } .buttonStyle(.plain) + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .activity, + id: activity.id, + name: activity.name, + origin: .personalLibrary + ) + }) } } .padding() diff --git a/Modules/ContentKit/Sources/Views/StoryDetailsView.swift b/Modules/ContentKit/Sources/Views/StoryDetailsView.swift index 0ebf2ef11f..ae54a769a0 100644 --- a/Modules/ContentKit/Sources/Views/StoryDetailsView.swift +++ b/Modules/ContentKit/Sources/Views/StoryDetailsView.swift @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 import AccountKit -import AnalyticsKit import DesignKit import Fit import LocalizationKit @@ -108,9 +107,6 @@ public struct StoryDetailsView: View { .markdownTheme(.gitHub) } } - .onAppear { - AnalyticsManager.logEventScreenView(screenName: "view_story_details_view") - } .toolbar { #if DEVELOPER_MODE || TESTFLIGHT_BUILD if let currentCaregiverID = self.caregiverManagerViewModel.currentCaregiver?.id { diff --git a/Modules/ContentKit/Sources/Views/StoryGridView.swift b/Modules/ContentKit/Sources/Views/StoryGridView.swift index 7a430e518e..039c7dd456 100644 --- a/Modules/ContentKit/Sources/Views/StoryGridView.swift +++ b/Modules/ContentKit/Sources/Views/StoryGridView.swift @@ -23,14 +23,13 @@ public struct StoryGridView: View { ForEach(self.stories) { story in NavigationLink(destination: StoryDetailsView(story: story, onStartStory: self.onStartStory) - .onAppear { - AnalyticsManager.logEventSelectContent( - type: .story, - id: story.id, - name: story.name, - origin: .generalLibrary - ) - } + .logEventScreenView( + screenName: "story_details", + context: .splitView, + parameters: [ + "lk_story_id": "\(story.name)-\(story.id)", + ] + ) ) { VStack(spacing: 0) { Image(uiImage: story.details.iconImage) @@ -52,6 +51,14 @@ public struct StoryGridView: View { } .padding(.vertical) } + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .story, + id: story.id, + name: story.name, + origin: .generalLibrary + ) + }) } } } diff --git a/Modules/ContentKit/Sources/Views/StoryListView.swift b/Modules/ContentKit/Sources/Views/StoryListView.swift index 33cb3e7268..fbb8f3a32d 100644 --- a/Modules/ContentKit/Sources/Views/StoryListView.swift +++ b/Modules/ContentKit/Sources/Views/StoryListView.swift @@ -25,14 +25,13 @@ public struct StoryListView: View { ForEach(self.stories) { story in NavigationLink(destination: StoryDetailsView(story: story, onStartStory: self.onStartStory) - .onAppear { - AnalyticsManager.logEventSelectContent( - type: .story, - id: story.id, - name: story.name, - origin: .personalLibrary - ) - } + .logEventScreenView( + screenName: "story_details", + context: .splitView, + parameters: [ + "lk_story_id": "\(story.name)-\(story.id)", + ] + ) ) { HStack(alignment: .center, spacing: 30) { Image(uiImage: story.details.iconImage) @@ -103,6 +102,14 @@ public struct StoryListView: View { } .frame(maxWidth: .infinity, maxHeight: 120) .contentShape(Rectangle()) + .simultaneousGesture(TapGesture().onEnded { + AnalyticsManager.logEventSelectContent( + type: .story, + id: story.id, + name: story.name, + origin: .personalLibrary + ) + }) } .buttonStyle(.plain) } diff --git a/Modules/GameEngineKit/Sources/OldSystem/Stories/StoryView.swift b/Modules/GameEngineKit/Sources/OldSystem/Stories/StoryView.swift index 0d7561afcf..aa83bab50b 100644 --- a/Modules/GameEngineKit/Sources/OldSystem/Stories/StoryView.swift +++ b/Modules/GameEngineKit/Sources/OldSystem/Stories/StoryView.swift @@ -56,6 +56,13 @@ public struct StoryView: View { } .sheet(isPresented: self.$isInfoSheetPresented) { StoryDetailsView(story: self.viewModel.currentStory) + .logEventScreenView( + screenName: "story_details", + context: .sheet, + parameters: [ + "lk_story_id": "\(self.viewModel.currentStory.name)-\(self.viewModel.currentStory.id)", + ] + ) } .onAppear { Robot.shared.stop() diff --git a/Modules/GameEngineKit/Sources/OldSystem/Views/Activity/ActivityView.swift b/Modules/GameEngineKit/Sources/OldSystem/Views/Activity/ActivityView.swift index 435debf866..df7d19fd73 100644 --- a/Modules/GameEngineKit/Sources/OldSystem/Views/Activity/ActivityView.swift +++ b/Modules/GameEngineKit/Sources/OldSystem/Views/Activity/ActivityView.swift @@ -194,6 +194,13 @@ public struct ActivityView: View { } .sheet(isPresented: self.$isInfoSheetPresented) { ActivityDetailsView(activity: self.viewModel.currentActivity) + .logEventScreenView( + screenName: "activity_details", + context: .sheet, + parameters: [ + "lk_activity_id": "\(self.viewModel.currentActivity.name)-\(self.viewModel.currentActivity.id)", + ] + ) } .fullScreenCover(isPresented: self.$viewModel.isCurrentActivityCompleted) { self.endOfActivityScoreView