Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
26 changes: 22 additions & 4 deletions MOUP/MOUP/App/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,15 @@ final class AppCoordinator: Coordinator {

switch destination {
case .notificationList:
moveToNotificationList()
let type = userInfo[PushNotificationKey.type] as? String
let workerId = userInfo[PushNotificationKey.workerId] as? Int
let workplaceId = userInfo[PushNotificationKey.workplaceId] as? Int

moveToNotificationList(
pushType: type,
pushWorkerId: workerId,
pushWorkplaceId: workplaceId
)
case .routineDetail:
break
case .workDetail:
Expand Down Expand Up @@ -152,13 +160,23 @@ final class AppCoordinator: Coordinator {
tabBarCoordinator.start()
}

private func moveToNotificationList() {
private func moveToNotificationList(
pushType: String? = nil,
pushWorkerId: Int? = nil,
pushWorkplaceId: Int? = nil,
pushNotificationId: Int? = nil
) {
guard let tabBarCoordinator = childCoordinators.first(where: { $0 is TabBarCoordinator }) as? TabBarCoordinator else {
self.logger.error("TabBarCoordinator를 찾을 수 없습니다.")
return
}

tabBarCoordinator.moveToNotificationList()

tabBarCoordinator.moveToNotificationList(
pushType: pushType,
pushWorkerId: pushWorkerId,
pushWorkplaceId: pushWorkplaceId,
pushNotificationId: pushNotificationId
)
self.logger.debug("알림 리스트로 이동 완료")
}

Expand Down
40 changes: 28 additions & 12 deletions MOUP/MOUP/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,20 @@ extension AppDelegate: UNUserNotificationCenterDelegate {

private func handleNotificationTap(_ userInfo: [AnyHashable: Any]) {
let notificationTypeString = userInfo[PushNotificationKey.type] as? String
let notificationType = PushNotificationType(from: notificationTypeString)

if let notificationType = notificationType {
self.logger.debug("알림 타입: \(notificationType.rawValue)")
} else {
self.logger.warning("알림 타입이 없거나 알 수 없는 타입입니다: \(notificationTypeString ?? "nil")")
}

let destination = determineDestination(for: notificationType)
let destination = determineDestination(
for: PushNotificationType(from: notificationTypeString)
)

let workerIdString = userInfo[PushNotificationKey.workerId] as? String
let workplaceIdString = userInfo[PushNotificationKey.workplaceId] as? String
let notificationIdString = userInfo[PushNotificationKey.notificationId] as? String

postNotificationTappedEvent(
destination: destination,
type: notificationTypeString
type: notificationTypeString,
workerId: workerIdString.flatMap { Int($0) },
workplaceId: workplaceIdString.flatMap { Int($0) },
notificationId: notificationIdString.flatMap { Int($0) }
)
}

Expand All @@ -134,16 +135,31 @@ extension AppDelegate: UNUserNotificationCenterDelegate {

private func postNotificationTappedEvent(
destination: PushNotificationDestination,
type: String?
type: String?,
workerId: Int?,
workplaceId: Int?,
notificationId: Int?
) {
var userInfo: [String: Any] = [
PushNotificationKey.destination: destination.rawValue
]

if let type = type {
if let type {
userInfo[PushNotificationKey.type] = type
}

if let workerId {
userInfo[PushNotificationKey.workerId] = workerId
}

if let workplaceId {
userInfo[PushNotificationKey.workplaceId] = workplaceId
}

if let notificationId {
userInfo[PushNotificationKey.notificationId] = notificationId
}

NotificationCenter.default.post(
name: .pushNotificationTapped,
object: nil,
Expand Down
5 changes: 4 additions & 1 deletion MOUP/MOUP/Common/Enums/PushNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ enum PushNotificationDestination: String {
enum PushNotificationType: String {
case inviteApproved = "INVITE_APPROVED"
case inviteRejected = "INVITE_REJECTED"
case inviteRequest = "INVITE_REQUEST"
case inviteRequest = "WORKPLACE_JOIN_REQUEST"
case paydayReminder = "PAYDAY_REMINDER"

init?(from string: String?) {
Expand All @@ -33,4 +33,7 @@ enum PushNotificationType: String {
enum PushNotificationKey {
static let type = "type"
static let destination = "destination"
static let workerId = "workerId"
static let workplaceId = "workplaceId"
static let notificationId = "notificationId"
}
5 changes: 5 additions & 0 deletions MOUP/MOUP/Common/Utils/FCMTokenManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ final class FCMTokenManager {
return
}

guard KeychainManager.shared.read(key: "accessToken") != nil else {
logger.info("로그인 전이므로 FCM 토큰 동기화를 보류합니다.")
return
}

Task {
do {
try await authUseCase.updateFCMToken(token)
Expand Down
20 changes: 17 additions & 3 deletions MOUP/MOUP/Coordinator/HomeCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ final class HomeCoordinator: Coordinator {
private let notificationRepository: NotificationRepositoryProtocol
private let notificationUseCase: NotificationUseCaseProtocol
private let draftRoutineStorage: DraftRoutineStorageProtocol
private lazy var notificationListViewModel: NotificationListViewModel = {
return NotificationListViewModel(
notificationUseCase: notificationUseCase,
workplaceUseCase: workplaceUseCase
)
}()

init(navigationController: UINavigationController, userRole: UserRole) {
self.navigationController = navigationController
Expand Down Expand Up @@ -193,9 +199,17 @@ final class HomeCoordinator: Coordinator {
childCoordinators.removeAll { $0 === coordinator }
}

func showNotificationList() {
let viewModel = NotificationListViewModel(notificationUseCase: notificationUseCase)
let notificationListVC = NotificationListViewController(viewModel: viewModel)
func showNotificationList(
pushType: String? = nil,
pushWorkerId: Int? = nil,
pushWorkplaceId: Int? = nil
) {
let notificationListVC = NotificationListViewController(
viewModel: notificationListViewModel,
pushType: pushType,
pushWorkerId: pushWorkerId,
pushWorkplaceId: pushWorkplaceId
)
navigationController.pushViewController(notificationListVC, animated: true)
}

Expand Down
17 changes: 14 additions & 3 deletions MOUP/MOUP/Coordinator/TabBarCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,27 @@ final class TabBarCoordinator: Coordinator {
window.makeKeyAndVisible()
}

func moveToNotificationList() {
func moveToNotificationList(
pushType: String? = nil,
pushWorkerId: Int? = nil,
pushWorkplaceId: Int? = nil,
pushNotificationId: Int? = nil
) {
tabBarController.selectedIndex = 0

guard let homeCoordinator = childCoordinators.first(where: {
$0 is HomeCoordinator
}) as? HomeCoordinator else {
return
}

homeCoordinator.showNotificationList()

DispatchQueue.main.async {
homeCoordinator.showNotificationList(
pushType: pushType,
pushWorkerId: pushWorkerId,
pushWorkplaceId: pushWorkplaceId
)
}
}
}

Expand Down
45 changes: 44 additions & 1 deletion MOUP/MOUP/Data/DTO/Response/NotificationResponseDTOs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,55 @@

import Foundation

struct NotificationResponseDTO: Decodable {
struct NotificationListResponseDTO: Decodable {
let notificationList: [NotificationItemDTO]
}

struct NotificationItemDTO: Decodable {
let id: Int
let senderId: Int?
let receiverId: Int?
let title: String
let content: String
let sentAt: String
let readAt: String?

let type: String?
let workerId: Int?
let workplaceId: Int?

func toDomain() -> UserNotification {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = TimeZone(identifier: "Asia/Seoul")

let sentDate = dateFormatter.date(from: sentAt) ?? Date()
let readDate = readAt.flatMap { readAtString in
dateFormatter.date(from: readAtString)
}

let notificationType = PushNotificationType(from: type)

let metadata: NotificationMetadata?
if workerId != nil || workplaceId != nil {
metadata = NotificationMetadata(
workerId: workerId,
workplaceId: workplaceId
)
} else {
metadata = nil
}

return UserNotification(
id: id,
senderId: senderId,
receiverId: receiverId,
title: title,
content: content,
sentAt: sentDate,
readAt: readDate,
type: notificationType,
metadata: metadata
)
}
}
17 changes: 16 additions & 1 deletion MOUP/MOUP/Data/Error/NetworkError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ enum NetworkError: LocalizedError {
case noResponse
case invalidResponse(Error) // 정해진 에러 외 케이스
case decodeError // 디코딩이 원활하지 않았을 때
case badRequest // 400
case forbidden // 403
case notFound // 404
case invalidField // 422
case unknown // 기타
}

extension NetworkError {
Expand All @@ -20,7 +25,7 @@ extension NetworkError {
switch self {
case .serverError:
"현재 서버가 불안정합니다. 잠시 후 다시 시도해주세요."
case .noResponse, .invalidResponse, .decodeError:
case .noResponse, .invalidResponse, .decodeError, .badRequest, .forbidden, .notFound, .invalidField, .unknown:
"예상치 못한 결과가 발생했습니다. 잠시 후 다시 시도해주세요." // TODO: - 사용자 친화 멘트 생각해봐야 함.
}
}
Expand All @@ -36,6 +41,16 @@ extension NetworkError {
"invalidResponse: \(error.localizedDescription)"
case .decodeError:
"디코딩 과정 문제 발생"
case .badRequest:
"잘못된 요청입니다."
case .forbidden:
"권한이 없습니다."
case .notFound:
"요청한 정보를 찾을 수 없습니다."
case .invalidField:
"유효하지 않은 값입니다."
case .unknown:
"알 수 없는 오류가 발생했습니다."
}
}
}
2 changes: 2 additions & 0 deletions MOUP/MOUP/Data/Repositories/AuthRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ final class AuthRepository: AuthRepositoryProtocol {

UserDefaultsManager.shared.userRole = role
UserDefaultsManager.shared.hasSignIn = true

FCMTokenManager.shared.syncTokenToServer()
}

func signUp(requestDTO: RegisterRequestDTO) async throws -> UserRole {
Expand Down
39 changes: 2 additions & 37 deletions MOUP/MOUP/Data/Repositories/NotificationRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ final class NotificationRepository: NotificationRepositoryProtocol {
}

func fetchNotifications() async throws -> [UserNotification] {
let dtos = try await notificationService.fetchNotifications()
return dtos.map { mapToEntity($0) }
let response = try await notificationService.fetchNotifications()
return response.notificationList.map { $0.toDomain() }
}

func markAsRead(id: Int) async throws {
Expand All @@ -34,39 +34,4 @@ final class NotificationRepository: NotificationRepositoryProtocol {
func deleteAllNotifications() async throws {
try await notificationService.deleteAllNotifications()
}

private func mapToEntity(_ dto: NotificationResponseDTO) -> UserNotification {
let sentDate = parseDate(dto.sentAt)
let readDate = dto.readAt.flatMap { parseDate($0) }

return UserNotification(
id: dto.id,
senderId: dto.senderId,
receiverId: dto.receiverId,
title: dto.title,
content: dto.content,
sentAt: sentDate,
readAt: readDate
)
}

private func parseDate(_ dateString: String) -> Date {
let formats = [
"yyyy-MM-dd'T'HH:mm:ss",
"yyyy-MM-dd'T'HH:mm:ss.SSS",
"yyyy-MM-dd'T'HH:mm:ss.SSSSSS"
]

for format in formats {
let formatter = DateFormatter()
formatter.dateFormat = format
formatter.timeZone = TimeZone(identifier: "UTC")

if let date = formatter.date(from: dateString) {
return date
}
}

return Date()
}
}
13 changes: 13 additions & 0 deletions MOUP/MOUP/Data/Repositories/WorkplaceRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,17 @@ final class WorkplaceRepository: WorkplaceRepositoryProtocol {
try await workplaceService.updateWorkplace(workplaceId: workplaceId, request: request)
}

func approveJoinRequest(workplaceId: Int, workerId: Int) async throws {
try await workplaceService.approveJoinRequest(
workplaceId: workplaceId,
workerId: workerId
)
}

func rejectJoinRequest(workplaceId: Int, workerId: Int) async throws {
try await workplaceService.rejectJoinRequest(
workplaceId: workplaceId,
workerId: workerId
)
}
}
Loading