diff --git a/Runnect-iOS/Runnect-iOS/Global/Analytics/GAEvent.swift b/Runnect-iOS/Runnect-iOS/Global/Analytics/GAEvent.swift index 6790d6b9..d84211ca 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Analytics/GAEvent.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Analytics/GAEvent.swift @@ -8,21 +8,81 @@ import Foundation struct GAEvent { + + struct Param { + static let source = "source" + static let courseId = "course_id" + static let distanceM = "distance_m" + static let pointCount = "point_count" + static let drawingTimeSec = "drawing_time_sec" + static let departureName = "departure_name" + static let elapsedSec = "elapsed_sec" + static let totalTimeSec = "total_time_sec" + static let totalDistanceM = "total_distance_m" + static let avgPaceSecPerKm = "avg_pace_sec_per_km" + static let pauseCount = "pause_count" + static let totalPauseSec = "total_pause_sec" + static let pauseDurationSec = "pause_duration_sec" + static let completionRate = "completion_rate" + static let abandonReason = "abandon_reason" + static let targetDistanceM = "target_distance_m" + static let countdownSecRemaining = "countdown_sec_remaining" + static let method = "method" + static let isNewUser = "is_new_user" + static let errorCode = "error_code" + static let nicknameLength = "nickname_length" + static let shareTarget = "share_target" + static let keyword = "keyword" + static let resultCount = "result_count" + static let storageType = "storage_type" + static let daysSinceCreated = "days_since_created" + static let courseCount = "course_count" + static let recordId = "record_id" + static let launchType = "launch_type" + static let referrer = "referrer" + static let deeplinkUrl = "deeplink_url" + static let targetScreen = "target_screen" + static let screenName = "screen_name" + static let withdrawReason = "withdraw_reason" + static let daysSinceSignup = "days_since_signup" + static let totalRuns = "total_runs" + static let totalCourses = "total_courses" + static let changedFields = "changed_fields" + } + struct View { - + // 진입 화면 static let viewHome = "view_home" // 앱 실행 static let viewSocialLogin = "view_social_login" - + // 코스발견 static let viewCourseSearch = "view_course_search" static let viewCourseDetail = "view_course_detail" static let viewUserProfile = "view_user_profile" static let viewCourseUpload = "view_course_upload" - + // 마이페이지 static let viewSuccessLogout = "view_success_logout" static let viewSuccessWithdraw = "view_success_withdraw" + + // 코스 그리기 / 러닝 + static let viewGiveNickname = "view_give_nickname" + static let viewCourseDrawing = "view_course_drawing" + static let viewCourseCompleteResult = "view_course_complete_result" + static let viewCountdown = "view_countdown" + static let viewEndRun = "view_end_run" + + // 코스 발견 / 보관함 + static let viewDiscoverPick = "view_discover_pick" + static let viewStorageMyDraw = "view_storage_my_draw" + static let viewStorageScrap = "view_storage_scrap" + static let viewMyDrawDetail = "view_my_draw_detail" + static let viewMyHistoryDetail = "view_my_history_detail" + + // 마이페이지 추가 + static let viewMyReward = "view_my_reward" + static let viewEditProfile = "view_edit_profile" } struct Button { @@ -80,9 +140,43 @@ struct GAEvent { static let clickJoinInCourseDiscovery = "click_join_in_course_discovery" static let clickJoinInStorage = "click_join_in_storage" static let clickJoinInMyPage = "click_join_in_my_page" + + // 신규 클릭 이벤트 + static let clickCancelCountdown = "click_cancel_countdown" + static let clickRunFromDetail = "click_run_from_detail" + static let clickRunFromStorage = "click_run_from_storage" + static let clickUnscrap = "click_unscrap" + static let clickSaveRunRecord = "click_save_run_record" + static let clickShareRunRecord = "click_share_run_record" + static let clickRunAgain = "click_run_again" + static let clickMyDrawCourse = "click_my_draw_course" + static let clickNicknameSkip = "click_nickname_skip" + static let clickShareAfterCourseComplete = "click_share_after_course_complete" + static let clickRunAgainFromHistory = "click_run_again_from_history" } struct Share { static let openShareLink = "open_share_link" } + + struct Action { + static let loginSuccess = "action_login_success" + static let loginFail = "action_login_fail" + static let nicknameComplete = "action_nickname_complete" + static let courseDrawingStart = "action_course_drawing_start" + static let courseDrawingComplete = "action_course_drawing_complete" + static let runStart = "action_run_start" + static let runPause = "action_run_pause" + static let runResume = "action_run_resume" + static let runComplete = "action_run_complete" + static let runAbandon = "action_run_abandon" + static let courseSearchExecute = "action_course_search_execute" + static let courseUploadComplete = "action_course_upload_complete" + static let editProfileComplete = "action_edit_profile_complete" + } + + struct System { + static let appOpen = "sys_app_open" + static let deeplinkOpen = "sys_deeplink_open" + } } diff --git a/Runnect-iOS/Runnect-iOS/Global/Analytics/GAManager.swift b/Runnect-iOS/Runnect-iOS/Global/Analytics/GAManager.swift index 72ad32bb..3e6a222b 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Analytics/GAManager.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Analytics/GAManager.swift @@ -18,6 +18,7 @@ final class GAManager { case screen(screenName: String) case button(buttonName: String) case share(eventName: String, courseType: String, courseId: Int) + case custom(eventName: String, parameters: [String: Any]?) var eventName: String { switch self { @@ -27,6 +28,8 @@ final class GAManager { return "button" case .share(let eventName, _, _): return eventName + case .custom(let eventName, _): + return eventName } } @@ -38,6 +41,8 @@ final class GAManager { return ["button": buttonName] case .share(_, let courseType, let courseId): return ["course_type": courseType, "course_id": courseId] + case .custom(_, let parameters): + return parameters } } } diff --git a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift index 478433ef..85017d49 100644 --- a/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift +++ b/Runnect-iOS/Runnect-iOS/Global/Supports/SceneDelegate.swift @@ -53,6 +53,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { courseId: courseId )) + let targetScreen = courseType == .publicCourse ? "CourseDetailVC" : "RunningWaitingVC" + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.System.deeplinkOpen, + parameters: [ + GAEvent.Param.deeplinkUrl: incomingURL.absoluteString, + GAEvent.Param.targetScreen: targetScreen + ] + )) + guard let windowScene = scene as? UIWindowScene else { return } let window = UIWindow(windowScene: windowScene) let navigationController = UINavigationController() diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift index dea9cbf5..d0c644a1 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseDetailVC.swift @@ -168,9 +168,17 @@ extension CourseDetailVC { showToastOnWindow(text: "러넥트에 가입하면 코스를 스크랩할 수 있어요") return } - + guard let publicCourseId = publicCourseId else { return } - + + // 스크랩 취소 이벤트 + if sender.isSelected, let courseId = self.courseId { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Button.clickUnscrap, + parameters: [GAEvent.Param.courseId: courseId] + )) + } + scrapCourse(scrapTF: !sender.isSelected) delegate?.didUpdateScrapState(publicCourseId: publicCourseId, isScrapped: !sender.isSelected) /// 코스 발견 UI Update 부분 marathonDelegate?.didUpdateMarathonScrapState(publicCourseId: publicCourseId, isScrapped: !sender.isSelected) // 마라톤 코스 UI @@ -199,7 +207,7 @@ extension CourseDetailVC { return } - analyze(screenName: GAEvent.Button.clickUserProfile) + analyze(buttonName: GAEvent.Button.clickUserProfile) guard let userId = self.userId else {return} let userProfile = UserProfileVC() @@ -210,6 +218,10 @@ extension CourseDetailVC { @objc private func startButtonDidTap() { guard handleVisitor() else { return } guard let courseId = self.courseId else { return } + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Button.clickRunFromDetail, + parameters: [GAEvent.Param.courseId: courseId] + )) getCourseDetailWithPath(courseId: courseId) } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseEditVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseEditVC.swift index cf5b969f..c96b41f8 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseEditVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDetail/VC/CourseEditVC.swift @@ -68,6 +68,13 @@ class CourseEditVC: UIViewController { setTapGesture() addKeyboardObserver() textViewDidChange(self.activityTextView) + + if let publicCourseId = publicCourseId { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.View.viewMyDrawDetail, + parameters: [GAEvent.Param.courseId: publicCourseId] + )) + } } override func viewWillDisappear(_ animated: Bool) { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseSearchVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseSearchVC.swift index 50c001ba..b2098246 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseSearchVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseSearchVC.swift @@ -239,6 +239,13 @@ extension CourseSearchVC { let responseDto = try result.map(BaseResponse.self) guard let data = responseDto.data else { return } self.analyze(buttonName: GAEvent.Button.clickTrySearchCourse) + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.courseSearchExecute, + parameters: [ + GAEvent.Param.keyword: keyword, + GAEvent.Param.resultCount: data.publicCourses.count + ] + )) self.setData(data: data.publicCourses) } catch { self.setData(data: []) diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseUploadVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseUploadVC.swift index 1c7e462b..4f3b4018 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseUploadVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDiscovery/Views/VC/CourseUploadVC.swift @@ -341,6 +341,12 @@ extension CourseUploadVC { case .success(let result): let status = result.statusCode if 200..<300 ~= status { + if let courseId = self.courseModel?.id { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.courseUploadComplete, + parameters: [GAEvent.Param.courseId: courseId] + )) + } delegate?.didUploadCourse() DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.navigationController?.popToRootViewController(animated: true) diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift index 76c3664f..fa642c9e 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseDrawing/VC/CourseDrawingVC.swift @@ -142,6 +142,7 @@ final class CourseDrawingVC: UIViewController { self.setAddTarget() self.bindMapView() self.setNavigationGesture(false) + analyze(screenName: GAEvent.View.viewCourseDrawing) } override func viewWillDisappear(_ animated: Bool) { @@ -214,6 +215,13 @@ extension CourseDrawingVC { } private func presentAlertVC(courseId: Int) { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.courseDrawingComplete, + // distance는 km 단위이므로 *1000하여 m으로 변환 (Android 동일) + parameters: [GAEvent.Param.courseId: courseId, GAEvent.Param.distanceM: Int(self.distance * 1000)] + )) + analyze(screenName: GAEvent.View.viewCourseCompleteResult) + let alertVC = CustomAlertVC() alertVC.modalPresentationStyle = .overFullScreen diff --git a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift index 0aba39b1..3d0bad35 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/CourseStorage/VC/CourseStorageVC.swift @@ -89,6 +89,11 @@ extension CourseStorageVC { viewPager.$selectedTabIndex.sink { [weak self] selectedTabIndex in guard let self = self else { return } self.deleteCourseButton.isHidden = (selectedTabIndex != 0) + if selectedTabIndex == 0 { + self.analyze(screenName: GAEvent.View.viewStorageMyDraw) + } else { + self.analyze(screenName: GAEvent.View.viewStorageScrap) + } }.store(in: cancelBag) privateCourseListView.courseDrawButtonTapped.sink { [weak self] in diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift index c98ed81c..dc1b5b57 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/ActivityRecordDetailVC.swift @@ -120,6 +120,7 @@ final class ActivityRecordDetailVC: UIViewController { self.view = view self.setKeyboardNotification() self.setTapGesture() + analyze(screenName: GAEvent.View.viewMyHistoryDetail) } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/GoalRewardInfoVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/GoalRewardInfoVC.swift index 28c72b41..6eb5b1d4 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/GoalRewardInfoVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/InfoVC/GoalRewardInfoVC.swift @@ -58,6 +58,7 @@ final class GoalRewardInfoVC: UIViewController { setDelegate() register() getGoalRewardInfo() + analyze(screenName: GAEvent.View.viewMyReward) } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift index 4d4dba8e..37e191ef 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/MyPage/VC/NicknameEditorVC.swift @@ -73,6 +73,7 @@ final class NicknameEditorVC: UIViewController { setLayout() showKeyboard() setAddTarget() + analyze(screenName: GAEvent.View.viewEditProfile) } } @@ -195,6 +196,10 @@ extension NicknameEditorVC { case .success(let result): let status = result.statusCode if 200..<300 ~= status { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.editProfileComplete, + parameters: [GAEvent.Param.changedFields: "nickname"] + )) self.delegate?.nicknameEditDidSuccess() self.navigationController?.popViewController(animated: false) } else { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/CountDownVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/CountDownVC.swift index c83fa54f..23da7de5 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/CountDownVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/CountDownVC.swift @@ -45,6 +45,13 @@ final class CountDownVC: UIViewController { setLayout() WatchSessionService.shared.sendCountdownStarted() animateTimeLabel() + + if let courseId = runningModel?.courseId { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.View.viewCountdown, + parameters: [GAEvent.Param.courseId: courseId] + )) + } } } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunTrackingVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunTrackingVC.swift index 86a7633d..6d7496bf 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunTrackingVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunTrackingVC.swift @@ -143,6 +143,13 @@ final class RunTrackingVC: UIViewController { self.stopwatch.isRunning = true self.startRunLocationTracking() self.startWatchDataSync() + + if let courseId = runningModel?.courseId { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.runStart, + parameters: [GAEvent.Param.courseId: courseId] + )) + } } deinit { @@ -270,11 +277,23 @@ extension RunTrackingVC { alertVC.modalPresentationStyle = .overFullScreen alertVC.rightButtonTapAction = { [weak self] in alertVC.dismiss(animated: false) - self?.stopwatch.isRunning = false - self?.stopRunLocationTracking() + guard let self = self else { return } + if let courseId = self.runningModel?.courseId { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.runAbandon, + parameters: [ + GAEvent.Param.courseId: courseId, + GAEvent.Param.elapsedSec: self.totalTime, + // distance는 km(String) → m(Int) 변환 (Android 동일) + GAEvent.Param.distanceM: Int((Double(self.distance) ?? 0) * 1000) + ] + )) + } + self.stopwatch.isRunning = false + self.stopRunLocationTracking() WatchSessionService.shared.stopSendingRunningData() WatchSessionService.shared.sendRunReset() - self?.navigationController?.popViewController(animated: true) + self.navigationController?.popViewController(animated: true) } self.present(alertVC, animated: false) } @@ -285,11 +304,23 @@ extension RunTrackingVC { alertVC.modalPresentationStyle = .overFullScreen alertVC.rightButtonTapAction = { [weak self] in alertVC.dismiss(animated: false) - self?.stopwatch.isRunning = false - self?.stopRunLocationTracking() + guard let self = self else { return } + if let courseId = self.runningModel?.courseId { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.runComplete, + parameters: [ + GAEvent.Param.courseId: courseId, + GAEvent.Param.totalTimeSec: self.totalTime, + // distance는 km(String) → m(Int) 변환 (Android 동일) + GAEvent.Param.totalDistanceM: Int((Double(self.distance) ?? 0) * 1000) + ] + )) + } + self.stopwatch.isRunning = false + self.stopRunLocationTracking() WatchSessionService.shared.stopSendingRunningData() WatchSessionService.shared.sendRunCompleted() - self?.pushToRunningRecordVC() + self.pushToRunningRecordVC() } self.present(alertVC, animated: false) } diff --git a/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningRecordVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningRecordVC.swift index 09014d21..966f6684 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningRecordVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/Running/VC/RunningRecordVC.swift @@ -86,6 +86,13 @@ final class RunningRecordVC: UIViewController { self.setKeyboardNotification() self.setTapGesture() self.setNaviBarBackAction() + + if let courseId = runningModel?.courseId { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.View.viewEndRun, + parameters: [GAEvent.Param.courseId: courseId] + )) + } } override func viewDidAppear(_ animated: Bool) { @@ -316,6 +323,12 @@ extension RunningRecordVC { case .success(let result): let status = result.statusCode if 200..<300 ~= status { + if let courseId = self.runningModel?.courseId { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Button.clickSaveRunRecord, + parameters: [GAEvent.Param.courseId: courseId] + )) + } analyze(buttonName: GAEvent.Button.clickStoreRunningTracking) WatchSessionService.shared.sendRunReset() self.showToastOnWindow(text: "저장한 러닝 기록은 마이페이지에서 볼 수 있어요.") diff --git a/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/NickNameSetUpVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/NickNameSetUpVC.swift index fed3c9a3..1cb13fe2 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/NickNameSetUpVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/NickNameSetUpVC.swift @@ -55,6 +55,7 @@ final class NickNameSetUpVC: UIViewController { self.setLayout() self.setDelegate() self.setAddTarget() + analyze(screenName: GAEvent.View.viewGiveNickname) } } @@ -160,6 +161,10 @@ extension NickNameSetUpVC { do { let responseDto = try result.map(BaseResponse.self) if responseDto.status == 200 { + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.nicknameComplete, + parameters: [GAEvent.Param.nicknameLength: nickname.count] + )) UserManager.shared.userType = .registered self.pushToTabBarController() } else { diff --git a/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/SignInSocialLoginVC.swift b/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/SignInSocialLoginVC.swift index 95599365..1299005f 100644 --- a/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/SignInSocialLoginVC.swift +++ b/Runnect-iOS/Runnect-iOS/Presentation/SignIn/VC/SignInSocialLoginVC.swift @@ -84,6 +84,10 @@ extension SignInSocialLoginVC { UserApi.shared.loginWithKakaoTalk {(oauthToken, error) in if let error = error { print(error) + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginFail, + parameters: [GAEvent.Param.method: "kakao", GAEvent.Param.errorCode: "LOGIN_FAIL"] // 에러 메시지 대신 안정적 상수 사용 (카디널리티 폭발 방지) + )) } else { print("카카오 톡으로 로그인 성공") self.analyze(buttonName: GAEvent.Button.clickKaKaoLogin) @@ -91,9 +95,18 @@ extension SignInSocialLoginVC { UserManager.shared.signIn(token: oauthToken.accessToken, provider: "KAKAO") { [weak self] result in switch result { case .success(let type): - type == "Signup" ? self?.pushToNickNameSetUpVC() : self?.pushToTabBarController() + let isNew = type == "Signup" + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginSuccess, + parameters: [GAEvent.Param.method: "kakao", GAEvent.Param.isNewUser: isNew ? 1 : 0] // Firebase는 Bool 미지원, Int로 변환 + )) + isNew ? self?.pushToNickNameSetUpVC() : self?.pushToTabBarController() case .failure(let error): print(error) + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginFail, + parameters: [GAEvent.Param.method: "kakao", GAEvent.Param.errorCode: "LOGIN_FAIL"] // 에러 메시지 대신 안정적 상수 사용 (카디널리티 폭발 방지) + )) self?.showNetworkFailureToast() } } @@ -104,15 +117,28 @@ extension SignInSocialLoginVC { UserApi.shared.loginWithKakaoAccount { (oauthToken, error) in if let error = error { print(error) + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginFail, + parameters: [GAEvent.Param.method: "kakao", GAEvent.Param.errorCode: "LOGIN_FAIL"] // 에러 메시지 대신 안정적 상수 사용 (카디널리티 폭발 방지) + )) } else { print("카카오 계정으로 로그인 성공") guard let oauthToken = oauthToken else { return } UserManager.shared.signIn(token: oauthToken.accessToken, provider: "KAKAO") { [weak self] result in switch result { case .success(let type): - type == "Signup" ? self?.pushToNickNameSetUpVC() : self?.pushToTabBarController() + let isNew = type == "Signup" + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginSuccess, + parameters: [GAEvent.Param.method: "kakao", GAEvent.Param.isNewUser: isNew ? 1 : 0] // Firebase는 Bool 미지원, Int로 변환 + )) + isNew ? self?.pushToNickNameSetUpVC() : self?.pushToTabBarController() case .failure(let error): print(error) + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginFail, + parameters: [GAEvent.Param.method: "kakao", GAEvent.Param.errorCode: "LOGIN_FAIL"] // 에러 메시지 대신 안정적 상수 사용 (카디널리티 폭발 방지) + )) self?.showNetworkFailureToast() } } @@ -214,19 +240,28 @@ extension SignInSocialLoginVC: ASAuthorizationControllerPresentationContextProvi print("token : \(String(describing: tokeStr))") UserManager.shared.signIn(token: tokeStr, provider: "APPLE") { [weak self] result in - + switch result { case .success(let type): self?.analyze(buttonName: GAEvent.Button.clickAppleLogin) - if type == "Signup" { + let isNew = type == "Signup" + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginSuccess, + parameters: [GAEvent.Param.method: "apple", GAEvent.Param.isNewUser: isNew ? 1 : 0] // Firebase는 Bool 미지원, Int로 변환 + )) + if isNew { self?.pushToNickNameSetUpVC() } else { self?.pushToTabBarController() } case .failure(let error): print(error) + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginFail, + parameters: [GAEvent.Param.method: "apple", GAEvent.Param.errorCode: "LOGIN_FAIL"] // 에러 메시지 대신 안정적 상수 사용 (카디널리티 폭발 방지) + )) self?.showNetworkFailureToast() - + } } default: @@ -237,5 +272,9 @@ extension SignInSocialLoginVC: ASAuthorizationControllerPresentationContextProvi /// Apple ID 연동 실패 시 func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { print("Apple Login error") + GAManager.shared.logEvent(eventType: .custom( + eventName: GAEvent.Action.loginFail, + parameters: [GAEvent.Param.method: "apple", GAEvent.Param.errorCode: "LOGIN_FAIL"] // 에러 메시지 대신 안정적 상수 사용 (카디널리티 폭발 방지) + )) } }