Skip to content

Commit 4b5a143

Browse files
[#213] 푸시 알람을 탭하면 관련 Todo를 보여주도록 구현한다 (#216)
* feat: 푸시알람을 탭하면 시트로 해당 todo를 볼 수 있도록 구현 * style: todoID -> todoId * fix: 푸시 알람을 탭해서 앱에 들어와도 푸시알림 데이터가 최신화되지 않는 현상 해결 * refactor: 불필요한 바인딩 코드 제거 * refactor: guard 문으로 수정 Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent f71fa38 commit 4b5a143

34 files changed

Lines changed: 338 additions & 127 deletions

DevLog/App/Assembler/DomainAssembler.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ private extension DomainAssembler {
5151
}
5252

5353
func registerTodoUseCases(_ container: DIContainer) {
54-
container.register(FetchTodoByIDUseCase.self) {
55-
FetchTodoByIDUseCaseImpl(container.resolve(TodoRepository.self))
54+
container.register(FetchTodoByIdUseCase.self) {
55+
FetchTodoByIdUseCaseImpl(container.resolve(TodoRepository.self))
5656
}
5757

5858
container.register(FetchTodosUseCase.self) {

DevLog/App/Delegate/AppDelegate.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
5050

5151
// 앱이 완전 종료되어도, 알림을 통해 앱이 시작된 경우 처리
5252
if let remoteNotification = launchOptions?[.remoteNotification] as? [AnyHashable: Any] {
53-
NotificationCenter.default.post(name: .pushTapped, object: nil, userInfo: remoteNotification)
53+
Task { @MainActor in
54+
PushNotificationRoute.shared.handlePushTap(userInfo: remoteNotification)
55+
}
5456
}
5557

5658
return true
@@ -114,14 +116,14 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
114116
withCompletionHandler completionHandler: @escaping () -> Void
115117
) {
116118
logger.info("Tapped notification: \(response.notification.request.content.userInfo)")
117-
// userInfo["todoId"]로 이동할 Todo 식별
118119
let userInfo = response.notification.request.content.userInfo
119-
NotificationCenter.default.post(name: .pushTapped, object: nil, userInfo: userInfo)
120+
Task { @MainActor in
121+
PushNotificationRoute.shared.handlePushTap(userInfo: userInfo)
122+
}
120123
completionHandler()
121124
}
122125
}
123126

124127
extension Notification.Name {
125128
static let fcmToken = Notification.Name("fcmToken")
126-
static let pushTapped = Notification.Name("pushTapped")
127129
}

DevLog/App/RootView.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import SwiftUI
1010
struct RootView: View {
1111
@Environment(\.diContainer) var container: DIContainer
1212
@State var viewModel: RootViewModel
13+
@State private var selectedRoute: AppRoute?
1314

1415
var body: some View {
1516
ZStack {
@@ -56,5 +57,29 @@ struct RootView: View {
5657
viewModel.send(.signOutAuto)
5758
}
5859
}
60+
.sheet(item: $selectedRoute) { route in
61+
switch route {
62+
case .todoDetail(let todoId):
63+
NavigationStack {
64+
TodoDetailView(viewModel: TodoDetailViewModel(
65+
fetchUseCase: container.resolve(FetchTodoByIdUseCase.self),
66+
upsertUseCase: container.resolve(UpsertTodoUseCase.self),
67+
todoId: todoId,
68+
showEditButton: false
69+
))
70+
.toolbar {
71+
ToolbarLeadingButton {
72+
selectedRoute = nil
73+
}
74+
}
75+
}
76+
.background(Color(.secondarySystemBackground))
77+
.presentationDragIndicator(.visible)
78+
}
79+
}
80+
.onReceive(PushNotificationRoute.shared.publisher) { route in
81+
selectedRoute = route
82+
PushNotificationRoute.shared.clear()
83+
}
5984
}
6085
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// PushNotificationRoute.swift
3+
// DevLog
4+
//
5+
// Created by opfic on 3/8/26.
6+
//
7+
8+
import Foundation
9+
import Combine
10+
11+
enum AppRoute: Equatable, Identifiable {
12+
case todoDetail(String)
13+
14+
var id: String {
15+
switch self {
16+
case .todoDetail(let todoId):
17+
return "todo:\(todoId)"
18+
}
19+
}
20+
}
21+
22+
final class PushNotificationRoute {
23+
static let shared = PushNotificationRoute()
24+
25+
var publisher: AnyPublisher<AppRoute, Never> {
26+
subject
27+
.compactMap { $0 }
28+
.eraseToAnyPublisher()
29+
}
30+
31+
private let subject = CurrentValueSubject<AppRoute?, Never>(nil)
32+
33+
private init() { }
34+
35+
func handlePushTap(userInfo: [AnyHashable: Any]) {
36+
guard let todoId = extractTodoId(from: userInfo) else { return }
37+
subject.send(.todoDetail(todoId))
38+
}
39+
40+
func clear() {
41+
subject.send(nil)
42+
}
43+
44+
private func extractTodoId(from userInfo: [AnyHashable: Any]) -> String? {
45+
guard let todoId = userInfo["todoId"] as? String, !todoId.isEmpty else {
46+
return nil
47+
}
48+
return todoId
49+
}
50+
}

DevLog/Data/DTO/PushNotificationResponse.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ struct PushNotificationResponse {
1313
let body: String
1414
let receivedAt: Date
1515
let isRead: Bool
16-
let todoID: String
16+
let todoId: String
1717
let todoKind: String
1818
}

DevLog/Data/Mapper/PushNotificationMapping.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extension PushNotificationResponse {
1717
body: self.body,
1818
receivedAt: self.receivedAt,
1919
isRead: self.isRead,
20-
todoID: self.todoID,
20+
todoId: self.todoId,
2121
todoKind: todoKind
2222
)
2323
}

DevLog/Data/Repository/PushNotificationRepositoryImpl.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Foundation
9+
import Combine
910

1011
final class PushNotificationRepositoryImpl: PushNotificationRepository {
1112
private let service: PushNotificationService
@@ -41,13 +42,22 @@ final class PushNotificationRepositoryImpl: PushNotificationRepository {
4142
return try response.toDomain()
4243
}
4344

45+
func observeNotifications(
46+
_ query: PushNotificationQuery,
47+
limit: Int
48+
) throws -> AnyPublisher<PushNotificationPage, Error> {
49+
try service.observeNotifications(query, limit: limit)
50+
.tryMap { try $0.toDomain() }
51+
.eraseToAnyPublisher()
52+
}
53+
4454
// 푸시 알림 기록 삭제
4555
func deleteNotification(_ notificationID: String) async throws {
4656
try await service.deleteNotification(notificationID)
4757
}
4858

4959
// 푸시 알림 읽음/안읽음 토글
50-
func toggleNotificationRead(_ todoID: String) async throws {
51-
try await service.toggleNotificationRead(todoID)
60+
func toggleNotificationRead(_ todoId: String) async throws {
61+
try await service.toggleNotificationRead(todoId)
5262
}
5363
}

DevLog/Data/Repository/TodoRepositoryImpl.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ final class TodoRepositoryImpl: TodoRepository {
2020
return try response.toDomain()
2121
}
2222

23-
func fetchTodo(_ todoID: String) async throws -> Todo {
24-
let response = try await todoService.fetchTodo(todoID: todoID)
23+
func fetchTodo(_ todoId: String) async throws -> Todo {
24+
let response = try await todoService.fetchTodo(todoId: todoId)
2525
return try response.toDomain()
2626
}
2727

@@ -30,7 +30,7 @@ final class TodoRepositoryImpl: TodoRepository {
3030
try await todoService.upsertTodo(request: request)
3131
}
3232

33-
func deleteTodo(_ todoID: String) async throws {
34-
try await todoService.deleteTodo(todoID: todoID)
33+
func deleteTodo(_ todoId: String) async throws {
34+
try await todoService.deleteTodo(todoId: todoId)
3535
}
3636
}

DevLog/Domain/Entity/PushNotification.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ struct PushNotification {
1313
let body: String
1414
let receivedAt: Date
1515
var isRead: Bool
16-
let todoID: String
16+
let todoId: String
1717
let todoKind: TodoKind
1818
}

DevLog/Domain/Protocol/PushNotificationRepository.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77

88
import Foundation
9+
import Combine
910

1011
protocol PushNotificationRepository {
1112
func fetchPushNotificationEnabled() async throws -> Bool
@@ -15,6 +16,10 @@ protocol PushNotificationRepository {
1516
_ query: PushNotificationQuery,
1617
cursor: PushNotificationCursor?
1718
) async throws -> PushNotificationPage
19+
func observeNotifications(
20+
_ query: PushNotificationQuery,
21+
limit: Int
22+
) throws -> AnyPublisher<PushNotificationPage, Error>
1823
func deleteNotification(_ notificationID: String) async throws
19-
func toggleNotificationRead(_ todoID: String) async throws
24+
func toggleNotificationRead(_ todoId: String) async throws
2025
}

0 commit comments

Comments
 (0)