Skip to content

Commit 3ba4044

Browse files
authored
Merge pull request #300 from teamterning/#299-푸시알림
#299 - 푸시알림 로직 서버 의존으로 변경 | v1.4.0 앱스토어 배포
2 parents 302394b + a969c71 commit 3ba4044

File tree

9 files changed

+139
-132
lines changed

9 files changed

+139
-132
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# 👔 terning 터닝 - 대학생 인턴, 공고 관리 캘린더
55

6-
## 앱스토어 링크 : [앱스토어](https://apps.apple.com/kr/app/terning-%ED%84%B0%EB%8B%9D-%EB%8C%80%ED%95%99%EC%83%9D-%EC%9D%B8%ED%84%B4-%EA%B3%B5%EA%B3%A0-%EA%B4%80%EB%A6%AC-%EC%BA%98%EB%A6%B0%EB%8D%94/id6547866420) v1.3.1
6+
## 앱스토어 링크 : [앱스토어](https://apps.apple.com/kr/app/terning-%ED%84%B0%EB%8B%9D-%EB%8C%80%ED%95%99%EC%83%9D-%EC%9D%B8%ED%84%B4-%EA%B3%B5%EA%B3%A0-%EA%B4%80%EB%A6%AC-%EC%BA%98%EB%A6%B0%EB%8D%94/id6547866420) v1.4.0
77
<p align="left"><img width="900" src="https://github.com/user-attachments/assets/984e7795-3746-4e7a-ad6c-cb1cb376c481"></p>
88

99
**내 계획에 딱 맞는 대학생 인턴의 시작, 터닝**

Terning-iOS/Terning-iOS.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,7 +2024,7 @@
20242024
CODE_SIGN_ENTITLEMENTS = "Terning-iOS/Terning-iOS.entitlements";
20252025
CODE_SIGN_IDENTITY = "Apple Development";
20262026
CODE_SIGN_STYLE = Automatic;
2027-
CURRENT_PROJECT_VERSION = 2025.0513.2213;
2027+
CURRENT_PROJECT_VERSION = 2025.0524.2007;
20282028
DEVELOPMENT_TEAM = 8Q4H7X3Q58;
20292029
ENABLE_USER_SCRIPT_SANDBOXING = NO;
20302030
GENERATE_INFOPLIST_FILE = YES;
@@ -2041,7 +2041,7 @@
20412041
"$(inherited)",
20422042
"@executable_path/Frameworks",
20432043
);
2044-
MARKETING_VERSION = 1.3.1;
2044+
MARKETING_VERSION = 1.4.0;
20452045
OTHER_LDFLAGS = (
20462046
"-Xlinker",
20472047
"-interposable",
@@ -2068,7 +2068,7 @@
20682068
CODE_SIGN_ENTITLEMENTS = "Terning-iOS/Terning-iOS.entitlements";
20692069
CODE_SIGN_IDENTITY = "Apple Development";
20702070
CODE_SIGN_STYLE = Automatic;
2071-
CURRENT_PROJECT_VERSION = 2025.0513.2213;
2071+
CURRENT_PROJECT_VERSION = 2025.0524.2007;
20722072
DEVELOPMENT_TEAM = 8Q4H7X3Q58;
20732073
ENABLE_USER_SCRIPT_SANDBOXING = NO;
20742074
GENERATE_INFOPLIST_FILE = YES;
@@ -2085,7 +2085,7 @@
20852085
"$(inherited)",
20862086
"@executable_path/Frameworks",
20872087
);
2088-
MARKETING_VERSION = 1.3.1;
2088+
MARKETING_VERSION = 1.4.0;
20892089
PRODUCT_BUNDLE_IDENTIFIER = "com.terning.Terning-iOS";
20902090
PRODUCT_NAME = "$(TARGET_NAME)";
20912091
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2104,7 +2104,7 @@
21042104
buildSettings = {
21052105
CODE_SIGN_IDENTITY = "Apple Development";
21062106
CODE_SIGN_STYLE = Automatic;
2107-
CURRENT_PROJECT_VERSION = 2025.0513.2213;
2107+
CURRENT_PROJECT_VERSION = 2025.0524.2007;
21082108
DEVELOPMENT_TEAM = 8Q4H7X3Q58;
21092109
ENABLE_USER_SCRIPT_SANDBOXING = YES;
21102110
GENERATE_INFOPLIST_FILE = YES;
@@ -2132,7 +2132,7 @@
21322132
buildSettings = {
21332133
CODE_SIGN_IDENTITY = "Apple Development";
21342134
CODE_SIGN_STYLE = Automatic;
2135-
CURRENT_PROJECT_VERSION = 2025.0513.2213;
2135+
CURRENT_PROJECT_VERSION = 2025.0524.2007;
21362136
DEVELOPMENT_TEAM = 8Q4H7X3Q58;
21372137
ENABLE_USER_SCRIPT_SANDBOXING = YES;
21382138
GENERATE_INFOPLIST_FILE = YES;

Terning-iOS/Terning-iOS/Application/AppDelegate.swift

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
6666

6767
/// 푸시 클릭시
6868
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
69-
guard UserManager.shared.isPushEnabled ?? true else {
70-
print("❌ 사용자가 푸시 OFF 설정함 → 무시")
71-
return
72-
}
7369

7470
let userInfo = response.notification.request.content.userInfo
7571
if let type = userInfo["type"] as? String {
@@ -79,11 +75,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
7975

8076
/// Foreground(앱 켜진 상태)에서도 알림 오는 설정
8177
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
82-
guard UserManager.shared.isPushEnabled ?? true else {
83-
print("❌ Foreground 푸시 무시됨")
84-
completionHandler([]) // 아무것도 표시하지 않음
85-
return
86-
}
78+
8779
completionHandler([.sound, .banner, .list])
8880
}
8981

@@ -110,8 +102,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
110102
print("🚫 알림 거부됨")
111103
}
112104

113-
// ✅ 권한 상태를 UserManager와 동기화
114-
// UserManager.shared.isPushEnabled = isGranted
115105
}
116106
}
117107
}
@@ -125,9 +115,12 @@ extension AppDelegate: MessagingDelegate {
125115
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
126116
print("🟢 Firebase 등록 토큰(token): \(String(describing: fcmToken))")
127117

118+
guard let fcmToken = fcmToken else { return }
119+
128120
UserManager.shared.fcmToken = fcmToken
129-
130-
let dataDict: [String: String] = ["token": fcmToken ?? ""]
121+
UserManager.shared.setUserFCMTokenToServer(fcmToken: fcmToken)
122+
123+
let dataDict: [String: String] = ["token": fcmToken]
131124
NotificationCenter.default.post(
132125
name: Notification.Name("FCMToken"),
133126
object: nil,

Terning-iOS/Terning-iOS/Data/Network/Base/TargetType/AuthTargetType.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ enum AuthTargetType {
1515
case postOnboarding(grade: String, workingPeriod: String, startYear: Int, startMonth: Int)
1616
case logout
1717
case withdraw
18+
case setUserFCMToken(fcmToken: String)
1819
}
1920

2021
extension AuthTargetType: TargetType {
@@ -40,12 +41,14 @@ extension AuthTargetType: TargetType {
4041
return "/auth/logout"
4142
case .withdraw:
4243
return "/auth/withdraw"
44+
case .setUserFCMToken:
45+
return "/auth/sync-user"
4346
}
4447
}
4548

4649
var method: Moya.Method {
4750
switch self {
48-
case .signIn, .signUp, .getNewToken, .postOnboarding, .logout:
51+
case .signIn, .signUp, .getNewToken, .postOnboarding, .logout, .setUserFCMToken:
4952
return .post
5053
case .withdraw:
5154
return .delete
@@ -80,6 +83,11 @@ extension AuthTargetType: TargetType {
8083
"startMonth": startMonth
8184
],
8285
encoding: JSONEncoding.default)
86+
case .setUserFCMToken(let fcmToken):
87+
return .requestParameters(
88+
parameters: ["fcmToken": fcmToken],
89+
encoding: JSONEncoding.default
90+
)
8391
case .logout, .withdraw:
8492
return .requestPlain
8593
}
@@ -113,6 +121,8 @@ extension AuthTargetType: TargetType {
113121
return Config.userIdHeader
114122
case .logout, .withdraw:
115123
return Config.defaultHeader
124+
case .setUserFCMToken:
125+
return Config.headerWithAccessToken
116126
}
117127
return headers
118128
}

Terning-iOS/Terning-iOS/Data/Network/User/UserManager.swift

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ final class UserManager {
2828
@UserDefaultWrapper<String>(key: "authId") public var authId
2929
@UserDefaultWrapper<String>(key: "authType") public var authType
3030
@UserDefaultWrapper<String>(key: "userName") public var userName
31+
32+
// FCM 로직
3133
@UserDefaultWrapper<String>(key: "fcmToken") public var fcmToken
32-
@UserDefaultWrapper<Bool>(key: "isPushEnabled") public var isPushEnabled
34+
@UserDefaultWrapper<String>(key: "lastSentFCMToken") public var lastSentFCMToken
35+
@NonOptionalUserDefaultWrapper<Bool>(key: "didSyncFCMToken", defaultValue: false) public var didSyncFCMToken
3336

3437
var hasAccessToken: Bool { return self.accessToken != nil }
3538
var hasKakaoToken: Bool { return self.kakaoAccessToken != nil }
@@ -166,3 +169,33 @@ extension UserManager {
166169
}
167170
}
168171
}
172+
173+
extension UserManager {
174+
175+
/// 서버에 FCM 토큰을 전송하는 메서드 (최초 1회만 전송)
176+
func setUserFCMTokenToServer(fcmToken: String) {
177+
print("🧾 [FCM] 전송 시도 시작")
178+
print(" ⮕ 현재 FCM Token: \(fcmToken)")
179+
print(" ⮕ 저장된 마지막 전송 FCM Token: \(lastSentFCMToken ?? "없음")")
180+
print(" ⮕ 로그인 상태: \(hasAccessToken ? "✅ 있음" : "❌ 없음")")
181+
print(" ⮕ 동기화된 적 있음?: \(didSyncFCMToken ? "✅ 예" : "❌ 아니오")")
182+
183+
// ✅ 조건: 로그인 + (아직 동기화 안 했거나, 저장된 토큰과 다를 때)
184+
guard hasAccessToken, (!didSyncFCMToken || lastSentFCMToken != fcmToken) else {
185+
print("🚫 전송 조건 불충족 → FCM 토큰 전송 안함\n")
186+
return
187+
}
188+
189+
authProvider.request(.setUserFCMToken(fcmToken: fcmToken)) { result in
190+
switch result {
191+
case .success(let response):
192+
print("✅ FCM 토큰 서버 전송 성공 (statusCode: \(response.statusCode))")
193+
self.lastSentFCMToken = fcmToken
194+
self.didSyncFCMToken = true
195+
case .failure(let error):
196+
print("❌ FCM 토큰 서버 전송 실패: \(error.localizedDescription)")
197+
}
198+
print("🧾 [FCM] 전송 처리 종료\n")
199+
}
200+
}
201+
}

Terning-iOS/Terning-iOS/Presentation/MyPage/Cell/MyPageBasicViewCell.swift

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,9 @@ extension MyPageBasicViewCell {
142142
toggleSwitch = toggle
143143

144144
toggleAction = { isOn in
145-
UserManager.shared.isPushEnabled = isOn
146145
UserManager.shared.updatePushStatus(isEnabled: isOn)
147-
print("📬 푸시 설정 저장됨: \(isOn)")
148-
146+
print("📬 푸시 상태 서버에 업데이트: \(isOn)")
147+
149148
if isOn {
150149
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
151150
DispatchQueue.main.async {
@@ -154,15 +153,9 @@ extension MyPageBasicViewCell {
154153
} else {
155154
print("❗️ 알림 권한 거부됨 ❗️")
156155
toggle.setOn(false, animated: true)
157-
UserManager.shared.isPushEnabled = false
158156
}
159157
}
160158
}
161-
} else {
162-
// 사용자가 껐을 때: 알림 등록 해제는 불가능하지만,
163-
// 내부적으로 isPushEnabled를 false로 저장했으니,
164-
// 푸시 수신 필터링 시 사용 가능
165-
print("🚫 푸시 사용 안 함 (값은 UserDefaults 저장됨!) 🚫")
166159
}
167160
}
168161
}
@@ -194,29 +187,25 @@ extension MyPageBasicViewCell {
194187
if let url = URL(string: UIApplication.openSettingsURLString) {
195188
UIApplication.shared.open(url)
196189

197-
// 3. 설정 앱으로 이동했으면, 앱 다시 돌아올 때 상태 확인을 위해 isOn = false 처리
190+
// 설정 이동 → 스위치 다시 false로 돌려놓기
198191
sender.setOn(false, animated: true)
199-
UserManager.shared.isPushEnabled = false
200192
}
201193
})
202194
alert.addAction(UIAlertAction(title: "취소", style: .cancel) { _ in
203195
sender.setOn(false, animated: true)
204-
UserManager.shared.isPushEnabled = false
205196
})
206197

207198
if let topVC = UIApplication.shared.topMostViewController {
208199
topVC.present(alert, animated: true)
209200
}
210201
} else {
211-
// ✅ 권한 있음 → 정상 저장
212-
UserManager.shared.isPushEnabled = true
202+
// ✅ 권한 있음 → 서버로 상태 반영
213203
self.toggleAction?(true)
214204
}
215205
}
216206
}
217207
} else {
218-
// 꺼졌을 때 처리
219-
UserManager.shared.isPushEnabled = false
208+
// ✅ 꺼졌을 때 서버로 상태 반영
220209
toggleAction?(false)
221210
}
222211
}

Terning-iOS/Terning-iOS/Presentation/MyPage/ViewController/MyPageViewController.swift

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,6 @@ final class MyPageViewController: UIViewController {
6363
bindViewModel()
6464
myPageView.registerCells()
6565
}
66-
67-
override func viewWillAppear(_ animated: Bool) {
68-
super.viewWillAppear(animated)
69-
70-
viewModel.getMyPageInfo()
71-
}
7266
}
7367

7468
// MARK: - UI & Layout
@@ -174,8 +168,9 @@ extension MyPageViewController {
174168
@objc
175169
private func handlePushPermissionChange(_ notification: Notification) {
176170
let isAuthorized = notification.userInfo?["isAuthorized"] as? Bool ?? false
177-
let isUserEnabled = UserManager.shared.isPushEnabled ?? false
178-
let finalToggleState = isAuthorized && isUserEnabled // ✅ 둘 다 true여야 ON
171+
172+
let isPushAllowedInApp = viewModel.userInfoRelay.value.pushStatus == "ENABLED"
173+
let isFullyEnabled = isAuthorized && isPushAllowedInApp // ✅ 둘 다 true여야 진짜 ON
179174

180175
var updatedSections = sections
181176

@@ -187,14 +182,13 @@ extension MyPageViewController {
187182
MyPageBasicCellModel(
188183
image: .icPushAlarm,
189184
title: "푸시 알림",
190-
accessoryType: .toggle(isOn: finalToggleState, action: nil)
185+
accessoryType: .toggle(isOn: isFullyEnabled, action: nil)
191186
)
192187
)
193188
]
194189
)
195190

196191
self.sections = updatedSections
197-
198192
myPageView.tableView.reloadSections(IndexSet(integer: index), with: .fade)
199193
}
200194
}

0 commit comments

Comments
 (0)