Skip to content

Commit a9fd340

Browse files
committed
feat: 온그라운드 상태에서의 푸시 배지 구현
1 parent ff7eb44 commit a9fd340

1 file changed

Lines changed: 124 additions & 1 deletion

File tree

DevLog/App/Delegate/AppDelegate.swift

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@ import FirebaseAuth
1111
import FirebaseFirestore
1212
import FirebaseMessaging
1313
import GoogleSignIn
14+
import Combine
1415
import UserNotifications
1516

1617
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
1718
private let logger = Logger(category: "AppDelegate")
19+
private var store: Firestore { Firestore.firestore() }
20+
private var authStateListenerHandle: AuthStateDidChangeListenerHandle?
21+
private var cancellable: AnyCancellable?
22+
1823
func application(
1924
_ app: UIApplication,
2025
open url: URL,
@@ -47,6 +52,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
4752

4853
// Firebase Messaging 설정
4954
Messaging.messaging().delegate = self
55+
observeAuthState()
5056

5157
// 앱이 완전 종료되어도, 알림을 통해 앱이 시작된 경우 처리
5258
if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
@@ -73,6 +79,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
7379
) {
7480
logger.error("Failed to register APNs token", error: error)
7581
}
82+
83+
func applicationDidBecomeActive(_ application: UIApplication) {
84+
syncBadgeCount()
85+
}
7686

7787
// FCMToken 갱신
7888
func messaging(
@@ -83,8 +93,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
8393
NotificationCenter.default.post(name: .fcmToken, object: nil, userInfo: ["fcmToken": fcmToken])
8494
}
8595
}
96+
}
8697

87-
private func updateUserTimeZone() {
98+
private extension AppDelegate {
99+
func updateUserTimeZone() {
88100
Task {
89101
do {
90102
guard let uid = Auth.auth().currentUser?.uid else { return }
@@ -96,6 +108,117 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
96108
}
97109
}
98110
}
111+
112+
func observeAuthState() {
113+
authStateListenerHandle = Auth.auth().addStateDidChangeListener { [weak self] _, user in
114+
guard let self else { return }
115+
116+
self.cancellable?.cancel()
117+
118+
guard user != nil else {
119+
self.updateBadgeCount(0)
120+
return
121+
}
122+
123+
self.startObservingBadgeCount()
124+
self.syncBadgeCount()
125+
}
126+
}
127+
128+
func startObservingBadgeCount() {
129+
cancellable = try? observeUnreadNotificationCount()
130+
.receive(on: DispatchQueue.main)
131+
.sink(
132+
receiveCompletion: { [weak self] completion in
133+
guard let self else { return }
134+
135+
if case .failure(let error) = completion {
136+
self.logger.error("Failed to observe unread notification count", error: error)
137+
}
138+
},
139+
receiveValue: { [weak self] count in
140+
self?.updateBadgeCount(count)
141+
}
142+
)
143+
}
144+
145+
func fetchUnreadNotificationCount() async throws -> Int {
146+
logger.info("Fetching unread notification count")
147+
148+
guard let uid = Auth.auth().currentUser?.uid else {
149+
logger.error("User not authenticated")
150+
throw AuthError.notAuthenticated
151+
}
152+
153+
do {
154+
let snapshot = try await store.collection("users/\(uid)/notifications")
155+
.whereField("isRead", isEqualTo: false)
156+
.getDocuments()
157+
158+
let unreadNotificationCount = snapshot.documents.count
159+
logger.info("Unread notification count: \(unreadNotificationCount)")
160+
return unreadNotificationCount
161+
} catch {
162+
logger.error("Failed to fetch unread notification count", error: error)
163+
throw error
164+
}
165+
}
166+
167+
func observeUnreadNotificationCount() throws -> AnyPublisher<Int, Error> {
168+
logger.info("Observing unread notification count")
169+
170+
guard let uid = Auth.auth().currentUser?.uid else {
171+
logger.error("User not authenticated")
172+
throw AuthError.notAuthenticated
173+
}
174+
175+
let subject = PassthroughSubject<Int, Error>()
176+
let listener = store.collection("users/\(uid)/notifications")
177+
.whereField("isRead", isEqualTo: false)
178+
.addSnapshotListener { [weak self] snapshot, error in
179+
if let error {
180+
self?.logger.error("Failed to observe unread notification count", error: error)
181+
subject.send(completion: .failure(error))
182+
return
183+
}
184+
185+
guard let snapshot else { return }
186+
187+
let unreadNotificationCount = snapshot.documents.count
188+
self?.logger.info("Observed unread notification count: \(unreadNotificationCount)")
189+
subject.send(unreadNotificationCount)
190+
}
191+
192+
return subject
193+
.handleEvents(receiveCancel: { listener.remove() })
194+
.eraseToAnyPublisher()
195+
}
196+
197+
func syncBadgeCount() {
198+
Task { @MainActor [weak self] in
199+
guard let self else { return }
200+
guard Auth.auth().currentUser != nil else {
201+
self.updateBadgeCount(0)
202+
return
203+
}
204+
205+
do {
206+
let unreadNotificationCount = try await self.fetchUnreadNotificationCount()
207+
self.updateBadgeCount(unreadNotificationCount)
208+
} catch {
209+
self.logger.error("Failed to fetch unread notification count", error: error)
210+
}
211+
}
212+
}
213+
214+
@MainActor
215+
private func updateBadgeCount(_ count: Int) {
216+
UNUserNotificationCenter.current().setBadgeCount(count) { [weak self] error in
217+
if let error {
218+
self?.logger.error("Failed to update badge count", error: error)
219+
}
220+
}
221+
}
99222
}
100223

101224
extension AppDelegate: UNUserNotificationCenterDelegate {

0 commit comments

Comments
 (0)