From 6a4c56447acbb52dfcc7dea7ef55024cdec3f040 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 5 Jun 2026 16:58:10 +0900 Subject: [PATCH 01/26] =?UTF-8?q?refactor:=20#437=20=EA=B8=B0=EC=A1=B4=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B0=8F=20API,=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC,=20=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift | 2 +- ...otificationAPI.swift => NotificationTokenAPI.swift} | 4 ++-- .../ByeBoo-iOS/Data/Repository/AuthRepository.swift | 4 ++-- ...ository.swift => NotificationTokenRepository.swift} | 10 +++++----- ...nterface.swift => NotificationTokenInterface.swift} | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/{NotificationAPI.swift => NotificationTokenAPI.swift} (95%) rename ByeBoo-iOS/ByeBoo-iOS/Data/Repository/{NotificationRepository.swift => NotificationTokenRepository.swift} (89%) rename ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/{NotificationInterface.swift => NotificationTokenInterface.swift} (88%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift b/ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift index 1bce5697..5ab4cde4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/App/AppDelegate.swift @@ -48,7 +48,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { - guard let notificationRepository = DIContainer.shared.resolve(type: DefaultNotificationRepository.self) else { + guard let notificationRepository = DIContainer.shared.resolve(type: DefaultNotificationTokenRepository.self) else { return } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift similarity index 95% rename from ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift rename to ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift index c77364c0..acb0f6e3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift @@ -9,13 +9,13 @@ import Foundation import Alamofire -enum NotificationAPI { +enum NotificationTokenAPI { case saveToken(dto: FCMTokenDTO) case updateToken(dto: FCMTokenDTO) case deleteToken(dto: FCMTokenDTO) } -extension NotificationAPI: EndPoint { +extension NotificationTokenAPI: EndPoint { var basePath: String { return "/api/v1/notification-tokens" diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift index c25722e2..394f3506 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/AuthRepository.swift @@ -89,7 +89,7 @@ struct DefaultAuthRepository: AuthInterface { let fcmToken = try await Messaging.messaging().token() let fcmTokenDTO = FCMTokenDTO(token: fcmToken) try await network.request( - NotificationAPI.saveToken(dto: fcmTokenDTO) + NotificationTokenAPI.saveToken(dto: fcmTokenDTO) ) } catch (let error) { ByeBooLogger.error(error) @@ -127,7 +127,7 @@ struct DefaultAuthRepository: AuthInterface { return false } try await network.request( - NotificationAPI.deleteToken(dto: .init(token: fcmToken)) + NotificationTokenAPI.deleteToken(dto: .init(token: fcmToken)) ) } catch (let error) { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift similarity index 89% rename from ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift rename to ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift index 4a136854..47063b8e 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift @@ -5,7 +5,7 @@ // Created by APPLE on 11/22/25. // -struct DefaultNotificationRepository: NotificationInterface { +struct DefaultNotificationTokenRepository: NotificationTokenInterface { private let network: NetworkService private let userDefaultsService: UserDefaultService @@ -38,7 +38,7 @@ struct DefaultNotificationRepository: NotificationInterface { let fcmTokenDTO = createDTO(token: token) try await network.request( - NotificationAPI.saveToken(dto: fcmTokenDTO) + NotificationTokenAPI.saveToken(dto: fcmTokenDTO) ) saveToken(token: token) } @@ -53,7 +53,7 @@ struct DefaultNotificationRepository: NotificationInterface { let fcmTokenDTO = createDTO(token: token) try await network.request( - NotificationAPI.updateToken(dto: fcmTokenDTO) + NotificationTokenAPI.updateToken(dto: fcmTokenDTO) ) saveToken(token: token) } @@ -66,7 +66,7 @@ struct DefaultNotificationRepository: NotificationInterface { let fcmTokenDTO = createDTO(token: token) let accessToken = keychainService.load(key: .accessToken) try await network.request( - NotificationAPI.deleteToken(dto: fcmTokenDTO) + NotificationTokenAPI.deleteToken(dto: fcmTokenDTO) ) let _ = userDefaultsService.delete(key: .fcmToken) } @@ -76,7 +76,7 @@ struct DefaultNotificationRepository: NotificationInterface { } } -final class MockNotificationRepository: NotificationInterface { +final class MockNotificationTokenRepository: NotificationTokenInterface { private let userDefaultsService: UserDefaultService var sendTokenCalled = false diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationTokenInterface.swift similarity index 88% rename from ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift rename to ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationTokenInterface.swift index 05cc0709..3c92561a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationTokenInterface.swift @@ -5,7 +5,7 @@ // Created by APPLE on 11/26/25. // -protocol NotificationInterface { +protocol NotificationTokenInterface { func loadToken() -> String? func sendToken(token: String) async throws func saveToken(token: String) From 23d3ef2654e58e3cd323171fd754e447d4917a17 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:15:54 +0900 Subject: [PATCH 02/26] =?UTF-8?q?feat:=20#437=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/DataDependencyAssembler.swift | 4 ++ .../Model/NotificationListReponseDTO.swift | 41 ++++++++++++ .../Network/EndPoint/NotificationAPI.swift | 63 +++++++++++++++++++ .../Repository/NotificationRepository.swift | 23 +++++++ .../Domain/DomainDependencyAssembler.swift | 17 +++-- .../Entity/NotificationListEntity.swift | 20 ++++++ .../Interface/NotificationInterface.swift | 10 +++ .../FetchNotificationListUseCase.swift | 23 +++++++ .../PresentationDependencyAssembler.swift | 7 ++- 9 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchNotificationListUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift index 4304c2bb..d5393fbd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/DataDependencyAssembler.swift @@ -67,6 +67,10 @@ struct DataDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: ReportsInterface.self) { _ in return DefaultReportsRepository(networkService: networkService) } + + DIContainer.shared.register(type: NotificationInterface.self) { _ in + return DefaultNotificationRepository(networkService: networkService) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift new file mode 100644 index 00000000..db9191f4 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift @@ -0,0 +1,41 @@ +// +// NotificationListReponseDTO.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +struct NotificationListReponseDTO: Decodable { + let notifications: [NotificationResponseDTO] +} + +struct NotificationResponseDTO: Decodable { + let notificationID: Int + let notificationType: String + let title: String + let content: String + let isRead: Bool + let createdAt: String + let landingURL: String +} + +extension NotificationListReponseDTO { + func toEntity() -> NotificationListEntity { + let notifications = notifications.map { $0.toEntity() } + return .init(notifications: notifications) + } +} + +extension NotificationResponseDTO { + func toEntity() -> NotificationEntity { + .init( + notificationID: notificationID, + notificationType: notificationType, + title: title, + content: content, + isRead: isRead, + createdAt: createdAt, + landingURL: landingURL + ) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift new file mode 100644 index 00000000..34a270f3 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift @@ -0,0 +1,63 @@ +// +// NotificationAPI.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +import Foundation + +import Alamofire + +enum NotificationAPI { + case fetchNotificationList +} + +extension NotificationAPI: EndPoint { + + var basePath: String { + return "/api/v1/notifications" + } + + var path: String { + switch self { + case .fetchNotificationList: + "" + } + } + + var method: HTTPMethod { + switch self { + case .fetchNotificationList: + .get + } + } + + var headers: HeaderType { + switch self { + case .fetchNotificationList: + .withAuth + } + } + + var parameterEncoding: ParameterEncoding { + switch self { + case .fetchNotificationList: + JSONEncoding.default + } + } + + var queryParameters: [String : String]? { + switch self { + case .fetchNotificationList: + nil + } + } + + var bodyParameters: Parameters? { + switch self { + case .fetchNotificationList: + nil + } + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift new file mode 100644 index 00000000..26c704b5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift @@ -0,0 +1,23 @@ +// +// NotificationRepository.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +struct DefaultNotificationRepository: NotificationInterface { + + private let networkService: NetworkService + + init(networkService: NetworkService) { + self.networkService = networkService + } + + func fetchNotifications() async throws -> NotificationListEntity { + let result = try await networkService.request( + NotificationAPI.fetchNotificationList, + decodingType: NotificationListReponseDTO.self + ) + return result.toEntity() + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index b79e9a4f..44a6f4bd 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -23,10 +23,11 @@ struct DomainDependencyAssembler: DependencyAssembler { let forbiddenWordRepository = DIContainer.shared.resolve(type: ForbiddenWordInterface.self), let commonQuestRepository = DIContainer.shared.resolve(type: CommonQuestInterface.self), let blocksRepository = DIContainer.shared.resolve(type: BlocksInterface.self), - let reportsRepository = DIContainer.shared.resolve(type: ReportsInterface.self) else { - ByeBooLogger.error(ByeBooError.DIFailedError) - return - } + let reportsRepository = DIContainer.shared.resolve(type: ReportsInterface.self), + let notificationRepository = DIContainer.shared.resolve(type: NotificationInterface.self) else { + ByeBooLogger.error(ByeBooError.DIFailedError) + return + } DIContainer.shared.register(type: FetchUserJourneyUseCase.self) { _ in return DefaultFetchUserJourneyUseCase(repository: userRepository) @@ -171,7 +172,7 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchCommonQuestMyAnswersUseCase.self) { _ in return DefaultFetchCommonQuestMyAnswersUseCase(repository: userRepository) } - + DIContainer.shared.register(type: FetchAIAnswerUseCase.self) { _ in return DefaultFetchAIAnswerUseCase(repository: questRepository) } @@ -187,7 +188,7 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: UpdateCommonQuestUseCase.self) { _ in return DefaultUpdateCommonQuestUseCase(repository: commonQuestRepository) } - + DIContainer.shared.register(type: BlockUserUseCase.self) { _ in return DefaultBlockUserCase(repository: blocksRepository) } @@ -207,5 +208,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: DeleteCommonQuestUseCase.self) { _ in return DefaultDeleteCommonQuestUseCase(repository: commonQuestRepository) } + + DIContainer.shared.register(type: FetchNotificationListUseCase.self) { _ in + return DefaultFetchNotificationListUseCase(repository: notificationRepository) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift new file mode 100644 index 00000000..42f7f4e8 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift @@ -0,0 +1,20 @@ +// +// NotificationListEntity.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +struct NotificationListEntity { + let notifications: [NotificationEntity] +} + +struct NotificationEntity { + let notificationID: Int + let notificationType: String + let title: String + let content: String + let isRead: Bool + let createdAt: String + let landingURL: String +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift new file mode 100644 index 00000000..05b07428 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift @@ -0,0 +1,10 @@ +// +// NotificationInterface.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +protocol NotificationInterface { + func fetchNotifications() async throws -> NotificationListEntity +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchNotificationListUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchNotificationListUseCase.swift new file mode 100644 index 00000000..bb0dec12 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchNotificationListUseCase.swift @@ -0,0 +1,23 @@ +// +// FetchNotificationListUseCase.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/5/26. +// + +protocol FetchNotificationListUseCase { + func execute() async throws -> NotificationListEntity +} + +struct DefaultFetchNotificationListUseCase: FetchNotificationListUseCase { + + private let repository: NotificationInterface + + init(repository: NotificationInterface) { + self.repository = repository + } + + func execute() async throws -> NotificationListEntity { + try await repository.fetchNotifications() + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 8ecf838a..79511f67 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -107,7 +107,9 @@ struct PresentationDependencyAssembler: DependencyAssembler { let setHelperUseCase = container.resolve(type: SetHelperUseCase.self), let fetchUserJourneyUseCase = container.resolve(type: FetchUserJourneyUseCase.self), let getUserNameUseCase = container.resolve(type: GetUserNameUseCase.self), - let getHelperUseCase = container.resolve(type: GetHelperUseCase.self) else { + let getHelperUseCase = container.resolve(type: GetHelperUseCase.self), + let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) + else { ByeBooLogger.error(ByeBooError.DIFailedError) return } @@ -118,7 +120,8 @@ struct PresentationDependencyAssembler: DependencyAssembler { fetchUserJourneyUseCase: fetchUserJourneyUseCase, getUserNameUseCase: getUserNameUseCase, setHelperUseCase: setHelperUseCase, - getHelperUseCase: getHelperUseCase + getHelperUseCase: getHelperUseCase, + fetchNotificationListUseCase: fetchNotificationListUseCase ) } From 41ab7ecd464083971e966dbd92a04d4936f9f535 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:17:14 +0900 Subject: [PATCH 03/26] =?UTF-8?q?feat:=20#437=20=ED=99=88=20=EB=B7=B0?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=EC=97=90=EC=84=9C=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewModel/HomeViewModel.swift | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index 18458a7e..fd16ee7a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -22,13 +22,15 @@ final class HomeViewModel { private var isHelperShownResultSubject = CurrentValueSubject(true) private var homeStateResultSubject = PassthroughSubject, Never>() private var journeyResultSubject = PassthroughSubject, Never>() - + private var notificationResultSubject = PassthroughSubject, Never>() + private let fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase private let fetchQuestStatusUseCase: FetchQuestStatusUseCase private let fetchUserJourneyUseCase: FetchUserJourneyUseCase private let getUserNameUseCase: GetUserNameUseCase private let setHelperUseCase: SetHelperUseCase private let getHelperUseCase: GetHelperUseCase + private let fetchNotificationListUseCase: FetchNotificationListUseCase init( fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase, @@ -36,7 +38,8 @@ final class HomeViewModel { fetchUserJourneyUseCase: FetchUserJourneyUseCase, getUserNameUseCase: GetUserNameUseCase, setHelperUseCase: SetHelperUseCase, - getHelperUseCase: GetHelperUseCase + getHelperUseCase: GetHelperUseCase, + fetchNotificationListUseCase: FetchNotificationListUseCase ) { self.fetchCharacterDialogueUseCase = fetchCharacterDialogueUseCase self.fetchQuestStatusUseCase = fetchQuestStatusUseCase @@ -44,13 +47,15 @@ final class HomeViewModel { self.getUserNameUseCase = getUserNameUseCase self.setHelperUseCase = setHelperUseCase self.getHelperUseCase = getHelperUseCase + self.fetchNotificationListUseCase = fetchNotificationListUseCase output = Output( characterResult: characterResultSubject.eraseToAnyPublisher(), userResult: userResultSubject.eraseToAnyPublisher(), helperResult: isHelperShownResultSubject.eraseToAnyPublisher(), homeStateResult: homeStateResultSubject.eraseToAnyPublisher(), - journeyResult: journeyResultSubject.eraseToAnyPublisher() + journeyResult: journeyResultSubject.eraseToAnyPublisher(), + notificationResult: notificationResultSubject.eraseToAnyPublisher() ) } } @@ -137,7 +142,6 @@ extension HomeViewModel { } private func isHelperShown(state: HomeState) { - if !getHelperUseCase.execute() && state == .beforeJourneyStart { isHelperShownResultSubject.send(false) } else { @@ -148,4 +152,13 @@ extension HomeViewModel { private func setHelperShown() { setHelperUseCase.execute() } + + private func fetchNotificationList() async { + do { + let notifications = try await fetchNotificationListUseCase.execute() + notificationResultSubject.send(.success(notifications)) + } catch { + notificationResultSubject.send(.failure(error as? ByeBooError ?? ByeBooError.unknownError)) + } + } } From ab1284d8cc00599387959baea288c546b4285005 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 5 Jun 2026 23:17:49 +0900 Subject: [PATCH 04/26] =?UTF-8?q?refactor:=20#437=20=ED=99=88=20=EB=B7=B0?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=20viewWillAppear=20=ED=8A=B8=EB=A6=AC?= =?UTF-8?q?=EA=B1=B0=20=EC=8B=9C=20=EA=B5=AC=EC=A1=B0=EC=A0=81=20=EB=8F=99?= =?UTF-8?q?=EC=8B=9C=EC=84=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewModel/HomeViewModel.swift | 79 ++++++++++--------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index fd16ee7a..0d0d7869 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -72,17 +72,24 @@ extension HomeViewModel: ViewModelType { let helperResult: AnyPublisher let homeStateResult: AnyPublisher, Never> let journeyResult: AnyPublisher, Never> + let notificationResult: AnyPublisher, Never> } func action(_ trigger: Input) { switch trigger { case .viewWillAppear: - // TODO: 구조적 동시성 반영 - fetchDialogue() - fetchStatus() - fetchJourney() - getUserResult() + + Task { + await withTaskGroup(of: Void.self) { group in + group.addTask { await self.fetchDialogue() } + group.addTask { await self.fetchStatus() } + group.addTask { await self.fetchJourney() } + group.addTask { await self.fetchNotificationList() } + + await group.waitForAll() + } + } case .helperDidTap: setHelperShown() } @@ -90,49 +97,43 @@ extension HomeViewModel: ViewModelType { } extension HomeViewModel { - private func fetchDialogue() { - Task { - do { - let dialogues = try await fetchCharacterDialogueUseCase.execute() - characterResultSubject.send(.success(dialogues)) - } catch { - characterResultSubject.send( - .failure( - error as? ByeBooError ?? ByeBooError.unknownError - ) + private func fetchDialogue() async { + do { + let dialogues = try await fetchCharacterDialogueUseCase.execute() + characterResultSubject.send(.success(dialogues)) + } catch { + characterResultSubject.send( + .failure( + error as? ByeBooError ?? ByeBooError.unknownError ) - } + ) } } - private func fetchStatus() { - Task { - do { - let status = try await fetchQuestStatusUseCase.execute() - homeStateResultSubject.send(.success(status)) - isHelperShown(state: status.currentStatus) - ByeBooLogger.debug("home status: \(status)") - } catch { - if let error = error as? ByeBooError { - homeStateResultSubject.send(.failure(error)) - } - isHelperShown(state: .beforeJourneyStart) + private func fetchStatus() async { + do { + let status = try await fetchQuestStatusUseCase.execute() + homeStateResultSubject.send(.success(status)) + isHelperShown(state: status.currentStatus) + ByeBooLogger.debug("home status: \(status)") + } catch { + if let error = error as? ByeBooError { + homeStateResultSubject.send(.failure(error)) } + isHelperShown(state: .beforeJourneyStart) } } - private func fetchJourney() { - Task { - do { - let journey = try await fetchUserJourneyUseCase.execute() - journeyResultSubject.send(.success(journey)) - } catch { - journeyResultSubject.send( - .failure( - error as? ByeBooError ?? ByeBooError.unknownError - ) + private func fetchJourney() async { + do { + let journey = try await fetchUserJourneyUseCase.execute() + journeyResultSubject.send(.success(journey)) + } catch { + journeyResultSubject.send( + .failure( + error as? ByeBooError ?? ByeBooError.unknownError ) - } + ) } } From 863544a6097f0c62f7c26eb3e40b4e512705b06a Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 6 Jun 2026 12:36:46 +0900 Subject: [PATCH 05/26] =?UTF-8?q?chore:=20#437=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EB=B9=84=EC=A0=80=EB=8B=9D=20=ED=8C=8C=EC=9D=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj index 814438d8..2387f417 100644 --- a/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj +++ b/ByeBoo-iOS/ByeBoo-iOS.xcodeproj/project.pbxproj @@ -329,7 +329,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS 1780581426"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; @@ -349,7 +349,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "ByeBoo-iOS/ByeBoo-Prod.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 10; DEVELOPMENT_TEAM = ""; @@ -372,7 +372,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.heartz.ByeBoo-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match AppStore com.heartz.ByeBoo-iOS"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.heartz.ByeBoo-iOS 1780581426"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; From a59529b2d7c0db280261eefb67e6dc6ea09d6b77 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:35:14 +0900 Subject: [PATCH 06/26] =?UTF-8?q?chore:=20#437=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=EB=AA=85=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Home/View/Notice/NoticeCardCell.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift index dc597d0a..7c26b48a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -86,12 +86,12 @@ extension NoticeCardCell { iconImage: UIImage, title: String, subtitle: String, - writtenTiem: String + writtenTime: String ) { self.backgroundColor = backgroundColor noticeImageView.image = iconImage titleLabel.text = title subtitleLabel.text = subtitle - writtenTimeLabel.text = writtenTiem + writtenTimeLabel.text = writtenTime } } From 944801b88ebc0d04f5de3ff82e57a92ca246c184 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sat, 6 Jun 2026 22:36:09 +0900 Subject: [PATCH 07/26] =?UTF-8?q?refactor:=20#437=20NoticesViewController?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=EC=9E=90=EC=97=90=EC=84=9C=20=EC=95=8C?= =?UTF-8?q?=EB=9E=8C=20=EC=9C=A0=EB=AC=B4=20=EC=A3=BC=EC=9E=85=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 11 +++-------- .../ViewController/NoticesViewController.swift | 18 ++++++++++++++---- .../Feature/Home/ViewModel/HomeViewModel.swift | 2 ++ .../Presentation/ViewControllerFactory.swift | 4 ++-- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 89abd74c..97cf8858 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -22,7 +22,6 @@ final class HomeViewController: BaseViewController { private var isFirstVisit: Bool = true private var journeyType: JourneyType = .recording private var isAnimating: Bool = false - private var isExistNotice: Bool? init(viewModel: HomeViewModel) { self.viewModel = viewModel @@ -61,9 +60,6 @@ final class HomeViewController: BaseViewController { if isFirstVisit { isFirstVisit.toggle() } self.navigationController?.setNavigationBarHidden(true, animated: false) - // 추후 API 연동 시 뷰모델 액션을 호출하여 알림 유무를 판단할 예정 - rootView.headerView.updateNotice(isExist: true) - isExistNotice = true } override func setAddTarget() { @@ -121,11 +117,10 @@ extension HomeViewController { } @objc - private func noticeButtonDidTap() { - guard let isExistNotice else { return } - - let viewController = ViewControllerFactory.shared.makeNoticesViewController(isExistNotice: isExistNotice) + private func noticeButtonDidTap() { + let viewController = ViewControllerFactory.shared.makeNoticesViewController() viewController.hidesBottomBarWhenPushed = true + viewController.configure(notificationList: viewModel.notifications) self.navigationController?.setNavigationBarHidden(false, animated: false) self.navigationController?.pushViewController(viewController, animated: false) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift index 78046246..cd33f35d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift @@ -9,11 +9,10 @@ import UIKit final class NoticesViewController: BaseViewController { - private let isExistNotice: Bool private let rootView = NoticesView() + private var notificationList: NotificationListEntity? - init(isExistNotice: Bool) { - self.isExistNotice = isExistNotice + init() { super.init(nibName: nil, bundle: nil) } @@ -35,7 +34,11 @@ final class NoticesViewController: BaseViewController { action: #selector(back) ) - rootView.contentView.decideNoticeContent(isExistNotice: isExistNotice) + guard let notifications = notificationList?.notifications else { + return + } + + rootView.contentView.decideNoticeContent(isExistNotice: !notifications.isEmpty) } override func setAddTarget() { @@ -68,6 +71,13 @@ extension NoticesViewController { private func readAllButtonDidTap() {} } +extension NoticesViewController { + + func configure(notificationList: NotificationListEntity?) { + self.notificationList = notificationList + } +} + extension NoticesViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index 0d0d7869..af9768c4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -15,6 +15,7 @@ final class HomeViewModel { var dialoguesResult: Result { characterResultSubject.value } + private(set) var notifications: NotificationListEntity? private(set) var output: Output private var characterResultSubject = CurrentValueSubject, Never>(.failure(ByeBooError.noData)) @@ -157,6 +158,7 @@ extension HomeViewModel { private func fetchNotificationList() async { do { let notifications = try await fetchNotificationListUseCase.execute() + self.notifications = notifications notificationResultSubject.send(.success(notifications)) } catch { notificationResultSubject.send(.failure(error as? ByeBooError ?? ByeBooError.unknownError)) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index dd356892..d4293e4b 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -225,8 +225,8 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { return .init(viewModel: viewModel) } - func makeNoticesViewController(isExistNotice: Bool) -> NoticesViewController { - .init(isExistNotice: isExistNotice) + func makeNoticesViewController() -> NoticesViewController { + .init() } } From 7907672df46c1a137a22556a9fc75581041e2e02 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:32:26 +0900 Subject: [PATCH 08/26] =?UTF-8?q?refactor:=20#437=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=86=A0=EC=BD=9C=EC=97=90=20=EC=A0=95=EC=9D=98=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=93=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Presentation/ViewControllerFactory.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index d4293e4b..0dd5b375 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -26,6 +26,14 @@ protocol ViewControllerFactoryProtocol { func makeWriteActiveTypeQuestViewController() -> WriteActiveTypeQuestViewController func makeFinishJourneyViewController() -> FinishJourneyViewController func makeCommonQuestBottomSheetViewController() -> CommonQuestBottomSheetViewController + func makeCompletedQuestsViewController() -> CompletedQuestsViewController + func makeParentQuestViewController() -> ParentQuestViewController + func makeCommonQuestViewController() -> CommonQuestViewController + func makeCommonQuestHistoryViewController() -> CommonQuestHistoryViewController + func makeCommonQuestMyAnswersViewController() -> CommonQuestMyAnswersViewController + func makeBlockedUserListViewController() -> BlockedkUserListViewController + func makeAIAnswerViewController() -> AIAnswerViewController + func makeNotificationsViewController() -> NotificationsViewController } final class ViewControllerFactory: ViewControllerFactoryProtocol { From 1a6a86c9546eaf4112cecdf460483d2b17ec0e26 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:35:37 +0900 Subject: [PATCH 09/26] =?UTF-8?q?feat:=20#437=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EC=B0=A8=EC=9D=B4=EB=A5=BC=20=EA=B3=84=EC=82=B0=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=A4=ED=95=91=ED=95=98=EB=8A=94=20=EC=9C=A0?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=9D=B4=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/DomainDependencyAssembler.swift | 4 ++ .../UseCase/FormatElapsedTimeUseCase.swift | 40 +++++++++++++++++++ .../ViewModel/CommonQuestViewModel.swift | 31 ++++---------- .../PresentationDependencyAssembler.swift | 15 ++++++- 4 files changed, 64 insertions(+), 26 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FormatElapsedTimeUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index 44a6f4bd..acb630a6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -212,5 +212,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FetchNotificationListUseCase.self) { _ in return DefaultFetchNotificationListUseCase(repository: notificationRepository) } + + DIContainer.shared.register(type: FormatElapsedTimeUseCase.self) { _ in + return DefaultFormatElapsedTimeUseCase() + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FormatElapsedTimeUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FormatElapsedTimeUseCase.swift new file mode 100644 index 00000000..23d14196 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FormatElapsedTimeUseCase.swift @@ -0,0 +1,40 @@ +// +// FormatElapsedTimeUseCase.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/7/26. +// + +import Foundation + +protocol FormatElapsedTimeUseCase { + func execute(from timeString: String) -> String? +} + +struct DefaultFormatElapsedTimeUseCase: FormatElapsedTimeUseCase { + + private let minute: Double = 60 + private let hour: Double = 3600 + private let day: Double = 86400 + + func execute(from timeString: String) -> String? { + guard let time = DateFormatter.toDetailDate(from: timeString) else { + return nil + } + + let diffTime = Date().timeIntervalSince(time) + + switch diffTime { + case ..() private let commonQuestSubject = PassthroughSubject, Never>.init() private let fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase - private let minute: Double = 60 - private let hour: Double = 3600 - private let day: Double = 86400 + private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase private(set) var output: Output private var commonQuest: CommonQuestAnswersEntity? @@ -24,8 +22,12 @@ final class CommonQuestViewModel { private var nextCursor: Int? = nil private var currentDate: String = DateFormatter.toAPIDateString(from: .now) - init(fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase) { + init( + fetchCommonQuestByDateUseCase: FetchCommonQuestByDateUseCase, + formatElapsedTimeUseCase: FormatElapsedTimeUseCase + ) { self.fetchCommonQuestByDateUseCase = fetchCommonQuestByDateUseCase + self.formatElapsedTimeUseCase = formatElapsedTimeUseCase self.output = Output( commonQuestPublisher: commonQuestSubject.eraseToAnyPublisher() ) @@ -127,25 +129,6 @@ extension CommonQuestViewModel { } func getWrittenAt(at index: Int) -> String? { - guard index >= 0 && index < answers.count, - let writtenAt = DateFormatter.toDetailDate(from: answers[index].writtenAt) - else { - return nil - } - - let diffTime = Date().timeIntervalSince(writtenAt) - - switch diffTime { - case .. Date: Sun, 7 Jun 2026 11:36:56 +0900 Subject: [PATCH 10/26] =?UTF-8?q?refactor:=20#437=20=EB=B7=B0=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20NotificationsViewModel=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 2 +- ...wift => NotificationsViewController.swift} | 42 +++++++++++++------ .../ViewModel/NotificationsViewModel.swift | 23 ++++++++++ .../Presentation/ViewControllerFactory.swift | 9 +++- 4 files changed, 60 insertions(+), 16 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/{NoticesViewController.swift => NotificationsViewController.swift} (65%) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 97cf8858..297a3b5c 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -118,7 +118,7 @@ extension HomeViewController { @objc private func noticeButtonDidTap() { - let viewController = ViewControllerFactory.shared.makeNoticesViewController() + let viewController = ViewControllerFactory.shared.makeNotificationsViewController() viewController.hidesBottomBarWhenPushed = true viewController.configure(notificationList: viewModel.notifications) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift similarity index 65% rename from ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift rename to ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index cd33f35d..cc0cc1b4 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NoticesViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -1,5 +1,5 @@ // -// NoticesViewController.swift +// NotificationsViewController.swift // ByeBoo-iOS // // Created by APPLE on 4/30/26. @@ -7,12 +7,14 @@ import UIKit -final class NoticesViewController: BaseViewController { +final class NotificationsViewController: BaseViewController { private let rootView = NoticesView() + private let viewModel: NotificationsViewModel private var notificationList: NotificationListEntity? - init() { + init(viewModel: NotificationsViewModel) { + self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -33,7 +35,7 @@ final class NoticesViewController: BaseViewController { type: .titleAndBack("알림"), action: #selector(back) ) - + guard let notifications = notificationList?.notifications else { return } @@ -58,31 +60,30 @@ final class NoticesViewController: BaseViewController { } } -extension NoticesViewController: BackNavigable { +extension NotificationsViewController: BackNavigable { func back() { self.navigationController?.popViewController(animated: false) } } -extension NoticesViewController { +extension NotificationsViewController { @objc private func readAllButtonDidTap() {} } -extension NoticesViewController { +extension NotificationsViewController { func configure(notificationList: NotificationListEntity?) { self.notificationList = notificationList } } -extension NoticesViewController: UITableViewDataSource { +extension NotificationsViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - // 실제로는 알림 개수만큼 설정 - 2 + notificationList?.notifications.count ?? 0 } func tableView( @@ -97,16 +98,31 @@ extension NoticesViewController: UITableViewDataSource { cellForRowAt indexPath: IndexPath ) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: NoticeCardCell.identifier, for: indexPath) as? NoticeCardCell + guard + let cell = tableView.dequeueReusableCell( + withIdentifier: NoticeCardCell.identifier, + for: indexPath + ) as? NoticeCardCell, + let notification = notificationList?.notifications[indexPath.section], + let notificationType = notification.notificationType, + let writtenTime = viewModel.formatElapsedTime(from: notification.createdAt) else { return UITableViewCell() } - + + cell.bind( + isRead: notification.isRead, + notificationType: notificationType, + title: notification.title, + subtitle: notification.content, + writtenTime: writtenTime + ) + return cell } } -extension NoticesViewController: UITableViewDelegate { +extension NotificationsViewController: UITableViewDelegate { func tableView( _ tableView: UITableView, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift new file mode 100644 index 00000000..6c3d8997 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -0,0 +1,23 @@ +// +// NotificationsViewModel.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/7/26. +// + +final class NotificationsViewModel { + + private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase + + init(formatElapsedTimeUseCase: FormatElapsedTimeUseCase) { + self.formatElapsedTimeUseCase = formatElapsedTimeUseCase + } + + func formatElapsedTime(from timeString: String) -> String? { + guard let formattedTime = formatElapsedTimeUseCase.execute(from: timeString) else { + return nil + } + + return formattedTime + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift index 0dd5b375..7c5371d5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/ViewControllerFactory.swift @@ -233,8 +233,13 @@ final class ViewControllerFactory: ViewControllerFactoryProtocol { return .init(viewModel: viewModel) } - func makeNoticesViewController() -> NoticesViewController { - .init() + func makeNotificationsViewController() -> NotificationsViewController { + guard let viewModel = DIContainer.shared.resolve(type: NotificationsViewModel.self) else { + DIErrorHandle() + fatalError() + } + + return .init(viewModel: viewModel) } } From 10e7d9c03602721c222a0eac3887c0feac869e03 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:37:32 +0900 Subject: [PATCH 11/26] =?UTF-8?q?feat:=20#437=20NotificationType=20?= =?UTF-8?q?=EC=97=B4=EA=B1=B0=ED=98=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Enum/NotificationType+Data.swift | 24 +++++++++++++++++++ .../Model/NotificationListReponseDTO.swift | 2 +- .../Domain/Entity/Enum/NotificationType.swift | 12 ++++++++++ .../Home/View/Notice/NoticeCardCell.swift | 17 +++++++++---- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Enum/NotificationType+Data.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/NotificationType.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Enum/NotificationType+Data.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Enum/NotificationType+Data.swift new file mode 100644 index 00000000..c1cb649c --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Enum/NotificationType+Data.swift @@ -0,0 +1,24 @@ +// +// NotificationType+Data.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/7/26. +// + +extension NotificationType { + + var responseKey: String { + switch self { + case .questOpen: + "QUEST_OPEN" + case .comment: + "COMMENT" + case .like: + "LIKE" + } + } + + static func keyToEnum(_ key: String) -> Self? { + allCases.first { $0.responseKey == key } + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift index db9191f4..28542da7 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift @@ -30,7 +30,7 @@ extension NotificationResponseDTO { func toEntity() -> NotificationEntity { .init( notificationID: notificationID, - notificationType: notificationType, + notificationType: NotificationType.keyToEnum(notificationType), title: title, content: content, isRead: isRead, diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/NotificationType.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/NotificationType.swift new file mode 100644 index 00000000..6fba51e5 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/Enum/NotificationType.swift @@ -0,0 +1,12 @@ +// +// NotificationType.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/7/26. +// + +enum NotificationType: CaseIterable { + case questOpen + case comment + case like +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift index 7c26b48a..b36128f8 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -82,16 +82,25 @@ final class NoticeCardCell: UITableViewCell { extension NoticeCardCell { func bind( - backgroundColor: UIColor, - iconImage: UIImage, + isRead: Bool, + notificationType: NotificationType, title: String, subtitle: String, writtenTime: String ) { - self.backgroundColor = backgroundColor - noticeImageView.image = iconImage + self.backgroundColor = isRead ? .primary30020 : .white5 + noticeImageView.image = mapToImage(for: notificationType) titleLabel.text = title subtitleLabel.text = subtitle writtenTimeLabel.text = writtenTime } + + private func mapToImage(for notificationType: NotificationType) -> UIImage { + switch notificationType { + case .questOpen: + return .myOn + case .comment, .like: + return .commonJourney + } + } } From 8e1277ee8670fc09151e1c7328fc326134701fce Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:37:48 +0900 Subject: [PATCH 12/26] =?UTF-8?q?feat:=20#437=20NotificationListEntity=20s?= =?UTF-8?q?tub=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/NotificationListEntity.swift | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift index 42f7f4e8..fd4dcc33 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/NotificationListEntity.swift @@ -11,10 +11,100 @@ struct NotificationListEntity { struct NotificationEntity { let notificationID: Int - let notificationType: String + let notificationType: NotificationType? let title: String let content: String let isRead: Bool let createdAt: String let landingURL: String } + +extension NotificationListEntity { + static func stub() -> Self { + .init( + notifications: [ + NotificationEntity( + notificationID: 9, + notificationType: .questOpen, + title: "오늘의 퀘스트 오픈 🌱", + content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", + isRead: false, + createdAt: "2026-06-07T11:29:00.735730", + landingURL: "myapp://quest/24" + ), + NotificationEntity( + notificationID: 8, + notificationType: .comment, + title: "공통여정에 답변이 달렸어요 💬", + content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", + isRead: true, + createdAt: "2026-06-07T08:35:43.735730", + landingURL: "myapp://common-quests/24" + ), + NotificationEntity( + notificationID: 7, + notificationType: .like, + title: "공통여정에 답변에 공감이 달렸어요 ❤️", + content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", + isRead: true, + createdAt: "2026-06-06T15:35:43.735730", + landingURL: "myapp://common-quests/23" + ), + NotificationEntity( + notificationID: 6, + notificationType: .questOpen, + title: "오늘의 퀘스트 오픈 🌱", + content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", + isRead: false, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://quest/24" + ), + NotificationEntity( + notificationID: 5, + notificationType: .comment, + title: "공통여정에 답변이 달렸어요 💬", + content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", + isRead: true, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://common-quests/24" + ), + NotificationEntity( + notificationID: 4, + notificationType: .like, + title: "공통여정에 답변에 공감이 달렸어요 ❤️", + content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", + isRead: true, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://common-quests/23" + ), + NotificationEntity( + notificationID: 3, + notificationType: .questOpen, + title: "오늘의 퀘스트 오픈 🌱", + content: "24번째 퀘스트가 오픈됐어요, 시작해볼까요?", + isRead: false, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://quest/24" + ), + NotificationEntity( + notificationID: 2, + notificationType: .comment, + title: "공통여정에 답변이 달렸어요 💬", + content: "내가 작성한 글에 보리보리쌀님이 답변을 남겼어요", + isRead: true, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://common-quests/24" + ), + NotificationEntity( + notificationID: 1, + notificationType: .like, + title: "공통여정에 답변에 공감이 달렸어요 ❤️", + content: "내가 작성한 글에 보리보리쌀님이 공감을 남겼어요", + isRead: true, + createdAt: "2026-02-19T02:09:43.735730", + landingURL: "myapp://common-quests/23" + ) + ] + ) + } +} From 2e29eb136adbc79842c2a5bcd0742fbd6fb397f6 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:38:11 +0900 Subject: [PATCH 13/26] =?UTF-8?q?refactor:=20#437=20bind=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EA=B5=AC=EB=8F=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 297a3b5c..4ab1d259 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -47,6 +47,9 @@ final class HomeViewController: BaseViewController { super.viewWillAppear(animated) viewModel.action(.viewWillAppear) + if isFirstVisit { isFirstVisit.toggle() } + + self.navigationController?.setNavigationBarHidden(true, animated: false) let property = HomeEvents.HomePageProperty( isFirstPageView: isFirstVisit, @@ -56,10 +59,6 @@ final class HomeViewController: BaseViewController { event: HomeEvents.Name.homePageView, properties: property.dictionary ) - - if isFirstVisit { isFirstVisit.toggle() } - - self.navigationController?.setNavigationBarHidden(true, animated: false) } override func setAddTarget() { @@ -147,6 +146,13 @@ extension HomeViewController { extension HomeViewController: ToastPresentable, ToastErrorHandler { private func bind() { + bindCharacter() + bindHomeState() + bindHelper() + bindNotifications() + } + + private func bindCharacter() { viewModel.output.characterResult .receive(on: DispatchQueue.main) .sink { [weak self] result in @@ -158,7 +164,9 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { } } .store(in: &cancellables) - + } + + private func bindHomeState() { Publishers.CombineLatest3( viewModel.output.userResult, viewModel.output.journeyResult, @@ -188,7 +196,9 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { } } .store(in: &cancellables) - + } + + private func bindHelper() { viewModel.output.helperResult .receive(on: DispatchQueue.main) .sink { [weak self] result in @@ -198,4 +208,19 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { } .store(in: &cancellables) } + + private func bindNotifications() { + viewModel.output.notificationResult + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let notificationList): + let isAllRead = notificationList.notifications.allSatisfy { $0.isRead } + self?.rootView.headerView.updateNotice(isExist: !isAllRead) + case .failure(let error): + self?.handleError(error) + } + } + .store(in: &cancellables) + } } From f1b6327b4cde29996422afde0710314cf5f91d69 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 11:38:18 +0900 Subject: [PATCH 14/26] =?UTF-8?q?chore:=20#437=20=EC=A4=84=EB=B0=94?= =?UTF-8?q?=EA=BF=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift index 7622a3cb..63cba471 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Quest/ViewModel/CommonQuestMyAnswerViewModel.swift @@ -13,6 +13,7 @@ final class CommonQuestMyAnswerViewModel { private let cancellables = Set() private let nameSubject = PassthroughSubject, Never>.init() private let answersSubject = PassthroughSubject, Never>.init() + private let getUserNameUseCase: GetUserNameUseCase private let fetchCommonQuestMyAnswersUseCase: FetchCommonQuestMyAnswersUseCase From 10207eac89a0acb70f5234810286bd16666aadc9 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Sun, 7 Jun 2026 12:09:39 +0900 Subject: [PATCH 15/26] =?UTF-8?q?refactor:=20#437=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=85=80=20=EC=84=A0=ED=83=9D=20=EC=8B=9C=20=ED=9A=8C=EC=83=89?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Feature/Home/View/Notice/NoticeCardCell.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift index b36128f8..1ba1773a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/View/Notice/NoticeCardCell.swift @@ -29,6 +29,7 @@ final class NoticeCardCell: UITableViewCell { private func setStyle() { self.do { $0.layer.cornerRadius = 12 + $0.selectionStyle = .none } titleLabel.applyByeBooFont( style: .body1Sb16, From 5f40a43b8d95409cdcf84e600788cddb43f021a9 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:13:17 +0900 Subject: [PATCH 16/26] =?UTF-8?q?chore:=20#437=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Repository/NotificationTokenRepository.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift index 47063b8e..98b3050d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationTokenRepository.swift @@ -1,5 +1,5 @@ // -// NotificationRepositoru.swift +// NotificationTokenRepository.swift // ByeBoo-iOS // // Created by APPLE on 11/22/25. From 4cf0b318a5fc6fbd9cbfc38f1c2e41b7ab248006 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:19:33 +0900 Subject: [PATCH 17/26] =?UTF-8?q?refactor:=20#437=20TaskGroup=20->=20async?= =?UTF-8?q?=20let=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Home/ViewModel/HomeViewModel.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index af9768c4..f0a664b5 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -82,15 +82,14 @@ extension HomeViewModel: ViewModelType { getUserResult() Task { - await withTaskGroup(of: Void.self) { group in - group.addTask { await self.fetchDialogue() } - group.addTask { await self.fetchStatus() } - group.addTask { await self.fetchJourney() } - group.addTask { await self.fetchNotificationList() } - - await group.waitForAll() - } + async let dialogue: Void = fetchDialogue() + async let status: Void = fetchStatus() + async let journey: Void = fetchJourney() + async let notificationList: Void = fetchNotificationList() + + let _ = await (dialogue, status, journey, notificationList) } + case .helperDidTap: setHelperShown() } From cf9eb095b81f5e64c989d90f66dba48a0bc75a6a Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:44:07 +0900 Subject: [PATCH 18/26] =?UTF-8?q?chore:=20#437=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...stReponseDTO.swift => NotificationListResponseDTO.swift} | 6 +++--- .../ByeBoo-iOS/Data/Repository/NotificationRepository.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename ByeBoo-iOS/ByeBoo-iOS/Data/Model/{NotificationListReponseDTO.swift => NotificationListResponseDTO.swift} (87%) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift similarity index 87% rename from ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift rename to ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift index 28542da7..9c2cb343 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListReponseDTO.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/NotificationListResponseDTO.swift @@ -1,11 +1,11 @@ // -// NotificationListReponseDTO.swift +// NotificationListResponseDTO.swift // ByeBoo-iOS // // Created by 더스틴 on 6/5/26. // -struct NotificationListReponseDTO: Decodable { +struct NotificationListResponseDTO: Decodable { let notifications: [NotificationResponseDTO] } @@ -19,7 +19,7 @@ struct NotificationResponseDTO: Decodable { let landingURL: String } -extension NotificationListReponseDTO { +extension NotificationListResponseDTO { func toEntity() -> NotificationListEntity { let notifications = notifications.map { $0.toEntity() } return .init(notifications: notifications) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift index 26c704b5..c93aefa3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift @@ -16,7 +16,7 @@ struct DefaultNotificationRepository: NotificationInterface { func fetchNotifications() async throws -> NotificationListEntity { let result = try await networkService.request( NotificationAPI.fetchNotificationList, - decodingType: NotificationListReponseDTO.self + decodingType: NotificationListResponseDTO.self ) return result.toEntity() } From 7c82b60adfc675166200d796ba631a5aa0180b4d Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:44:58 +0900 Subject: [PATCH 19/26] =?UTF-8?q?chore:=20#437=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift index acb0f6e3..ea727164 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationTokenAPI.swift @@ -1,5 +1,5 @@ // -// NotificationAPI.swift +// NotificationTokenAPI.swift // ByeBoo-iOS // // Created by APPLE on 11/22/25. From 27fe4d11ce883bf9d82ecdfa2ff281c1784af95f Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:19:03 +0900 Subject: [PATCH 20/26] =?UTF-8?q?refactor:=20#437=20=ED=99=88=20=EB=B7=B0?= =?UTF-8?q?=EB=AA=A8=EB=8D=B8=EC=97=90=EC=84=9C=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20API=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewModel/HomeViewModel.swift | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index f0a664b5..d3af648a 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -23,7 +23,6 @@ final class HomeViewModel { private var isHelperShownResultSubject = CurrentValueSubject(true) private var homeStateResultSubject = PassthroughSubject, Never>() private var journeyResultSubject = PassthroughSubject, Never>() - private var notificationResultSubject = PassthroughSubject, Never>() private let fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase private let fetchQuestStatusUseCase: FetchQuestStatusUseCase @@ -31,7 +30,6 @@ final class HomeViewModel { private let getUserNameUseCase: GetUserNameUseCase private let setHelperUseCase: SetHelperUseCase private let getHelperUseCase: GetHelperUseCase - private let fetchNotificationListUseCase: FetchNotificationListUseCase init( fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase, @@ -39,8 +37,7 @@ final class HomeViewModel { fetchUserJourneyUseCase: FetchUserJourneyUseCase, getUserNameUseCase: GetUserNameUseCase, setHelperUseCase: SetHelperUseCase, - getHelperUseCase: GetHelperUseCase, - fetchNotificationListUseCase: FetchNotificationListUseCase + getHelperUseCase: GetHelperUseCase ) { self.fetchCharacterDialogueUseCase = fetchCharacterDialogueUseCase self.fetchQuestStatusUseCase = fetchQuestStatusUseCase @@ -48,15 +45,13 @@ final class HomeViewModel { self.getUserNameUseCase = getUserNameUseCase self.setHelperUseCase = setHelperUseCase self.getHelperUseCase = getHelperUseCase - self.fetchNotificationListUseCase = fetchNotificationListUseCase output = Output( characterResult: characterResultSubject.eraseToAnyPublisher(), userResult: userResultSubject.eraseToAnyPublisher(), helperResult: isHelperShownResultSubject.eraseToAnyPublisher(), homeStateResult: homeStateResultSubject.eraseToAnyPublisher(), - journeyResult: journeyResultSubject.eraseToAnyPublisher(), - notificationResult: notificationResultSubject.eraseToAnyPublisher() + journeyResult: journeyResultSubject.eraseToAnyPublisher() ) } } @@ -73,7 +68,6 @@ extension HomeViewModel: ViewModelType { let helperResult: AnyPublisher let homeStateResult: AnyPublisher, Never> let journeyResult: AnyPublisher, Never> - let notificationResult: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -85,9 +79,8 @@ extension HomeViewModel: ViewModelType { async let dialogue: Void = fetchDialogue() async let status: Void = fetchStatus() async let journey: Void = fetchJourney() - async let notificationList: Void = fetchNotificationList() - let _ = await (dialogue, status, journey, notificationList) + let _ = await (dialogue, status, journey) } case .helperDidTap: @@ -153,14 +146,4 @@ extension HomeViewModel { private func setHelperShown() { setHelperUseCase.execute() } - - private func fetchNotificationList() async { - do { - let notifications = try await fetchNotificationListUseCase.execute() - self.notifications = notifications - notificationResultSubject.send(.success(notifications)) - } catch { - notificationResultSubject.send(.failure(error as? ByeBooError ?? ByeBooError.unknownError)) - } - } } From e505dbd62929b83d5d28e3c05904541a02f41ef9 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:22:26 +0900 Subject: [PATCH 21/26] =?UTF-8?q?feat:=20#437=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8D=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EA=B4=80=EB=A0=A8=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ByeBoo-iOS/Domain/DomainDependencyAssembler.swift | 4 ++++ .../Presentation/PresentationDependencyAssembler.swift | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift index acb630a6..8eeb3e5d 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/DomainDependencyAssembler.swift @@ -216,5 +216,9 @@ struct DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: FormatElapsedTimeUseCase.self) { _ in return DefaultFormatElapsedTimeUseCase() } + + DIContainer.shared.register(type: FetchHasUnreadNotificationUseCase.self) { _ in + return DefaultFetchHasUnreadNotificationUseCase(repository: notificationRepository) + } } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 7b6d5b43..95f1f426 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -108,7 +108,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { let fetchUserJourneyUseCase = container.resolve(type: FetchUserJourneyUseCase.self), let getUserNameUseCase = container.resolve(type: GetUserNameUseCase.self), let getHelperUseCase = container.resolve(type: GetHelperUseCase.self), - let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) + let fetchHasUnreadNotificationUseCase = container.resolve(type: FetchHasUnreadNotificationUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return @@ -121,7 +121,7 @@ struct PresentationDependencyAssembler: DependencyAssembler { getUserNameUseCase: getUserNameUseCase, setHelperUseCase: setHelperUseCase, getHelperUseCase: getHelperUseCase, - fetchNotificationListUseCase: fetchNotificationListUseCase + fetchHasUnreadNotificationUseCase: fetchHasUnreadNotificationUseCase ) } @@ -356,12 +356,12 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: NotificationsViewModel.self) { container in - guard let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self) else { + guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } - return NotificationsViewModel(formatElapsedTimeUseCase: formatElapsedTimeUseCase) + return NotificationsViewModel(fetchNotificationListUseCase: fetchNotificationListUseCase) } } } From 58b5c94fe6e93e416e0d3d73fde01124f32ce613 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:23:12 +0900 Subject: [PATCH 22/26] =?UTF-8?q?feat:=20#450=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8D=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?-=20Presentation=20=EC=98=81=EC=97=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 14 +++++------ .../Home/ViewModel/HomeViewModel.swift | 25 ++++++++++++++++--- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift index 4ab1d259..09fa25a3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -116,10 +116,9 @@ extension HomeViewController { } @objc - private func noticeButtonDidTap() { + private func noticeButtonDidTap() { let viewController = ViewControllerFactory.shared.makeNotificationsViewController() viewController.hidesBottomBarWhenPushed = true - viewController.configure(notificationList: viewModel.notifications) self.navigationController?.setNavigationBarHidden(false, animated: false) self.navigationController?.pushViewController(viewController, animated: false) @@ -149,7 +148,7 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { bindCharacter() bindHomeState() bindHelper() - bindNotifications() + bindHasNotification() } private func bindCharacter() { @@ -209,14 +208,13 @@ extension HomeViewController: ToastPresentable, ToastErrorHandler { .store(in: &cancellables) } - private func bindNotifications() { - viewModel.output.notificationResult + private func bindHasNotification() { + viewModel.output.hasNotifcationResult .receive(on: DispatchQueue.main) .sink { [weak self] result in switch result { - case .success(let notificationList): - let isAllRead = notificationList.notifications.allSatisfy { $0.isRead } - self?.rootView.headerView.updateNotice(isExist: !isAllRead) + case .success(let entity): + self?.rootView.headerView.updateNotice(isExist: entity.hasUnread) case .failure(let error): self?.handleError(error) } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index d3af648a..36fd3a65 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -15,7 +15,6 @@ final class HomeViewModel { var dialoguesResult: Result { characterResultSubject.value } - private(set) var notifications: NotificationListEntity? private(set) var output: Output private var characterResultSubject = CurrentValueSubject, Never>(.failure(ByeBooError.noData)) @@ -23,6 +22,7 @@ final class HomeViewModel { private var isHelperShownResultSubject = CurrentValueSubject(true) private var homeStateResultSubject = PassthroughSubject, Never>() private var journeyResultSubject = PassthroughSubject, Never>() + private var hasNotificationResultSubject = PassthroughSubject,Never>() private let fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase private let fetchQuestStatusUseCase: FetchQuestStatusUseCase @@ -30,6 +30,7 @@ final class HomeViewModel { private let getUserNameUseCase: GetUserNameUseCase private let setHelperUseCase: SetHelperUseCase private let getHelperUseCase: GetHelperUseCase + private let fetchHasUnreadNotificationUseCase: FetchHasUnreadNotificationUseCase init( fetchCharacterDialogueUseCase: FetchCharacterDialogueUseCase, @@ -37,7 +38,8 @@ final class HomeViewModel { fetchUserJourneyUseCase: FetchUserJourneyUseCase, getUserNameUseCase: GetUserNameUseCase, setHelperUseCase: SetHelperUseCase, - getHelperUseCase: GetHelperUseCase + getHelperUseCase: GetHelperUseCase, + fetchHasUnreadNotificationUseCase: FetchHasUnreadNotificationUseCase ) { self.fetchCharacterDialogueUseCase = fetchCharacterDialogueUseCase self.fetchQuestStatusUseCase = fetchQuestStatusUseCase @@ -45,13 +47,15 @@ final class HomeViewModel { self.getUserNameUseCase = getUserNameUseCase self.setHelperUseCase = setHelperUseCase self.getHelperUseCase = getHelperUseCase + self.fetchHasUnreadNotificationUseCase = fetchHasUnreadNotificationUseCase output = Output( characterResult: characterResultSubject.eraseToAnyPublisher(), userResult: userResultSubject.eraseToAnyPublisher(), helperResult: isHelperShownResultSubject.eraseToAnyPublisher(), homeStateResult: homeStateResultSubject.eraseToAnyPublisher(), - journeyResult: journeyResultSubject.eraseToAnyPublisher() + journeyResult: journeyResultSubject.eraseToAnyPublisher(), + hasNotifcationResult: hasNotificationResultSubject.eraseToAnyPublisher() ) } } @@ -68,6 +72,7 @@ extension HomeViewModel: ViewModelType { let helperResult: AnyPublisher let homeStateResult: AnyPublisher, Never> let journeyResult: AnyPublisher, Never> + let hasNotifcationResult: AnyPublisher, Never> } func action(_ trigger: Input) { @@ -79,6 +84,7 @@ extension HomeViewModel: ViewModelType { async let dialogue: Void = fetchDialogue() async let status: Void = fetchStatus() async let journey: Void = fetchJourney() + async let hasNotification: Void = fetchHasUnreadNotification() let _ = await (dialogue, status, journey) } @@ -146,4 +152,17 @@ extension HomeViewModel { private func setHelperShown() { setHelperUseCase.execute() } + + private func fetchHasUnreadNotification() async { + do { + let result = try await fetchHasUnreadNotificationUseCase.execute() + hasNotificationResultSubject.send(.success(result)) + } catch { + hasNotificationResultSubject.send( + .failure( + error as? ByeBooError ?? ByeBooError.unknownError + ) + ) + } + } } From 3ee2f63a33fdff8c10e32b5382ebefb8fea61676 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:23:24 +0900 Subject: [PATCH 23/26] =?UTF-8?q?feat:=20#450=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8D=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?-=20Domain=20=EC=98=81=EC=97=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Entity/HasUnreadNotificationEntity.swift | 10 ++++++++ .../Interface/NotificationInterface.swift | 1 + .../FetchHasUnreadNotificationUseCase.swift | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/HasUnreadNotificationEntity.swift create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchHasUnreadNotificationUseCase.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/HasUnreadNotificationEntity.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/HasUnreadNotificationEntity.swift new file mode 100644 index 00000000..ce047066 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Entity/HasUnreadNotificationEntity.swift @@ -0,0 +1,10 @@ +// +// HasUnreadNotificationEntity.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/19/26. +// + +struct HasUnreadNotificationEntity { + let hasUnread: Bool +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift index 05b07428..3640970f 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/Interface/NotificationInterface.swift @@ -7,4 +7,5 @@ protocol NotificationInterface { func fetchNotifications() async throws -> NotificationListEntity + func fetchHasUnreadNotification() async throws -> HasUnreadNotificationEntity } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchHasUnreadNotificationUseCase.swift b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchHasUnreadNotificationUseCase.swift new file mode 100644 index 00000000..cde37b86 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Domain/UseCase/FetchHasUnreadNotificationUseCase.swift @@ -0,0 +1,24 @@ +// +// FetchHasUnreadNotificationUseCase.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/19/26. +// + +protocol FetchHasUnreadNotificationUseCase { + func execute() async throws -> HasUnreadNotificationEntity +} + +struct DefaultFetchHasUnreadNotificationUseCase: FetchHasUnreadNotificationUseCase { + + private let repository: NotificationInterface + + init(repository: NotificationInterface) { + self.repository = repository + } + + func execute() async throws -> HasUnreadNotificationEntity { + let result = try await repository.fetchHasUnreadNotification() + return result + } +} From 481c0b800233dce8538ba5898ec4bb2b6db2ed1b Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:23:31 +0900 Subject: [PATCH 24/26] =?UTF-8?q?feat:=20#450=20=EC=9D=BD=EC=A7=80=20?= =?UTF-8?q?=EC=95=8D=EC=9D=80=20=EC=95=8C=EB=A6=BC=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?-=20Data=20=EC=98=81=EC=97=AD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/HasUnreadNotificationResponseDTO.swift | 16 ++++++++++++++++ .../Data/Network/EndPoint/NotificationAPI.swift | 13 ++++++++----- .../Data/Repository/NotificationRepository.swift | 8 ++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 ByeBoo-iOS/ByeBoo-iOS/Data/Model/HasUnreadNotificationResponseDTO.swift diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Model/HasUnreadNotificationResponseDTO.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/HasUnreadNotificationResponseDTO.swift new file mode 100644 index 00000000..fdc8f184 --- /dev/null +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Model/HasUnreadNotificationResponseDTO.swift @@ -0,0 +1,16 @@ +// +// HasUnreadNotificationResponseDTO.swift +// ByeBoo-iOS +// +// Created by 더스틴 on 6/19/26. +// + +struct HasUnreadNotificationResponseDTO: Decodable { + let hasUnread: Bool +} + +extension HasUnreadNotificationResponseDTO { + func toEntity() -> HasUnreadNotificationEntity { + .init(hasUnread: hasUnread) + } +} diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift index 34a270f3..97dc1a49 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Network/EndPoint/NotificationAPI.swift @@ -11,6 +11,7 @@ import Alamofire enum NotificationAPI { case fetchNotificationList + case fetchUnreadNotification } extension NotificationAPI: EndPoint { @@ -23,40 +24,42 @@ extension NotificationAPI: EndPoint { switch self { case .fetchNotificationList: "" + case .fetchUnreadNotification: + "/unread/status" } } var method: HTTPMethod { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .fetchUnreadNotification: .get } } var headers: HeaderType { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .fetchUnreadNotification: .withAuth } } var parameterEncoding: ParameterEncoding { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .fetchUnreadNotification: JSONEncoding.default } } var queryParameters: [String : String]? { switch self { - case .fetchNotificationList: + case .fetchNotificationList, .fetchUnreadNotification: nil } } var bodyParameters: Parameters? { switch self { - case .fetchNotificationList: + case .fetchNotificationList ,.fetchUnreadNotification: nil } } diff --git a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift index c93aefa3..91419050 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Data/Repository/NotificationRepository.swift @@ -20,4 +20,12 @@ struct DefaultNotificationRepository: NotificationInterface { ) return result.toEntity() } + + func fetchHasUnreadNotification() async throws -> HasUnreadNotificationEntity { + let result = try await networkService.request( + NotificationAPI.fetchUnreadNotification, + decodingType: HasUnreadNotificationResponseDTO.self + ) + return result.toEntity() + } } From a62c0a6d209d04b4b6f563613d0a7e29a9ae5b41 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:40:03 +0900 Subject: [PATCH 25/26] =?UTF-8?q?refactor:=20#437=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20NotificationViewModel=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NotificationsViewController.swift | 41 +++++++---- .../ViewModel/NotificationsViewModel.swift | 68 +++++++++++++++++-- 2 files changed, 89 insertions(+), 20 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift index cc0cc1b4..954fb040 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewController/NotificationsViewController.swift @@ -5,13 +5,14 @@ // Created by APPLE on 4/30/26. // +import Combine import UIKit final class NotificationsViewController: BaseViewController { private let rootView = NoticesView() private let viewModel: NotificationsViewModel - private var notificationList: NotificationListEntity? + private var cancellables = Set() init(viewModel: NotificationsViewModel) { self.viewModel = viewModel @@ -26,6 +27,11 @@ final class NotificationsViewController: BaseViewController { view = rootView } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + viewModel.action(.viewWillAppear) + } + override func viewDidLoad() { super.viewDidLoad() @@ -36,11 +42,7 @@ final class NotificationsViewController: BaseViewController { action: #selector(back) ) - guard let notifications = notificationList?.notifications else { - return - } - - rootView.contentView.decideNoticeContent(isExistNotice: !notifications.isEmpty) + bind() } override func setAddTarget() { @@ -67,23 +69,34 @@ extension NotificationsViewController: BackNavigable { } } -extension NotificationsViewController { +extension NotificationsViewController: ToastPresentable, ToastErrorHandler { - @objc - private func readAllButtonDidTap() {} + func bind() { + viewModel.output.notificationList + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(let notificationList): + self?.rootView.contentView.decideNoticeContent(isExistNotice: !notificationList.notifications.isEmpty) + self?.rootView.contentView.noticeCardsView.cardTableView.reloadData() + case .failure(let error): + self?.handleError(error) + } + } + .store(in: &cancellables) + } } extension NotificationsViewController { - func configure(notificationList: NotificationListEntity?) { - self.notificationList = notificationList - } + @objc + private func readAllButtonDidTap() {} } extension NotificationsViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { - notificationList?.notifications.count ?? 0 + viewModel.notificatinosCount } func tableView( @@ -103,7 +116,7 @@ extension NotificationsViewController: UITableViewDataSource { withIdentifier: NoticeCardCell.identifier, for: indexPath ) as? NoticeCardCell, - let notification = notificationList?.notifications[indexPath.section], + let notification = viewModel.getNotification(at: indexPath.section), let notificationType = notification.notificationType, let writtenTime = viewModel.formatElapsedTime(from: notification.createdAt) else { diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift index 6c3d8997..0cf4afa3 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/Feature/Home/ViewModel/NotificationsViewModel.swift @@ -5,19 +5,75 @@ // Created by 더스틴 on 6/7/26. // +import Combine + final class NotificationsViewModel { + private var cancellables = Set() + private var notifications: [NotificationEntity]? + + private let fetchNotificationListUseCase: FetchNotificationListUseCase private let formatElapsedTimeUseCase: FormatElapsedTimeUseCase - init(formatElapsedTimeUseCase: FormatElapsedTimeUseCase) { + private(set) var output: Output + private var notificationListSubject = PassthroughSubject, Never>() + + init( + fetchNotificationListUseCase: FetchNotificationListUseCase, + formatElapsedTimeUseCase: FormatElapsedTimeUseCase + ) { + self.fetchNotificationListUseCase = fetchNotificationListUseCase self.formatElapsedTimeUseCase = formatElapsedTimeUseCase + self.output = .init(notificationList: notificationListSubject.eraseToAnyPublisher()) + } +} + +extension NotificationsViewModel: ViewModelType { + enum Input { + case viewWillAppear } - func formatElapsedTime(from timeString: String) -> String? { - guard let formattedTime = formatElapsedTimeUseCase.execute(from: timeString) else { - return nil + struct Output { + let notificationList: AnyPublisher, Never> + } + + func action(_ trigger: Input) { + switch trigger { + case .viewWillAppear: + fetchNotificationList() } - - return formattedTime + } +} + +extension NotificationsViewModel { + + func fetchNotificationList() { + Task { + do { + let notificationList = try await fetchNotificationListUseCase.execute() + self.notifications = notificationList.notifications + notificationListSubject.send(.success(notificationList)) + } catch { + guard let error = error as? ByeBooError else { + return + } + notificationListSubject.send(.failure(error)) + } + } + } + + func formatElapsedTime(from timeString: String) -> String? { + formatElapsedTimeUseCase.execute(from: timeString) + } +} + +extension NotificationsViewModel { + + var notificatinosCount: Int { + notifications?.count ?? 0 + } + + func getNotification(at index: Int) -> NotificationEntity? { + notifications?[index] } } From a6dd04df79a3201e361ba4c51adfe69adf04db36 Mon Sep 17 00:00:00 2001 From: heopaka <94223526+dev-domo@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:40:39 +0900 Subject: [PATCH 26/26] =?UTF-8?q?refactor:=20#437=20NotificationsViewModel?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20FormatElaspedTimeUseCase=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/PresentationDependencyAssembler.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift index 95f1f426..a653c1d6 100644 --- a/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift +++ b/ByeBoo-iOS/ByeBoo-iOS/Presentation/PresentationDependencyAssembler.swift @@ -356,12 +356,16 @@ struct PresentationDependencyAssembler: DependencyAssembler { } DIContainer.shared.register(type: NotificationsViewModel.self) { container in - guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self) else { + guard let fetchNotificationListUseCase = container.resolve(type: FetchNotificationListUseCase.self), + let formatElapsedTimeUseCase = container.resolve(type: FormatElapsedTimeUseCase.self) else { ByeBooLogger.error(ByeBooError.DIFailedError) return } - return NotificationsViewModel(fetchNotificationListUseCase: fetchNotificationListUseCase) + return NotificationsViewModel( + fetchNotificationListUseCase: fetchNotificationListUseCase, + formatElapsedTimeUseCase: formatElapsedTimeUseCase + ) } } }