Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion EATSSU/App/Sources/Data/Firebase/HomeAnalyticsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ final class HomeAnalyticsManager {
TextLiteral.Restaurant.studentRestaurant: "haksik",
TextLiteral.Restaurant.dodamRestaurant: "dodam",
TextLiteral.Restaurant.dormitoryRestaurant: "dormitory",
TextLiteral.Restaurant.facultyRestaurant: "faculty"
TextLiteral.Restaurant.facultyRestaurant: "faculty",
TextLiteral.Restaurant.snackCorner: "snack_corner"
]

// 식사 유형(한글) -> 영문 소문자 파라미터로 변환
Expand Down
60 changes: 60 additions & 0 deletions EATSSU/App/Sources/Data/Firebase/MyPageAnalyticsManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// MyPageAnalyticsManager.swift
// EATSSU
//
// Created by 황상환 on 2026/05/23.
//

import Foundation

/// 마이페이지에서 발생하는 주요 이벤트를 Firebase Analytics에 로깅하는 담당자
final class MyPageAnalyticsManager {

// MARK: - Singleton

static let shared = MyPageAnalyticsManager()
private init() {}

// MARK: - Event & Parameter Keys

private enum Event {
static let clickMyPageMenu = "click_mypage_menu"
}

private enum Parameter {
static let menu = "menu"
static let college = "college"
static let major = "major"
}

/// click_mypage_menu 이벤트의 menu 파라미터 값
enum Menu: String {
case notificationSetting = "notification_setting"
case myInfo = "my_info"
case myReview = "my_review"
case inquiry = "inquiry"
case termsOfUse = "terms_of_use"
case privacyPolicy = "privacy_policy"
case creator = "creator"
case logout = "logout"
case withdraw = "withdraw"
}

// MARK: - Logging Methods

/// 마이페이지의 메뉴 항목을 클릭했을 때 호출
/// - Parameter menu: 클릭한 메뉴 종류
func logClickMyPageMenu(menu: Menu) {
var parameters: [String: Any] = [Parameter.menu: menu.rawValue]

let userInfo = UserInfoManager.shared.getCurrentUserInfo()
if let collegeId = userInfo?.collegeId {
parameters[Parameter.college] = collegeId
}
if let departmentId = userInfo?.departmentId {
parameters[Parameter.major] = departmentId
}

AnalyticsService.logEvent(Event.clickMyPageMenu, parameters: parameters)
}
}
40 changes: 31 additions & 9 deletions EATSSU/App/Sources/Data/Firebase/ReviewAnalyticsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,53 @@ final class ReviewAnalyticsManager {
}

private enum Parameter {
static let restaurants = "restaurants"
static let photoAttached = "photo_attached"
static let rating = "rating"
static let likes = "likes"
}

// 식당 이름(한글) -> 영문 소문자 파라미터로 변환
private let restaurantNameMap: [String: String] = [
TextLiteral.Restaurant.studentRestaurant: "haksik",
TextLiteral.Restaurant.dodamRestaurant: "dodam",
TextLiteral.Restaurant.dormitoryRestaurant: "dormitory",
TextLiteral.Restaurant.facultyRestaurant: "faculty",
TextLiteral.Restaurant.snackCorner: "snack_corner"
]

// MARK: - Logging Methods

/**
#1 '리뷰 작성하기' 버튼을 클릭했을 때 호출
- Parameter restaurantName: 리뷰를 작성할 메뉴가 속한 식당 이름 (예: "학생 식당")
*/
func logWriteReviewV1() {
AnalyticsService.logEvent(Event.writeReview)
func logWriteReviewV2(restaurantName: String?) {
AnalyticsService.logEvent(Event.writeReview, parameters: makeRestaurantsParameter(restaurantName))
}

/**
#2 리뷰 작성을 마치고 '완료하기' 버튼을 클릭했을 때 호출
- Parameter restaurantName: 리뷰를 작성한 메뉴가 속한 식당 이름 (예: "학생 식당")
- Parameter photoAttached: 사진 첨부 여부 (0: 없음, 1: 있음)
- Parameter rating: 사용자가 부여한 메인 별점 (1~5)
- Parameter selection: 사용자가 한 번에 리뷰를 작성하는 메뉴의 총 개수
- Parameter likes: 사용자가 한 번에 리뷰를 작성하는 메뉴의 총 개수
*/
func logCompleteReviewV1(photoAttached: Int, rating: Int, likes: Int) {
AnalyticsService.logEvent(Event.completeReview, parameters: [
Parameter.photoAttached: photoAttached,
Parameter.rating: rating,
Parameter.likes: likes
])
func logCompleteReviewV2(restaurantName: String?, photoAttached: Int, rating: Int, likes: Int) {
var parameters: [String: Any] = makeRestaurantsParameter(restaurantName) ?? [:]
parameters[Parameter.photoAttached] = photoAttached
parameters[Parameter.rating] = rating
parameters[Parameter.likes] = likes
AnalyticsService.logEvent(Event.completeReview, parameters: parameters)
}

// MARK: - Helpers

private func makeRestaurantsParameter(_ restaurantName: String?) -> [String: Any]? {
guard let restaurantName,
let value = restaurantNameMap[restaurantName] else {
return nil
}
return [Parameter.restaurants: value]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ final class WidgetAnalyticsManager {
"도담식당": "dodam",
"기숙사 식당": "dormitory",
"FACULTY (교직원 전용)": "faculty",
"스낵 코너": "snack_corner",
]

private init() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ struct ReviewMenuTypeInfo {
var menuType: String
var menuID: Int
var changeMenuIDList: [Int]?
var restaurantName: String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ extension HomeRestaurantViewController: UITableViewDataSource {

var reviewMenuTypeInfo = ReviewMenuTypeInfo(menuType: "", menuID: 0)

if section < sectionHeaderRestaurant.count {
reviewMenuTypeInfo.restaurantName = sectionHeaderRestaurant[section]
}

if !isSnackCorner {
guard let menus = changeMenuTableViewData[restaurant],
menuIndex < menus.count else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ final class MyPageViewController: BaseViewController {

@objc
private func userWithdrawButtonTapped() {
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "withdraw"])
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .withdraw)
let userWithdrawViewController = UserWithdrawViewController(nickName: nickName)
navigationController?.pushViewController(userWithdrawViewController, animated: true)
}
Expand Down Expand Up @@ -189,25 +189,25 @@ extension MyPageViewController: UITableViewDelegate {
switch indexPath.row {
// "푸시 알림 설정" 스위치 토글
case MyPageLabels.NotificationSetting.rawValue:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "notification_setting"])
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .notificationSetting)
handleNotificationSettingToggle(at: indexPath)

// "내 정보" 스크린으로 이동
case MyPageLabels.MyInfo.rawValue:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "my_info"])
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .myInfo)
let setNickNameVC = SetNickNameViewController()
setNickNameVC.source = .signup
navigationController?.pushViewController(setNickNameVC, animated: true)

// "내 리뷰" 스크린으로 이동
case MyPageLabels.MyReview.rawValue:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "my_review"])
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .myReview)
let myReviewViewController = MyReviewViewController(nickname: nickName)
navigationController?.pushViewController(myReviewViewController, animated: true)

// "문의하기" 스크린으로 이동
case MyPageLabels.Inquiry.rawValue:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "inquiry"])
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .inquiry)
TalkApi.shared.chatChannel(channelPublicId: TextLiteral.KakaoChannel.id) { [weak self] error in
if error != nil {
if let kakaoChannelLink = URL(string: "http://pf.kakao.com/\(TextLiteral.KakaoChannel.id)") {
Expand All @@ -226,21 +226,21 @@ extension MyPageViewController: UITableViewDelegate {

// "서비스 이용약관" 스크린으로 이동
case MyPageLabels.TermsOfUse.rawValue:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "terms_of_use"])
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .termsOfUse)
let provisionViewController = ProvisionViewController(agreementType: .termsOfService)
provisionViewController.navigationTitle = TextLiteral.MyPage.termsOfUse
navigationController?.pushViewController(provisionViewController, animated: true)

// "개인정보 이용약관" 스크린으로 이동
case MyPageLabels.PrivacyTermsOfUse.rawValue:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "privacy_policy"])
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .privacyPolicy)
let provisionViewController = ProvisionViewController(agreementType: .privacyPolicy)
provisionViewController.navigationTitle = TextLiteral.MyPage.privacyTermsOfUse
navigationController?.pushViewController(provisionViewController, animated: true)

// "만든사람들" 노션 페이지로 이동 (앱 내 웹뷰)
case MyPageLabels.Creator.rawValue:
AnalyticsService.logEvent("click_mypage_menu", parameters: ["menu": "creator"])
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .creator)
if let creatorsURL = URL(string: URLConstants.creatorsNotion) {
let creatorsWebVC = ProvisionViewController(url: creatorsURL)
creatorsWebVC.navigationTitle = TextLiteral.MyPage.creators
Expand All @@ -249,6 +249,7 @@ extension MyPageViewController: UITableViewDelegate {

// "로그아웃" 팝업알림 표시
case MyPageLabels.Logout.rawValue:
MyPageAnalyticsManager.shared.logClickMyPageMenu(menu: .logout)
logoutShowAlert()

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ final class ReviewViewController: BaseViewController {

/// 메뉴 ID (FIXED 타입) 또는 식사 ID (VARIABLE 타입)
var menuID: Int = 0

/// 메뉴 타입 ("FIXED" 또는 "VARIABLE")
var type = "VARIABLE"

/// 리뷰 작성 대상 메뉴가 속한 식당 이름 (예: "학생 식당")
private var restaurantName: String?

/// 메뉴 이름 리스트
private var menuNameList: [String] = []
Expand Down Expand Up @@ -201,18 +204,20 @@ final class ReviewViewController: BaseViewController {

/// 리뷰 작성 버튼 탭 처리
@objc private func handleAddReviewButtonTap() {
ReviewAnalyticsManager.shared.logWriteReviewV1()
ReviewAnalyticsManager.shared.logWriteReviewV2(restaurantName: restaurantName)

if type == "VARIABLE" {
let reviewVC = SetRateViewController(mealId: menuID)
reviewVC.restaurantName = restaurantName
Comment thread
Hrepay marked this conversation as resolved.
reviewVC.dataBind(
list: validMenusForReview.map { $0.name },
idList: validMenusForReview.map { $0.menuId }
)
navigationController?.pushViewController(reviewVC, animated: true)

} else {
let reviewVC = SetRateViewController(menuId: menuID)
reviewVC.restaurantName = restaurantName
reviewVC.dataBind(
list: menuNameList,
idList: menuIDList ?? []
Expand Down Expand Up @@ -294,10 +299,12 @@ final class ReviewViewController: BaseViewController {
/// 리뷰 작성 버튼 탭 처리 (로그인 체크 포함)
func userTapReviewButton() {
if RealmService.shared.isAccessTokenPresent() {
ReviewAnalyticsManager.shared.logWriteReviewV2(restaurantName: restaurantName)
DispatchQueue.global().async {
DispatchQueue.main.async { [self] in
if type == "FIXED" {
let setRateViewController = SetRateViewController(menuId: menuID)
setRateViewController.restaurantName = restaurantName
setRateViewController.dataBind(
list: menuNameList,
idList: menuIDList ?? []
Expand All @@ -308,6 +315,7 @@ final class ReviewViewController: BaseViewController {
)
} else {
let setRateViewController = SetRateViewController(mealId: menuID)
setRateViewController.restaurantName = restaurantName
setRateViewController.dataBind(
list: validMenusForReview.map { $0.name },
idList: validMenusForReview.map { $0.menuId }
Expand Down Expand Up @@ -715,14 +723,9 @@ extension ReviewViewController: ReviewMenuTypeInfoDelegate {

/// 메뉴 타입 정보 델리게이트
func didDelegateReviewMenuTypeInfo(for menuTypeData: ReviewMenuTypeInfo) {
let reviewMenuTypeInfo = ReviewMenuTypeInfo(
menuType: menuTypeData.menuType,
menuID: menuTypeData.menuID,
changeMenuIDList: menuTypeData.changeMenuIDList
)

type = reviewMenuTypeInfo.menuType
menuID = reviewMenuTypeInfo.menuID
menuIDList = reviewMenuTypeInfo.changeMenuIDList
type = menuTypeData.menuType
menuID = menuTypeData.menuID
menuIDList = menuTypeData.changeMenuIDList
restaurantName = menuTypeData.restaurantName
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ final class SetRateViewController: BaseViewController, UINavigationControllerDel
private var userPickedImage: UIImage?

private var isReviewSubmitted = false

/// 리뷰를 작성 중인 메뉴가 속한 식당 이름 (예: "학생 식당")
var restaurantName: String?


enum ReviewType {
Expand Down Expand Up @@ -425,7 +428,8 @@ extension SetRateViewController {
await MainActor.run {
self.isReviewSubmitted = true
let hasPhoto = self.userPickedImage != nil || self.setRateView.userReviewImageView.image != nil
ReviewAnalyticsManager.shared.logCompleteReviewV1(
ReviewAnalyticsManager.shared.logCompleteReviewV2(
restaurantName: self.restaurantName,
photoAttached: hasPhoto ? 1 : 0,
rating: self.setRateView.rateView.currentStar,
likes: self.likedStates.filter { $0 }.count
Expand Down Expand Up @@ -476,7 +480,8 @@ extension SetRateViewController {

await MainActor.run {
self.isReviewSubmitted = true
ReviewAnalyticsManager.shared.logCompleteReviewV1(
ReviewAnalyticsManager.shared.logCompleteReviewV2(
restaurantName: self.restaurantName,
photoAttached: imageUrl != nil ? 1 : 0,
rating: self.setRateView.rateView.currentStar,
likes: self.likedStates.filter { $0 }.count
Expand Down Expand Up @@ -528,7 +533,8 @@ extension SetRateViewController {

await MainActor.run {
self.isReviewSubmitted = true
ReviewAnalyticsManager.shared.logCompleteReviewV1(
ReviewAnalyticsManager.shared.logCompleteReviewV2(
restaurantName: self.restaurantName,
photoAttached: imageUrl != nil ? 1 : 0,
rating: self.setRateView.rateView.currentStar,
likes: self.likedStates.filter { $0 }.count
Expand Down
3 changes: 2 additions & 1 deletion EATSSU/App/Sources/Utility/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD

let config = PostHogConfig(apiKey: apiKey, host: "https://us.i.posthog.com")
config.captureApplicationLifecycleEvents = true
config.captureScreenViews = true
// 수동 .screen() 호출(AnalyticsService.logScreen)과 중복되므로 자동수집은 비활성화
config.captureScreenViews = false

PostHogSDK.shared.setup(config)
#endif
Expand Down