Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
21 changes: 21 additions & 0 deletions DevLog/App/Assembler/AppLayerAssembler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// AppLayerAssembler.swift
// DevLog
//
// Created by opfic on 3/19/26.
//

final class AppLayerAssembler: Assembler {
func assemble(_ container: any DIContainer) {
container.register(FCMTokenSyncHandler.self) {
FCMTokenSyncHandler(
repository: container.resolve(UserDataRepository.self)
)
}
container.register(UserTimeZoneSyncHandler.self) {
UserTimeZoneSyncHandler(
repository: container.resolve(UserDataRepository.self)
)
}
}
}
3 changes: 2 additions & 1 deletion DevLog/App/Assembler/Assembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ final class AppAssembler: Assembler {
PersistenceAssembler(),
InfraAssembler(),
DataAssembler(),
DomainAssembler()
DomainAssembler(),
AppLayerAssembler()
]

func assemble(_ container: any DIContainer) {
Expand Down
28 changes: 10 additions & 18 deletions DevLog/App/Delegate/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import UIKit
import Firebase
import FirebaseAuth
import GoogleSignIn

class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
private let logger = Logger(category: "AppDelegate")
private let container = AppDIContainer.shared

func application(
_ app: UIApplication,
Expand All @@ -26,7 +26,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
FirebaseApp.configure()

_ = container.resolve(FCMTokenSyncHandler.self)
_ = container.resolve(UserTimeZoneSyncHandler.self)

// 알림 권한 요청
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
Expand All @@ -41,7 +43,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
}

// 앱이 온그라운드로 되었을 때, 로그인 세션이 존재한다면 현재 유저의 timeZone 저장
updateUserTimeZone()
NotificationCenter.default.post(name: .didRequestUserTimeZoneSync, object: nil)

// Firebase Messaging 설정
Messaging.messaging().delegate = self
Expand Down Expand Up @@ -78,21 +80,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
) {
if let fcmToken = fcmToken {
logger.info("FCM token: \(fcmToken)")
}
}
}

private extension AppDelegate {
func updateUserTimeZone() {
Task {
do {
guard let uid = Auth.auth().currentUser?.uid else { return }
let settingsRef = Firestore.firestore().document("users/\(uid)/userData/settings")

try await settingsRef.setData(["timeZone": TimeZone.autoupdatingCurrent.identifier], merge: true)
} catch {
logger.error("Failed to update timeZone", error: error)
}
NotificationCenter.default.post(
name: .didRefreshFCMToken,
object: nil,
userInfo: ["fcmToken": fcmToken]
)
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions DevLog/App/Handler/FCMTokenSyncHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// FCMTokenSyncHandler.swift
// DevLog
//
// Created by opfic on 3/19/26.
//

import Combine
import Foundation

final class FCMTokenSyncHandler {
private let repository: UserDataRepository
private let logger = Logger(category: "FCMTokenSyncHandler")
private var cancellables = Set<AnyCancellable>()

init(
repository: UserDataRepository,
notificationCenter: NotificationCenter = .default
) {
self.repository = repository

notificationCenter.publisher(for: .didRefreshFCMToken)
.compactMap { $0.userInfo?["fcmToken"] as? String }
.sink { [weak self] fcmToken in
Task {
do {
try await self?.repository.updateFCMToken(fcmToken)
} catch {
self?.logger.error("Failed to sync refreshed FCM token", error: error)
}
}
}
.store(in: &cancellables)
}
}
34 changes: 34 additions & 0 deletions DevLog/App/Handler/UserTimeZoneSyncHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// UserTimeZoneSyncHandler.swift
// DevLog
//
// Created by opfic on 3/19/26.
//

import Combine
import Foundation

final class UserTimeZoneSyncHandler {
private let repository: UserDataRepository
private let logger = Logger(category: "UserTimeZoneSyncHandler")
private var cancellables = Set<AnyCancellable>()

init(
repository: UserDataRepository,
notificationCenter: NotificationCenter = .default
) {
self.repository = repository

notificationCenter.publisher(for: .didRequestUserTimeZoneSync)
.sink { [weak self] _ in
Task {
do {
try await self?.repository.updateUserTimeZone()
} catch {
self?.logger.error("Failed to sync user timeZone", error: error)
}
}
}
.store(in: &cancellables)
}
}
Comment on lines +8 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

AppDelegate의 주석(// 앱이 온그라운드로 되었을 때...)을 보면, 앱이 포그라운드로 전환될 때마다 타임존을 동기화하려는 의도가 있었던 것으로 보입니다. 현재 구현은 앱이 처음 시작될 때만 동기화를 요청하고 있어, 이 의도를 완전히 반영하지 못하고 있습니다.

사용자가 앱을 백그라운드로 전환한 후 기기의 시간대를 변경하고 다시 앱으로 돌아오는 경우, 변경된 타임존이 즉시 반영되지 않는 문제가 발생할 수 있습니다.

UIApplication.willEnterForegroundNotification 알림을 추가로 수신하여, 앱이 포그라운드로 진입할 때마다 타임존을 동기화하도록 개선하는 것을 제안합니다. 이렇게 하면 앱의 동작이 주석의 의도와 일치하게 되고, 사용자 경험도 향상될 것입니다.

import Combine
import Foundation
import UIKit

final class UserTimeZoneSyncHandler {
    private let repository: UserDataRepository
    private let logger = Logger(category: "UserTimeZoneSyncHandler")
    private var cancellables = Set<AnyCancellable>()

    init(
        repository: UserDataRepository,
        notificationCenter: NotificationCenter = .default
    ) {
        self.repository = repository

        let launchPublisher = notificationCenter.publisher(for: .didRequestUserTimeZoneSync).map { _ in () }
        let foregroundPublisher = notificationCenter.publisher(for: UIApplication.willEnterForegroundNotification).map { _ in () }

        Publishers.Merge(launchPublisher, foregroundPublisher)
            .sink { [weak self] in
                Task {
                    do {
                        try await self?.repository.updateUserTimeZone()
                    } catch {
                        self?.logger.error("Failed to sync user timeZone", error: error)
                    }
                }
            }
            .store(in: &cancellables)
    }
}

13 changes: 13 additions & 0 deletions DevLog/App/Notification/NotificationName+.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// NotificationName+.swift
// DevLog
//
// Created by opfic on 3/19/26.
//

import Foundation

extension Notification.Name {
static let didRefreshFCMToken = Notification.Name("didRefreshFCMToken")
static let didRequestUserTimeZoneSync = Notification.Name("didRequestUserTimeZoneSync")
}
10 changes: 9 additions & 1 deletion DevLog/Data/Repository/UserDataRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ final class UserDataRepositoryImpl: UserDataRepository {
}

func upsertStatusMessage(_ message: String) async throws {
try await self.userService.upsertStatusMessage(message)
try await userService.upsertStatusMessage(message)
}

func updateFCMToken(_ fcmToken: String) async throws {
try await userService.updateFCMToken(fcmToken)
}

func updateUserTimeZone() async throws {
try await userService.updateUserTimeZone()
}
}
2 changes: 2 additions & 0 deletions DevLog/Domain/Protocol/UserDataRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
protocol UserDataRepository {
func fetch() async throws -> UserProfile
func upsertStatusMessage(_ message: String) async throws
func updateFCMToken(_ fcmToken: String) async throws
func updateUserTimeZone() async throws
}
28 changes: 27 additions & 1 deletion DevLog/Infra/Service/UserService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,12 @@ final class UserService {
}
}

func updateFCMToken(_ userId: String, fcmToken: String) async throws {
func updateFCMToken(_ fcmToken: String) async throws {
guard let userId = Auth.auth().currentUser?.uid else {
logger.info("Skipping FCM token update because no authenticated user exists")
return
}

logger.info("Updating FCM token for user: \(userId)")

do {
Expand All @@ -157,4 +162,25 @@ final class UserService {
throw error
}
}

func updateUserTimeZone() async throws {
guard let userId = Auth.auth().currentUser?.uid else {
logger.info("Skipping timeZone update because no authenticated user exists")
return
}

logger.info("Updating timeZone for user: \(userId)")

do {
let settingsRef = store.document("users/\(userId)/userData/settings")
try await settingsRef.setData(
["timeZone": TimeZone.autoupdatingCurrent.identifier],
merge: true
)
logger.info("Successfully updated timeZone")
} catch {
logger.error("Failed to update timeZone", error: error)
throw error
}
}
}