@@ -11,10 +11,15 @@ import FirebaseAuth
1111import FirebaseFirestore
1212import FirebaseMessaging
1313import GoogleSignIn
14+ import Combine
1415import UserNotifications
1516
1617class 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,122 @@ 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 syncBadgeCount( ) {
129+ Task { @MainActor [ weak self] in
130+ guard let self else { return }
131+ guard Auth . auth ( ) . currentUser != nil else {
132+ self . updateBadgeCount ( 0 )
133+ return
134+ }
135+
136+ do {
137+ let unreadNotificationCount = try await self . fetchUnreadNotificationCount ( )
138+ self . updateBadgeCount ( unreadNotificationCount)
139+ } catch {
140+ self . logger. error ( " Failed to fetch unread notification count " , error: error)
141+ }
142+ }
143+ }
144+
145+ private func startObservingBadgeCount( ) {
146+ do {
147+ cancellable = try observeUnreadNotificationCount ( )
148+ . receive ( on: DispatchQueue . main)
149+ . sink (
150+ receiveCompletion: { [ weak self] completion in
151+ guard let self else { return }
152+
153+ if case . failure( let error) = completion {
154+ self . logger. error ( " Failed to observe unread notification count " , error: error)
155+ }
156+ } ,
157+ receiveValue: { [ weak self] count in
158+ self ? . updateBadgeCount ( count)
159+ }
160+ )
161+ } catch {
162+ logger. error ( " Failed to start observing badge count " , error: error)
163+ }
164+ }
165+
166+ private func fetchUnreadNotificationCount( ) async throws -> Int {
167+ logger. info ( " Fetching unread notification count " )
168+
169+ guard let uid = Auth . auth ( ) . currentUser? . uid else {
170+ logger. error ( " User not authenticated " )
171+ throw AuthError . notAuthenticated
172+ }
173+
174+ do {
175+ let snapshot = try await store. collection ( " users/ \( uid) /notifications " )
176+ . whereField ( " isRead " , isEqualTo: false )
177+ . getDocuments ( )
178+
179+ let unreadNotificationCount = snapshot. documents. count
180+ logger. info ( " Unread notification count: \( unreadNotificationCount) " )
181+ return unreadNotificationCount
182+ } catch {
183+ logger. error ( " Failed to fetch unread notification count " , error: error)
184+ throw error
185+ }
186+ }
187+
188+ private func observeUnreadNotificationCount( ) throws -> AnyPublisher < Int , Error > {
189+ logger. info ( " Observing unread notification count " )
190+
191+ guard let uid = Auth . auth ( ) . currentUser? . uid else {
192+ logger. error ( " User not authenticated " )
193+ throw AuthError . notAuthenticated
194+ }
195+
196+ let subject = PassthroughSubject < Int , Error > ( )
197+ let listener = store. collection ( " users/ \( uid) /notifications " )
198+ . whereField ( " isRead " , isEqualTo: false )
199+ . addSnapshotListener { [ weak self] snapshot, error in
200+ guard let self else { return }
201+ if let error {
202+ self . logger. error ( " Failed to observe unread notification count " , error: error)
203+ subject. send ( completion: . failure( error) )
204+ return
205+ }
206+
207+ guard let snapshot else { return }
208+
209+ let unreadNotificationCount = snapshot. documents. count
210+ self . logger. info ( " Observed unread notification count: \( unreadNotificationCount) " )
211+ subject. send ( unreadNotificationCount)
212+ }
213+
214+ return subject
215+ . handleEvents ( receiveCancel: { listener. remove ( ) } )
216+ . eraseToAnyPublisher ( )
217+ }
218+
219+ @MainActor
220+ private func updateBadgeCount( _ count: Int ) {
221+ UNUserNotificationCenter . current ( ) . setBadgeCount ( count) { [ weak self] error in
222+ if let error {
223+ self ? . logger. error ( " Failed to update badge count " , error: error)
224+ }
225+ }
226+ }
99227}
100228
101229extension AppDelegate : UNUserNotificationCenterDelegate {
0 commit comments