Skip to content

Commit 717335c

Browse files
authored
[#31] PushNotificationView를 Store 프로토콜을 채택한 뷰모델을 사용하여 재구성한다 (#58)
* refactor: 인프라 DTO 형태로 개선 * feat: 데이터 레이어 구현 * refactor: 도메인 모델 재정의 * feat: 유즈케이스 구현 * feat: 유즈케이스 의존성 등록 * style: 얼럿 메시지 수정 * feat: 유즈케이스 적용 및 UI 구현 * style: 개행 제거 * feat: 토스트 구현 * feat: 탭 액션이 가능하면 텍스트가 파란색으로 되고, 사라질 때 액션을 호출할 수 있도록 추가 * feat: 토스트를 탭 하면 알람 리스트에서 제거를 취소하도록 추가 * feat: 토스트 내용을 문자열이 아닌 뷰로 확장 * feat: 토스트 확장 * feat: 읽지 않은 알림이면 파란색 점을 보여주고, 받은 시간과의 차를 실시간으로 보여주도록 추가
1 parent c1cdb1a commit 717335c

16 files changed

Lines changed: 501 additions & 100 deletions

DevLog/App/Assembler/DomainAssembler.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,13 @@ final class DomainAssembler: Assembler {
6262
container.register(DeleteWebPageUseCase.self) {
6363
DeleteWebPageUseCaseImpl(container.resolve(WebPageRepository.self))
6464
}
65+
66+
container.register(DeletePushNotificationUseCase.self) {
67+
DeletePushNotificationUseCaseImpl(container.resolve(PushNotificationRepository.self))
68+
}
69+
70+
container.register(FetchPushNotificationsUseCase.self) {
71+
FetchPushNotificationsUseCaseImpl(container.resolve(PushNotificationRepository.self))
72+
}
6573
}
6674
}

DevLog/Data/Repository/PushNotificationRepositoryImpl.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,42 @@ final class PushNotificationRepositoryImpl: PushNotificationRepository {
1414
self.service = pushNotificationService
1515
}
1616

17+
/// 푸시 알림 On/Off 설정
1718
func fetchPushNotificationEnabled() async throws -> Bool {
1819
return try await service.fetchPushNotificationEnabled()
1920
}
2021

22+
/// 푸시 알림 시간 설정
2123
func fetchPushNotificationTime() async throws -> DateComponents {
2224
return try await service.fetchPushNotificationTime()
2325
}
2426

27+
/// 푸시 알림 설정 업데이트
2528
func updatePushNotificationSettings(_ settings: PushNotificationSettings) async throws {
2629
try await service.updatePushNotificationSettings(
2730
isEnabled: settings.isEnabled, components: settings.scheduledTime
2831
)
2932
}
33+
34+
/// 푸시 알림 기록 요청
35+
func requestNotifications() async throws -> [PushNotification] {
36+
try await service.requestNotifications()
37+
.compactMap { dto in
38+
dto.id.map { id in
39+
PushNotification(
40+
id: id,
41+
title: dto.title,
42+
body: dto.body,
43+
receivedAt: dto.receivedAt.dateValue(),
44+
isRead: dto.isRead,
45+
todoID: dto.todoID
46+
)
47+
}
48+
}
49+
}
50+
51+
// 푸시 알림 기록 삭제
52+
func deleteNotification(_ notificationID: String) async throws {
53+
try await service.deleteNotification(notificationID)
54+
}
3055
}

DevLog/Domain/Protocol/PushNotificationRepository.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@ protocol PushNotificationRepository {
1111
func fetchPushNotificationEnabled() async throws -> Bool
1212
func fetchPushNotificationTime() async throws -> DateComponents
1313
func updatePushNotificationSettings(_ settings: PushNotificationSettings) async throws
14+
func requestNotifications() async throws -> [PushNotification]
15+
func deleteNotification(_ notificationID: String) async throws
1416
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// DeletePushNotificationUseCase.swift
3+
// DevLog
4+
//
5+
// Created by 최윤진 on 2/10/26.
6+
//
7+
8+
protocol DeletePushNotificationUseCase {
9+
func execute(_ notificationID: String) async throws
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// DeletePushNotificationUseCaseImpl.swift
3+
// DevLog
4+
//
5+
// Created by 최윤진 on 2/10/26.
6+
//
7+
8+
final class DeletePushNotificationUseCaseImpl: DeletePushNotificationUseCase {
9+
private let repository: PushNotificationRepository
10+
11+
init(_ repository: PushNotificationRepository) {
12+
self.repository = repository
13+
}
14+
15+
func execute(_ notificationID: String) async throws {
16+
try await repository.deleteNotification(notificationID)
17+
}
18+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//
2+
// FetchPushNotificationsUseCase.swift
3+
// DevLog
4+
//
5+
// Created by 최윤진 on 2/10/26.
6+
//
7+
8+
protocol FetchPushNotificationsUseCase {
9+
func execute() async throws -> [PushNotification]
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// FetchPushNotificationsUseCaseImpl.swift
3+
// DevLog
4+
//
5+
// Created by 최윤진 on 2/10/26.
6+
//
7+
8+
final class FetchPushNotificationsUseCaseImpl: FetchPushNotificationsUseCase {
9+
private let repository: PushNotificationRepository
10+
11+
init(_ repository: PushNotificationRepository) {
12+
self.repository = repository
13+
}
14+
15+
func execute() async throws -> [PushNotification] {
16+
try await repository.requestNotifications()
17+
}
18+
}

DevLog/Infra/DTO/PushNotification.swift

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

88
import Foundation
9-
import FirebaseFirestore
109

11-
struct PushNotification: Codable, Identifiable {
12-
@DocumentID var id: String?
13-
let title: String // 알림 제목
14-
let body: String // 알림 내용
15-
let receivedDate: Date // 알림 수신 날짜
16-
var isRead: Bool // 알림 읽음 여부
17-
let todoId: String // Todo ID
18-
19-
init(from: QueryDocumentSnapshot) {
20-
self.id = from.documentID
21-
self.title = from["title"] as? String ?? ""
22-
self.body = from["body"] as? String ?? ""
23-
self.receivedDate = (from["receivedDate"] as? Timestamp)?.dateValue() ?? Date()
24-
self.isRead = from["isRead"] as? Bool ?? false
25-
self.todoId = from["todoId"] as? String ?? ""
26-
}
27-
28-
init(id: String? = nil, title: String, body: String, receivedDate: Date, isRead: Bool, todoId: String) {
29-
self.id = id
30-
self.title = title
31-
self.body = body
32-
self.receivedDate = receivedDate
33-
self.isRead = isRead
34-
self.todoId = todoId
35-
}
36-
37-
func toDictionary() -> [String: Any] {
38-
return [
39-
"title": title,
40-
"body": body,
41-
"receivedDate": Timestamp(date: receivedDate),
42-
"isRead": isRead,
43-
"todoId": todoId
44-
]
45-
}
10+
struct PushNotification: Identifiable {
11+
let id: String
12+
let title: String
13+
let body: String
14+
let receivedAt: Date
15+
var isRead: Bool
16+
let todoID: String
4617
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// PushNotificationResponse.swift
3+
// DevLog
4+
//
5+
// Created by 최윤진 on 2/10/26.
6+
//
7+
8+
import FirebaseFirestore
9+
10+
struct PushNotificationResponse: Decodable {
11+
@DocumentID var id: String?
12+
let title: String
13+
let body: String
14+
let receivedAt: Timestamp
15+
let isRead: Bool
16+
let todoID: String
17+
}

DevLog/Infra/Service/PushNotificationService.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,19 @@ final class PushNotificationService {
6666
try await settingsRef.setData(dict, merge: true)
6767
}
6868

69-
/// 푸시 알림 데이터 요청
70-
func requestNotification() async throws -> [PushNotification] {
69+
/// 푸시 알림 기록 요청
70+
func requestNotifications() async throws -> [PushNotificationResponse] {
7171
guard let uid = Auth.auth().currentUser?.uid else { throw AuthError.notAuthenticated }
7272

7373
let collection = store.collection("users/\(uid)/notifications")
74-
7574
let snapshot = try await collection.getDocuments()
76-
77-
return snapshot.documents.compactMap { PushNotification(from: $0) }
75+
76+
return try snapshot.documents.compactMap { document in
77+
try document.data(as: PushNotificationResponse.self)
78+
}
7879
}
7980

80-
/// 푸시 알림 데이터 삭제
81+
/// 푸시 알림 기록 삭제
8182
func deleteNotification(_ notificationID: String) async throws {
8283
guard let uid = Auth.auth().currentUser?.uid else { throw AuthError.notAuthenticated }
8384

0 commit comments

Comments
 (0)