diff --git a/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyBuilder.swift b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyBuilder.swift new file mode 100644 index 00000000..9a89e0c4 --- /dev/null +++ b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyBuilder.swift @@ -0,0 +1,53 @@ +// +// AmplitudeEventPropertyBuilder.swift +// Core +// +// Created by sejin on 1/21/24. +// Copyright © 2024 SOPT-iOS. All rights reserved. +// + +import Foundation + +public class AmplitudeEventPropertyBuilder { + private var eventProperties = [String: Any]() + + public init() {} + + /// ViewType 은 UserType과 같다. + public func addViewType() -> Self { + let key: AmplitudeEventPropertyKey = .viewType + let userType = UserDefaultKeyList.Auth.getUserType() + let value = userType.rawValue.lowercased() + self.eventProperties[key.rawValue] = value + return self + } + + public func add(key: String, value: Any) -> Self { + self.eventProperties[key] = value + return self + } + + public func add(key: AmplitudeEventPropertyKey, value: Any) -> Self { + self.eventProperties[key.rawValue] = value + return self + } + + public func add(key: AmplitudeEventPropertyKey, value: Optional) -> Self { + self.eventProperties[key.rawValue] = value + return self + } + + public func add(key: AmplitudeEventPropertyKey, value: Value) -> Self { + self.eventProperties[key.rawValue] = value.toString() + return self + } + + public func removeOptional() -> Self { + self.eventProperties = self.eventProperties.compactMapValues { $0 } + return self + } + + public func build() -> [String: Any] { + return eventProperties + } +} diff --git a/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyKey.swift b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyKey.swift new file mode 100644 index 00000000..521a27a4 --- /dev/null +++ b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyKey.swift @@ -0,0 +1,18 @@ +// +// AmplitudeEventPropertyKey.swift +// Core +// +// Created by sejin on 1/21/24. +// Copyright © 2024 SOPT-iOS. All rights reserved. +// + +import Foundation + +public enum AmplitudeEventPropertyKey: String { + case viewType = "view_type" + case clickViewType = "click_view_type" + + // 콕 찌르기 피쳐 관련 Key + case viewProfile = "view_profile" // 멤버 프로필 id + case friendType = "friend_type" +} diff --git a/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyValueConvertible.swift b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyValueConvertible.swift new file mode 100644 index 00000000..ac227a7d --- /dev/null +++ b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventPropertyValueConvertible.swift @@ -0,0 +1,13 @@ +// +// AmplitudeEventPropertyValueConvertible.swift +// Core +// +// Created by sejin on 1/21/24. +// Copyright © 2024 SOPT-iOS. All rights reserved. +// + +import Foundation + +public protocol AmplitudeEventPropertyValueConvertible { + func toString() -> String +} diff --git a/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventType.swift b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventType.swift new file mode 100644 index 00000000..6c9fa32c --- /dev/null +++ b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeEventType.swift @@ -0,0 +1,39 @@ +// +// AmplitudeEventType.swift +// Core +// +// Created by sejin on 2023/09/21. +// Copyright © 2023 SOPT-iOS. All rights reserved. +// + +import Foundation + +public enum AmplitudeEventType: String { + // 클릭 이벤트 + case clickAlarm = "click_alarm" + case clickMyPage = "click_mypage" + case clickAttendacne = "click_attendance" + case clickGroup = "click_group" + case clickProject = "click_project" + case clickMember = "click_member" + case clickOfficialHomepage = "click_homepage" + case clickSoptamp = "click_soptamp" + case clickInstagram = "click_instagram" + case clickYoutube = "click_youtube" + case clickReview = "click_review" + case clickFaq = "click_faq" + case clickPlaygroundCommunity = "click_playground_community" + case clickPoke = "click_poke" + case clickMemberProfile = "click_memberprofile" + case clickPokeIcon = "click_poke_icon" + case clickPokeAlarmDetail = "click_poke_alarm_detail" + case clickPokeQuit = "click_poke_quit" + + // 뷰 이벤트 + case viewAppHome = "view_apphome" + case viewPokeOnboarding = "view_poke_onboarding" + case viewPokeMain = "view_poke_main" + case viewPokeAlarmDetail = "view_poke_alarm_detail" + case viewPokeFriend = "view_poke_friend" + case viewPokeFriendDetail = "view_poke_friend_detail" +} diff --git a/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeInstance.swift b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeInstance.swift index 6663be8e..2cb77eae 100644 --- a/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeInstance.swift +++ b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeInstance.swift @@ -16,24 +16,25 @@ public struct AmplitudeInstance { } public extension Amplitude { - func track(event: AmplitudeEventType, userType: UserType, otherProperties: [String: Any]? = nil) { - let eventType: String = event.rawValue - var eventProperties: [String: Any] = ["view_type": userType.rawValue.lowercased()] - - if let otherProperties = otherProperties { - for (key, value) in otherProperties { - eventProperties.updateValue(value, forKey: key) - } - } + func track(eventType: AmplitudeEventType, eventProperties: [String: Any]? = nil) { + let eventType: String = eventType.rawValue AmplitudeInstance.shared.track(eventType: eventType, eventProperties: eventProperties, options: nil) } - func trackWithUserType(event: AmplitudeEventType) { + func trackWithUserType(event: AmplitudeEventType, otherProperties: [String: Any]? = nil) { let eventType: String = event.rawValue let userType = UserDefaultKeyList.Auth.getUserType() - var eventProperties: [String: Any] = ["view_type": userType.rawValue.lowercased()] + let eventProperties: [String: Any] = [AmplitudeEventPropertyKey.viewType.rawValue: userType.rawValue.lowercased()] AmplitudeInstance.shared.track(eventType: eventType, eventProperties: eventProperties, options: nil) } + + func addPushNotificationAuthorizationIdentity(isAuthorized: Bool) { + let identify = Identify() + let key: AmplitudeUserPropertyKey = .stateOfPushNotification + identify.set(property: key.rawValue, value: isAuthorized) + + AmplitudeInstance.shared.identify(identify: identify) + } } diff --git a/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeUserPropertyKey.swift b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeUserPropertyKey.swift new file mode 100644 index 00000000..dc8edb3f --- /dev/null +++ b/SOPT-iOS/Projects/Core/Sources/Amplitude/AmplitudeUserPropertyKey.swift @@ -0,0 +1,13 @@ +// +// AmplitudeUserPropertyKey.swift +// Core +// +// Created by sejin on 1/21/24. +// Copyright © 2024 SOPT-iOS. All rights reserved. +// + +import Foundation + +public enum AmplitudeUserPropertyKey: String { + case stateOfPushNotification = "state_of_push_notification" +} diff --git a/SOPT-iOS/Projects/Core/Sources/Enum/AmplitudeEventType.swift b/SOPT-iOS/Projects/Core/Sources/Enum/AmplitudeEventType.swift deleted file mode 100644 index 807844d6..00000000 --- a/SOPT-iOS/Projects/Core/Sources/Enum/AmplitudeEventType.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// AmplitudeEventType.swift -// Core -// -// Created by sejin on 2023/09/21. -// Copyright © 2023 SOPT-iOS. All rights reserved. -// - -import Foundation - -public enum AmplitudeEventType: String { - // 서비스 클릭 이벤트 - case main = "view_apphome" - case alarm = "click_alarm" - case myPage = "click_mypage" - case attendacne = "click_attendance" - case group = "click_group" - case project = "click_project" - case member = "click_member" - case officialHomepage = "click_homepage" - case soptamp = "click_soptamp" - case instagram = "click_instagram" - case youtube = "click_youtube" - case review = "click_review" - case faq = "click_faq" - case playgroundCommunity = "click_playground_community" - case poke = "click_poke" -} diff --git a/SOPT-iOS/Projects/Core/Sources/Enum/ServiceTypeTransform.swift b/SOPT-iOS/Projects/Core/Sources/Enum/ServiceTypeTransform.swift index d8885d12..c96efe1f 100644 --- a/SOPT-iOS/Projects/Core/Sources/Enum/ServiceTypeTransform.swift +++ b/SOPT-iOS/Projects/Core/Sources/Enum/ServiceTypeTransform.swift @@ -11,16 +11,16 @@ import Foundation public extension ServiceType { var toAmplitudeEventType: AmplitudeEventType { switch self { - case .officialHomepage: return .officialHomepage - case .review: return .review - case .project: return .project - case .faq: return .faq - case .youtube: return .youtube - case .attendance: return .attendacne - case .member: return .member - case .group: return .group - case .instagram: return .instagram - case .playgroundCommunity: return .playgroundCommunity + case .officialHomepage: return .clickOfficialHomepage + case .review: return .clickReview + case .project: return .clickProject + case .faq: return .clickFaq + case .youtube: return .clickYoutube + case .attendance: return .clickAttendacne + case .member: return .clickMember + case .group: return .clickGroup + case .instagram: return .clickInstagram + case .playgroundCommunity: return .clickPlaygroundCommunity } } } @@ -28,8 +28,8 @@ public extension ServiceType { public extension AppServiceType { var toAmplitudeEventType: AmplitudeEventType { switch self { - case .soptamp: return .soptamp - case .poke: return .poke + case .soptamp: return .clickSoptamp + case .poke: return .clickPoke } } } diff --git a/SOPT-iOS/Projects/Core/Sources/Utils/trackAmplitudeEvent.swift b/SOPT-iOS/Projects/Core/Sources/Utils/trackAmplitudeEvent.swift deleted file mode 100644 index b00e3a83..00000000 --- a/SOPT-iOS/Projects/Core/Sources/Utils/trackAmplitudeEvent.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// trackAmplitudeEvent.swift -// Core -// -// Created by sejin on 2023/09/21. -// Copyright © 2023 SOPT-iOS. All rights reserved. -// - -import UIKit - -public extension UIViewController { - func track(event: AmplitudeEventType, otherProperties: [String: Any]? = nil) { - let userType = UserDefaultKeyList.Auth.getUserType() - AmplitudeInstance.shared.track(event: event, userType: userType, otherProperties: otherProperties) - } -} diff --git a/SOPT-iOS/Projects/Demo/Sources/AppLifecycleAdapter/AppLifecycleAdapter.swift b/SOPT-iOS/Projects/Demo/Sources/AppLifecycleAdapter/AppLifecycleAdapter.swift index e70f6201..a705ee42 100644 --- a/SOPT-iOS/Projects/Demo/Sources/AppLifecycleAdapter/AppLifecycleAdapter.swift +++ b/SOPT-iOS/Projects/Demo/Sources/AppLifecycleAdapter/AppLifecycleAdapter.swift @@ -31,6 +31,7 @@ extension AppLifecycleAdapter { .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] _ in self?.reissureTokens() + self?.checkNotificationSetting() }).store(in: self.cancelBag) } @@ -44,4 +45,11 @@ extension AppLifecycleAdapter { self.authService.reissuance { _ in } } + + private func checkNotificationSetting() { + UNUserNotificationCenter.current().getNotificationSettings { setting in + let isNotificationAuthorized = setting.authorizationStatus == .authorized + AmplitudeInstance.shared.addPushNotificationAuthorizationIdentity(isAuthorized: isNotificationAuthorized) + } + } } diff --git a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift index 7aa161e8..882d051e 100644 --- a/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift +++ b/SOPT-iOS/Projects/Features/MainFeature/Sources/MainScene/ViewModel/MainViewModel.swift @@ -78,14 +78,14 @@ extension MainViewModel { .sink { [weak self] _ in guard let self = self else { return } self.onNoticeButtonTap?() - self.trackAmplitude(event: .alarm) + self.trackAmplitude(event: .clickAlarm) }.store(in: cancelBag) input.myPageButtonTapped .sink { [weak self] _ in guard let self = self else { return } self.onMyPageButtonTap?(self.userType) - self.trackAmplitude(event: .myPage) + self.trackAmplitude(event: .clickMyPage) }.store(in: cancelBag) input.cellTapped @@ -147,7 +147,7 @@ extension MainViewModel { guard let self = self else { return } self.requestAuthorizationForNotification() self.useCase.getServiceState() - self.trackAmplitude(event: .main) + self.trackAmplitude(event: .viewAppHome) }.store(in: cancelBag) return output @@ -235,10 +235,8 @@ extension MainViewModel { // APNS 권한 허용 확인 let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in - if let error = error { - print(error) - } - + if let error = error { print(error) } + AmplitudeInstance.shared.addPushNotificationAuthorizationIdentity(isAuthorized: granted) print("APNs-알림 권한 허용 유무 \(granted)") if granted { diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Interface/Sources/Enum/PokeRelation.swift b/SOPT-iOS/Projects/Features/PokeFeature/Interface/Sources/Enum/PokeRelation.swift index 6690a753..c202e649 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Interface/Sources/Enum/PokeRelation.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Interface/Sources/Enum/PokeRelation.swift @@ -15,4 +15,17 @@ public enum PokeRelation: String { case soulmate = "천생연분" } - +extension PokeRelation { + public var toEnglishName: String { + switch self { + case .nonFriend: + return "nonFriend" + case .newFriend: + return "newFriend" + case .bestFriend: + return "bestFriend" + case .soulmate: + return "soulmate" + } + } +} diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/Amplitude/PokeAmplitudeEventPropertyValue.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Amplitude/PokeAmplitudeEventPropertyValue.swift new file mode 100644 index 00000000..dff0fb96 --- /dev/null +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Amplitude/PokeAmplitudeEventPropertyValue.swift @@ -0,0 +1,24 @@ +// +// PokeAmplitudeEventPropertyValue.swift +// PokeFeature +// +// Created by sejin on 1/21/24. +// Copyright © 2024 SOPT-iOS. All rights reserved. +// + +import Foundation +import Core + +enum PokeAmplitudeEventPropertyValue: String, AmplitudeEventPropertyValueConvertible { + case onboarding = "onboarding" + case pokeMainAlarm = "poke_main_alarm" + case pokeMainFriend = "poke_main_friend" + case pokeMainRecommendNotMyFriend = "poke_main_recommend_notmyfriend" + case pokeMainRecommendMyFriend = "poke_main_recommend_myfriend" + case pokeAlarm = "poke_alarm" + case friend = "friend" + + func toString() -> String { + self.rawValue + } +} diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/Amplitude/PokeEventTracker.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Amplitude/PokeEventTracker.swift new file mode 100644 index 00000000..0557f6a2 --- /dev/null +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/Amplitude/PokeEventTracker.swift @@ -0,0 +1,48 @@ +// +// PokeEventTracker.swift +// PokeFeature +// +// Created by sejin on 1/21/24. +// Copyright © 2024 SOPT-iOS. All rights reserved. +// + +import Foundation +import Core +import PokeFeatureInterface + +struct PokeEventTracker { + func trackViewEvent(with viewEvent: AmplitudeEventType) { + AmplitudeInstance.shared.trackWithUserType(event: viewEvent) + } + + func trackViewFriendsListEvent(friendType: PokeRelation) { + let properties = AmplitudeEventPropertyBuilder() + .addViewType() + .add(key: .friendType, value: friendType.toEnglishName) + .build() + + AmplitudeInstance.shared.track(eventType: .viewPokeFriendDetail, eventProperties: properties) + } + + func trackClickPokeEvent(clickView: PokeAmplitudeEventPropertyValue, playgroundId: Int? = nil) { + let properties = AmplitudeEventPropertyBuilder() + .addViewType() + .add(key: .clickViewType, value: clickView) + .add(key: .viewProfile, value: playgroundId) + .removeOptional() + .build() + + AmplitudeInstance.shared.track(eventType: .clickPokeIcon, eventProperties: properties) + } + + func trackClickMemberProfileEvent(clickView: PokeAmplitudeEventPropertyValue, playgroundId: Int? = nil) { + let properties = AmplitudeEventPropertyBuilder() + .addViewType() + .add(key: .clickViewType, value: clickView) + .add(key: .viewProfile, value: playgroundId) + .removeOptional() + .build() + + AmplitudeInstance.shared.track(eventType: .clickMemberProfile, eventProperties: properties) + } +} diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift index 8192a08a..0ce54f3c 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/VC/PokeMainVC.swift @@ -211,10 +211,16 @@ extension PokeMainVC: UIGestureRecognizerDelegate { extension PokeMainVC { private func bindViewModel() { - let profileImageTap = Publishers.Merge4(pokedUserContentView.profileImageTap, - friendSectionContentView.profileImageTap, - firstProfileCardGroupView.profileImageTap, - secondProfileCardGroupView.profileImageTap).asDriver() + let profileImageTap = Publishers.Merge4( + pokedUserContentView.profileImageTap + .map { ($0, PokeAmplitudeEventPropertyValue.pokeMainAlarm) }, + friendSectionContentView.profileImageTap + .map { ($0, PokeAmplitudeEventPropertyValue.pokeMainFriend) }, + firstProfileCardGroupView.profileImageTap + .map { ($0, PokeAmplitudeEventPropertyValue.pokeMainRecommendNotMyFriend) }, + secondProfileCardGroupView.profileImageTap + .map { ($0, PokeAmplitudeEventPropertyValue.pokeMainRecommendNotMyFriend) }) + .asDriver() let randomUserSectionFriendProfileImageTap = Publishers.Merge( firstProfileCardGroupView.friendProfileImageTap, diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/ViewModel/PokeMainViewModel.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/ViewModel/PokeMainViewModel.swift index 8519179f..deb339e1 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/ViewModel/PokeMainViewModel.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMainScene/ViewModel/PokeMainViewModel.swift @@ -33,6 +33,7 @@ public class PokeMainViewModel: private let useCase: PokeMainUseCase private let isRouteFromRoot: Bool private var cancelBag = CancelBag() + private let eventTracker = PokeEventTracker() // MARK: - Inputs @@ -45,7 +46,7 @@ public class PokeMainViewModel: let friendSectionKokButtonTap: Driver let nearbyFriendsSectionKokButtonTap: Driver let refreshRequest: Driver - let profileImageTap: Driver + let profileImageTap: Driver<(PokeUserModel?, PokeAmplitudeEventPropertyValue)> let randomUserSectionFriendProfileImageTap: Driver } @@ -93,13 +94,20 @@ extension PokeMainViewModel { self?.useCase.checkPokeNewUser() }.store(in: cancelBag) + input.viewDidLoad + .sink { [weak self] _ in + self?.eventTracker.trackViewEvent(with: .viewPokeMain) + }.store(in: cancelBag) + input.naviBackButtonTap .sink { [weak self] _ in + self?.eventTracker.trackViewEvent(with: .clickPokeQuit) self?.onNaviBackTap?() }.store(in: cancelBag) input.pokedSectionHeaderButtonTap .sink { [weak self] _ in + self?.eventTracker.trackViewEvent(with: .clickPokeAlarmDetail) self?.onPokeNotificationsTap?() }.store(in: cancelBag) @@ -116,6 +124,7 @@ extension PokeMainViewModel { return value } .sink { [weak self] userModel, messageModel in + self?.eventTracker.trackClickPokeEvent(clickView: .pokeMainAlarm) self?.useCase.poke(userId: userModel.userId, message: messageModel, willBeNewFriend: userModel.isFirstMeet) }.store(in: cancelBag) @@ -132,17 +141,35 @@ extension PokeMainViewModel { }.store(in: cancelBag) input.profileImageTap - .compactMap { $0 } + .compactMap { $0.0 } .sink { [weak self] user in self?.onProfileImageTapped?(user.playgroundId) }.store(in: cancelBag) + input.profileImageTap + .map { $0.1 } + .sink { [weak self] clickView in + self?.eventTracker.trackClickMemberProfileEvent(clickView: clickView) + }.store(in: cancelBag) + input.randomUserSectionFriendProfileImageTap .compactMap { $0 } .sink { [weak self] playgroundId in + self?.eventTracker.trackClickMemberProfileEvent(clickView: .pokeMainRecommendMyFriend) self?.onProfileImageTapped?(playgroundId) }.store(in: cancelBag) + // Amplitude 트래킹 + input.friendSectionKokButtonTap + .sink { [weak self] _ in + self?.eventTracker.trackClickPokeEvent(clickView: .pokeMainFriend) + }.store(in: cancelBag) + + input.nearbyFriendsSectionKokButtonTap + .sink { [weak self] _ in + self?.eventTracker.trackClickPokeEvent(clickView: .pokeMainRecommendNotMyFriend) + }.store(in: cancelBag) + return output } diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/ViewModel/PokeMyFriendsListViewModel.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/ViewModel/PokeMyFriendsListViewModel.swift index 81a6afd1..af1a855e 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/ViewModel/PokeMyFriendsListViewModel.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsListScene/ViewModel/PokeMyFriendsListViewModel.swift @@ -26,6 +26,7 @@ public class PokeMyFriendsListViewModel: private let useCase: PokeMyFriendsUseCase private var cancelBag = CancelBag() + private let eventTracker = PokeEventTracker() public let relation: PokeRelation var friends = [PokeUserModel]() @@ -64,6 +65,12 @@ extension PokeMyFriendsListViewModel { let output = Output() self.bindOutput(output: output, cancelBag: cancelBag) + input.viewDidLoad + .sink { [weak self] _ in + guard let self = self else { return } + self.eventTracker.trackViewFriendsListEvent(friendType: self.relation) + }.store(in: cancelBag) + input.viewDidLoad .merge(with: input.reachToBottom) .withUnretained(self) diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsScene/ViewModel/PokeMyFriendsViewModel.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsScene/ViewModel/PokeMyFriendsViewModel.swift index ccb08bf2..fcbbfa5b 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsScene/ViewModel/PokeMyFriendsViewModel.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeMyFriendsScene/ViewModel/PokeMyFriendsViewModel.swift @@ -27,6 +27,7 @@ public class PokeMyFriendsViewModel: private let useCase: PokeMyFriendsUseCase private var cancelBag = CancelBag() private var myFriends: PokeMyFriendsModel? + private let eventTracker = PokeEventTracker() // MARK: - Inputs @@ -58,6 +59,7 @@ extension PokeMyFriendsViewModel { input.viewDidLoad .withUnretained(self) .sink { owner, _ in + owner.eventTracker.trackViewEvent(with: .viewPokeFriend) owner.useCase.getFriends() }.store(in: cancelBag) @@ -74,12 +76,14 @@ extension PokeMyFriendsViewModel { return value } .sink {[weak self] userModel, messageModel in + self?.eventTracker.trackClickPokeEvent(clickView: .friend, playgroundId: userModel.playgroundId) self?.useCase.poke(userId: userModel.userId, message: messageModel) }.store(in: cancelBag) input.profileImageTap .compactMap { $0 } .sink { [weak self] user in + self?.eventTracker.trackClickMemberProfileEvent(clickView: .friend, playgroundId: user.playgroundId) self?.onProfileImageTapped?(user.playgroundId) }.store(in: cancelBag) diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeNotificationScene/ViewModel/PokeNotificationViewModel.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeNotificationScene/ViewModel/PokeNotificationViewModel.swift index 814d3243..e299d674 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeNotificationScene/ViewModel/PokeNotificationViewModel.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeNotificationScene/ViewModel/PokeNotificationViewModel.swift @@ -32,6 +32,7 @@ public final class PokeNotificationViewModel: PokeNotificationViewModelType { private let usecase: PokeNotificationUsecase private let cancelBag = CancelBag() + private let eventTracker = PokeEventTracker() init(usecase: PokeNotificationUsecase) { self.usecase = usecase @@ -43,6 +44,11 @@ extension PokeNotificationViewModel { let output = Output() self.bindOutput(output: output, cancelBag: cancelBag) + input.viewDidLoaded + .sink { [weak self] _ in + self?.eventTracker.trackViewEvent(with: .viewPokeAlarmDetail) + }.store(in: cancelBag) + input.viewDidLoaded .merge(with: input.reachToBottom) .sink(receiveValue: { [weak self] _ in @@ -56,6 +62,7 @@ extension PokeNotificationViewModel { return value } .sink(receiveValue: { [weak self] userModel, messageModel in + self?.eventTracker.trackClickPokeEvent(clickView: .pokeAlarm) self?.usecase.poke(user: userModel, message: messageModel) }).store(in: cancelBag) diff --git a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeOnboardingScene/ViewModel/PokeOnboardingViewModel.swift b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeOnboardingScene/ViewModel/PokeOnboardingViewModel.swift index 75b79b53..b4a52e5c 100644 --- a/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeOnboardingScene/ViewModel/PokeOnboardingViewModel.swift +++ b/SOPT-iOS/Projects/Features/PokeFeature/Sources/PokeOnboardingScene/ViewModel/PokeOnboardingViewModel.swift @@ -35,6 +35,8 @@ public final class PokeOnboardingViewModel: PokeOnboardingViewModelType { // MARK: Privates private let usecase: PokeOnboardingUsecase + private let eventTracker = PokeEventTracker() + // MARK: - Lifecycles public init(usecase: PokeOnboardingUsecase) { @@ -50,6 +52,7 @@ extension PokeOnboardingViewModel { input.viewDidLoaded .withUnretained(self) .sink(receiveValue: { [weak self] _ in + AmplitudeInstance.shared.trackWithUserType(event: .viewPokeOnboarding) self?.usecase.getRandomAcquaintances() }).store(in: cancelBag) @@ -68,11 +71,13 @@ extension PokeOnboardingViewModel { return value } .sink(receiveValue: { [weak self] userModel, messageModel in + self?.eventTracker.trackClickPokeEvent(clickView: .onboarding, playgroundId: userModel.playgroundId) self?.usecase.poke(userId: userModel.userId, message: messageModel) }).store(in: cancelBag) input.avatarTapped .sink(receiveValue: { [weak self] userModel in + self?.eventTracker.trackClickMemberProfileEvent(clickView: .onboarding, playgroundId: userModel.playgroundId) self?.onAvartarTapped?(String(describing: userModel.playgroundId)) }).store(in: cancelBag) diff --git a/SOPT-iOS/Tuist/Dependencies.swift b/SOPT-iOS/Tuist/Dependencies.swift index fc5caba4..1213d1a4 100644 --- a/SOPT-iOS/Tuist/Dependencies.swift +++ b/SOPT-iOS/Tuist/Dependencies.swift @@ -20,7 +20,7 @@ let spm = SwiftPackageManagerDependencies([ .remote(url: "https://github.com/Quick/Quick.git", requirement: .upToNextMajor(from: "5.0.0")), .remote(url: "https://github.com/Quick/Nimble.git", requirement: .upToNextMajor(from: "10.0.0")), .remote(url: "https://github.com/airbnb/lottie-ios", requirement: .upToNextMajor(from: "4.1.3")), - .remote(url: "https://github.com/amplitude/Amplitude-Swift", requirement: .upToNextMajor(from: "0.5.0")) + .remote(url: "https://github.com/amplitude/Amplitude-Swift", requirement: .upToNextMajor(from: "1.0.0")) ], baseSettings: Settings.settings( configurations: XCConfig.framework ))