Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions DevLog/App/Assembler/DomainAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ private extension DomainAssembler {
}

func registerTodoUseCases(_ container: DIContainer) {
container.register(FetchTodoByIDUseCase.self) {
FetchTodoByIDUseCaseImpl(container.resolve(TodoRepository.self))
container.register(FetchTodoByIdUseCase.self) {
FetchTodoByIdUseCaseImpl(container.resolve(TodoRepository.self))
}

container.register(FetchTodosUseCase.self) {
Expand Down
10 changes: 6 additions & 4 deletions DevLog/App/Delegate/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {

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

return true
Expand Down Expand Up @@ -114,14 +116,14 @@ extension AppDelegate: UNUserNotificationCenterDelegate {
withCompletionHandler completionHandler: @escaping () -> Void
) {
logger.info("Tapped notification: \(response.notification.request.content.userInfo)")
// userInfo["todoId"]로 이동할 Todo 식별
let userInfo = response.notification.request.content.userInfo
NotificationCenter.default.post(name: .pushTapped, object: nil, userInfo: userInfo)
Task { @MainActor in
PushNotificationRoute.shared.handlePushTap(userInfo: userInfo)
}
completionHandler()
}
}

extension Notification.Name {
static let fcmToken = Notification.Name("fcmToken")
static let pushTapped = Notification.Name("pushTapped")
}
28 changes: 28 additions & 0 deletions DevLog/App/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import SwiftUI
struct RootView: View {
@Environment(\.diContainer) var container: DIContainer
@State var viewModel: RootViewModel
@State private var selectedRoute: AppRoute?

var body: some View {
ZStack {
Expand Down Expand Up @@ -56,5 +57,32 @@ struct RootView: View {
viewModel.send(.signOutAuto)
}
}
.sheet(item: Binding(
get: { selectedRoute },
set: { selectedRoute = $0 }
)) { route in

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

Binding(get:set:)을 사용하여 sheetitem을 바인딩하고 있는데, AppRouteIdentifiable을 준수하므로 $selectedRoute를 직접 사용하여 코드를 더 간결하게 만들 수 있습니다.

        .sheet(item: $selectedRoute) { route in

switch route {
case .todoDetail(let todoId):
NavigationStack {
TodoDetailView(viewModel: TodoDetailViewModel(
fetchUseCase: container.resolve(FetchTodoByIdUseCase.self),
upsertUseCase: container.resolve(UpsertTodoUseCase.self),
todoId: todoId,
showEditButton: false
))
.toolbar {
ToolbarLeadingButton {
selectedRoute = nil
}
}
}
.background(Color(.secondarySystemBackground))
.presentationDragIndicator(.visible)
}
}
.onReceive(PushNotificationRoute.shared.publisher) { route in
selectedRoute = route
PushNotificationRoute.shared.clear()
}
}
}
50 changes: 50 additions & 0 deletions DevLog/App/Routing/PushNotificationRoute.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// PushNotificationRoute.swift
// DevLog
//
// Created by opfic on 3/8/26.
//

import Foundation
import Combine

enum AppRoute: Equatable, Identifiable {
case todoDetail(String)

var id: String {
switch self {
case .todoDetail(let todoId):
return "todo:\(todoId)"
}
}
}

final class PushNotificationRoute {
static let shared = PushNotificationRoute()

var publisher: AnyPublisher<AppRoute, Never> {
subject
.compactMap { $0 }
.eraseToAnyPublisher()
}

private let subject = CurrentValueSubject<AppRoute?, Never>(nil)

private init() { }

func handlePushTap(userInfo: [AnyHashable: Any]) {
guard let todoId = extractTodoId(from: userInfo) else { return }
subject.send(.todoDetail(todoId))
}

func clear() {
subject.send(nil)
}

private func extractTodoId(from userInfo: [AnyHashable: Any]) -> String? {
if let todoId = userInfo["todoId"] as? String, !todoId.isEmpty {
return todoId
}
return nil
}
Comment thread
opficdev marked this conversation as resolved.
}
2 changes: 1 addition & 1 deletion DevLog/Data/DTO/PushNotificationResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ struct PushNotificationResponse {
let body: String
let receivedAt: Date
let isRead: Bool
let todoID: String
let todoId: String
let todoKind: String
}
2 changes: 1 addition & 1 deletion DevLog/Data/Mapper/PushNotificationMapping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension PushNotificationResponse {
body: self.body,
receivedAt: self.receivedAt,
isRead: self.isRead,
todoID: self.todoID,
todoId: self.todoId,
todoKind: todoKind
)
}
Expand Down
14 changes: 12 additions & 2 deletions DevLog/Data/Repository/PushNotificationRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import Combine

final class PushNotificationRepositoryImpl: PushNotificationRepository {
private let service: PushNotificationService
Expand Down Expand Up @@ -41,13 +42,22 @@ final class PushNotificationRepositoryImpl: PushNotificationRepository {
return try response.toDomain()
}

func observeNotifications(
_ query: PushNotificationQuery,
limit: Int
) throws -> AnyPublisher<PushNotificationPage, Error> {
try service.observeNotifications(query, limit: limit)
.tryMap { try $0.toDomain() }
.eraseToAnyPublisher()
}

// 푸시 알림 기록 삭제
func deleteNotification(_ notificationID: String) async throws {
try await service.deleteNotification(notificationID)
}

// 푸시 알림 읽음/안읽음 토글
func toggleNotificationRead(_ todoID: String) async throws {
try await service.toggleNotificationRead(todoID)
func toggleNotificationRead(_ todoId: String) async throws {
try await service.toggleNotificationRead(todoId)
}
}
8 changes: 4 additions & 4 deletions DevLog/Data/Repository/TodoRepositoryImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ final class TodoRepositoryImpl: TodoRepository {
return try response.toDomain()
}

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

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

func deleteTodo(_ todoID: String) async throws {
try await todoService.deleteTodo(todoID: todoID)
func deleteTodo(_ todoId: String) async throws {
try await todoService.deleteTodo(todoId: todoId)
}
}
2 changes: 1 addition & 1 deletion DevLog/Domain/Entity/PushNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ struct PushNotification {
let body: String
let receivedAt: Date
var isRead: Bool
let todoID: String
let todoId: String
let todoKind: TodoKind
}
7 changes: 6 additions & 1 deletion DevLog/Domain/Protocol/PushNotificationRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import Combine

protocol PushNotificationRepository {
func fetchPushNotificationEnabled() async throws -> Bool
Expand All @@ -15,6 +16,10 @@ protocol PushNotificationRepository {
_ query: PushNotificationQuery,
cursor: PushNotificationCursor?
) async throws -> PushNotificationPage
func observeNotifications(
_ query: PushNotificationQuery,
limit: Int
) throws -> AnyPublisher<PushNotificationPage, Error>
func deleteNotification(_ notificationID: String) async throws
func toggleNotificationRead(_ todoID: String) async throws
func toggleNotificationRead(_ todoId: String) async throws
}
4 changes: 2 additions & 2 deletions DevLog/Domain/Protocol/TodoRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation

protocol TodoRepository {
func fetchTodos(_ query: TodoQuery, cursor: TodoCursor?) async throws -> TodoPage
func fetchTodo(_ todoID: String) async throws -> Todo
func fetchTodo(_ todoId: String) async throws -> Todo
func upsertTodo(_ todo: Todo) async throws
func deleteTodo(_ todoID: String) async throws
func deleteTodo(_ todoId: String) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
// Created by 최윤진 on 2/10/26.
//

import Combine

protocol FetchPushNotificationsUseCase {
func execute(
_ query: PushNotificationQuery,
cursor: PushNotificationCursor?
) async throws -> PushNotificationPage

func observe(
_ query: PushNotificationQuery,
limit: Int
) throws -> AnyPublisher<PushNotificationPage, Error>
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
// Created by 최윤진 on 2/10/26.
//

import Combine

final class FetchPushNotificationsUseCaseImpl: FetchPushNotificationsUseCase {
private let repository: PushNotificationRepository

Expand All @@ -18,4 +20,13 @@ final class FetchPushNotificationsUseCaseImpl: FetchPushNotificationsUseCase {
) async throws -> PushNotificationPage {
try await repository.requestNotifications(query, cursor: cursor)
}

func observe(
_ query: PushNotificationQuery,
limit: Int
) throws -> AnyPublisher<PushNotificationPage, Error> {
try repository.observeNotifications(query, limit: limit)
.removeDuplicates()
.eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
//

protocol TogglePushNotificationReadUseCase {
func execute(_ todoID: String) async throws
func execute(_ todoId: String) async throws
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class TogglePushNotificationReadUseCaseImpl: TogglePushNotificationReadUse
self.repository = repository
}

func execute(_ todoID: String) async throws {
try await repository.toggleNotificationRead(todoID)
func execute(_ todoId: String) async throws {
try await repository.toggleNotificationRead(todoId)
}
}
2 changes: 1 addition & 1 deletion DevLog/Domain/UseCase/Todo/Delete/DeleteTodoUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
//

protocol DeleteTodoUseCase {
func execute(_ todoID: String) async throws
func execute(_ todoId: String) async throws
}
4 changes: 2 additions & 2 deletions DevLog/Domain/UseCase/Todo/Delete/DeleteTodoUseCaseImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class DeleteTodoUseCaseImpl: DeleteTodoUseCase {
self.repository = repository
}

func execute(_ todoID: String) async throws {
try await repository.deleteTodo(todoID)
func execute(_ todoId: String) async throws {
try await repository.deleteTodo(todoId)
}
}
4 changes: 2 additions & 2 deletions DevLog/Domain/UseCase/Todo/Fetch/FetchTodoByIDUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
// Created by opfic on 2/15/26.
//

protocol FetchTodoByIDUseCase {
func execute(_ todoID: String) async throws -> Todo
protocol FetchTodoByIdUseCase {
func execute(_ todoId: String) async throws -> Todo
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
// Created by opfic on 2/15/26.
//

final class FetchTodoByIDUseCaseImpl: FetchTodoByIDUseCase {
final class FetchTodoByIdUseCaseImpl: FetchTodoByIdUseCase {
private let repository: TodoRepository

init(_ repository: TodoRepository) {
self.repository = repository
}

func execute(_ todoID: String) async throws -> Todo {
try await repository.fetchTodo(todoID)
func execute(_ todoId: String) async throws -> Todo {
try await repository.fetchTodo(todoId)
}
}
Loading