From 75c156b3ee7128c0b8aff7020f4a17b2d28ac82a Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 13 Nov 2024 14:42:22 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[IDLE-515]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=EC=A3=BC=EB=B3=80=20=EA=B3=B5=EA=B3=A0=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=EC=8B=9C=20=EC=95=8C=EB=A6=BC=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EB=84=A4=EB=B9=84?= =?UTF-8?q?=EA=B2=8C=EC=9D=B4=EC=85=98=20=EA=B2=BD=EB=A1=9C=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 --- .../Notifications/RemoteNotificationHelper.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/project/Projects/Presentation/Feature/Base/Sources/Notifications/RemoteNotificationHelper.swift b/project/Projects/Presentation/Feature/Base/Sources/Notifications/RemoteNotificationHelper.swift index fc06b4e3..09d67e13 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/Notifications/RemoteNotificationHelper.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/Notifications/RemoteNotificationHelper.swift @@ -20,18 +20,30 @@ public protocol RemoteNotificationHelper { } public enum DeepLinkPathComponent { + + // MARK: Center case centerMainPage case postApplicantPage case splashPage + + // MARK: Worker + case workerMainPage + case postDetailForWorkerPage } public enum PreDefinedDeeplinkPath: String { + + /// 센터관리자가 등록한 공고에 요양보호사가 지원하는 상황 case postApplicant = "APPLICANT" + case newJobPostingForWorker = "NEW_JOB_POSTING" + public var outsideLinks: [DeepLinkPathComponent] { switch self { case .postApplicant: [.centerMainPage, .postApplicantPage] + case .newJobPostingForWorker: + [.workerMainPage, .postDetailForWorkerPage] } } @@ -39,6 +51,8 @@ public enum PreDefinedDeeplinkPath: String { switch self { case .postApplicant: [.postApplicantPage] + case .newJobPostingForWorker: + [.postDetailForWorkerPage] } } } From b20f564117d847f92fe55142f0e52f3af3653549 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 13 Nov 2024 14:47:02 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[IDLE-515]=20CenterMainPageDeeplink?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/WorkerMainPageDeepLink.swift | 33 +++++++++++++++++++ .../Sources/Deeplinks/DeeplinkParser.swift | 8 +++-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/WorkerMainPageDeepLink.swift diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/WorkerMainPageDeepLink.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/WorkerMainPageDeepLink.swift new file mode 100644 index 00000000..bc651bad --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/WorkerMainPageDeepLink.swift @@ -0,0 +1,33 @@ +// +// WorkerMainPageDeepLink.swift +// Root +// +// Created by choijunios on 11/13/24. +// + +import Foundation +import BaseFeature + +class WorkerMainPageDeepLink: DeeplinkExecutable { + + var component: DeepLinkPathComponent = .centerMainPage + + var children: [DeeplinkExecutable] = [ + PostApplicantDeeplink() + ] + + var isDestination: Bool = false + + init() { } + + func execute(with coordinator: any BaseFeature.Coordinator, userInfo: [AnyHashable : Any]?) -> Coordinator? { + + guard let appCoordinator = coordinator as? AppCoordinator else { + return nil + } + + let mainPageCoordinator = appCoordinator.runWorkerMainPageFlow() + + return mainPageCoordinator + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift index d17b885c..52197a4c 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift @@ -40,7 +40,7 @@ class DeeplinkParser { if startFromRoot { start = try findRoot(component: component) } else { - start = try findStartPoint(component: component) + start = try findBranchPoint(component: component) } deeplinks.append(start) @@ -61,17 +61,21 @@ class DeeplinkParser { switch component { case .centerMainPage: return CenterMainPageDeeplink() + case .workerMainPage: + return WorkerMainPageDeepLink() default: throw DeeplinkParserError.rootNotFound } } - private func findStartPoint(component: DeepLinkPathComponent) throws -> DeeplinkExecutable { + private func findBranchPoint(component: DeepLinkPathComponent) throws -> DeeplinkExecutable { switch component { case .centerMainPage: return CenterMainPageDeeplink() case .postApplicantPage: return PostApplicantDeeplink() + case .workerMainPage: + return CenterMainPageDeeplink() default: throw DeeplinkParserError.startPointNotFound } From 8d94cda2676a91b3f4b4f2c3770e141d005f6a89 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 13 Nov 2024 16:31:33 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[IDLE-515]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=EA=B0=80=20=EC=95=8C=EB=A6=BC=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EA=B3=B5=EA=B3=A0=EB=A1=9C=20=EB=B0=94?= =?UTF-8?q?=EB=A1=9C=EC=9D=B4=EB=8F=99=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AuthInOutStreamManager+PhoneNumber.swift | 5 +-- .../PostDetailForWorkerDeepLink.swift | 37 +++++++++++++++++++ .../Components/WorkerMainPageDeepLink.swift | 6 +-- .../Sources/Deeplinks/DeeplinkParser.swift | 21 ++++++++--- 4 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/PostDetailForWorkerDeepLink.swift diff --git a/project/Projects/Presentation/Feature/Base/Sources/CommonView/AuthInOutStreamManager/AuthInOutStreamManager+PhoneNumber.swift b/project/Projects/Presentation/Feature/Base/Sources/CommonView/AuthInOutStreamManager/AuthInOutStreamManager+PhoneNumber.swift index c2d1058d..13056da3 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/CommonView/AuthInOutStreamManager/AuthInOutStreamManager+PhoneNumber.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/CommonView/AuthInOutStreamManager/AuthInOutStreamManager+PhoneNumber.swift @@ -51,10 +51,7 @@ public extension AuthInOutStreamManager { .flatMap { [useCase, input] _ in let formatted = Self.formatPhoneNumber(phoneNumber: input.editingPhoneNumber.value) -#if DEBUG - print("✅ 디버그모드에서 번호인증 요청 무조건 통과") - return Single.just(Result.success(formatted)) -#endif + return useCase.requestPhoneNumberAuthentication(phoneNumber: formatted) } .share() diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/PostDetailForWorkerDeepLink.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/PostDetailForWorkerDeepLink.swift new file mode 100644 index 00000000..20caae09 --- /dev/null +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/PostDetailForWorkerDeepLink.swift @@ -0,0 +1,37 @@ +// +// PostDetailForWorkerDeepLink.swift +// Root +// +// Created by choijunios on 11/13/24. +// + +import Foundation +import WorkerMainPageFeature +import PostDetailForWorkerFeature +import BaseFeature +import Domain + +class PostDetailForWorkerDeepLink: DeeplinkExecutable { + + var component: DeepLinkPathComponent = .postDetailForWorkerPage + + var children: [DeeplinkExecutable] = [] + + var isDestination: Bool = false + + init() { } + + func execute(with coordinator: any BaseFeature.Coordinator, userInfo: [AnyHashable : Any]?) -> Coordinator? { + + + guard let appCoordinator = coordinator as? AppCoordinator else { + return nil + } + + guard let postId = userInfo?["jobPostingId"] as? String else { return nil } + + let postDetailForWorkerCoordinator = appCoordinator.postDetailForWorkerFlow(postInfo: .init(type: .native, id: postId)) + + return postDetailForWorkerCoordinator + } +} diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/WorkerMainPageDeepLink.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/WorkerMainPageDeepLink.swift index bc651bad..060967e5 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/WorkerMainPageDeepLink.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/Components/WorkerMainPageDeepLink.swift @@ -13,7 +13,7 @@ class WorkerMainPageDeepLink: DeeplinkExecutable { var component: DeepLinkPathComponent = .centerMainPage var children: [DeeplinkExecutable] = [ - PostApplicantDeeplink() + PostDetailForWorkerDeepLink() ] var isDestination: Bool = false @@ -26,8 +26,8 @@ class WorkerMainPageDeepLink: DeeplinkExecutable { return nil } - let mainPageCoordinator = appCoordinator.runWorkerMainPageFlow() + let _ = appCoordinator.runWorkerMainPageFlow() - return mainPageCoordinator + return appCoordinator } } diff --git a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift index 52197a4c..bef24547 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Deeplinks/DeeplinkParser.swift @@ -27,6 +27,9 @@ enum DeeplinkParserError: LocalizedError { class DeeplinkParser { + /// [딥링크 동작] + /// 먼저 딥링크를 처리할 수 있는 루트와 스타팅 포인트를 탐색합니다. 스타팅포인트는 옵셔널 입니다. + /// 스타팅 포인트를 따로둔 이유는 알림 인앱처리시 루트가 코디네이터에 대한 처리가 필요없기 때문입니다. func makeDeeplinkList(components: [DeepLinkPathComponent], startFromRoot: Bool = true) throws -> [DeeplinkExecutable] { var deeplinks: [DeeplinkExecutable] = [] @@ -35,12 +38,14 @@ class DeeplinkParser { if deeplinks.isEmpty { + // 딥링크의 경로의 첫번째 시작지점을 선정합니다. + var start: DeeplinkExecutable! if startFromRoot { start = try findRoot(component: component) } else { - start = try findBranchPoint(component: component) + start = try findFirstStartPointAboveApp(component: component) } deeplinks.append(start) @@ -57,6 +62,7 @@ class DeeplinkParser { return deeplinks } + /// 딥링크의 루트를 의미합니다. private func findRoot(component: DeepLinkPathComponent) throws -> DeeplinkExecutable { switch component { case .centerMainPage: @@ -68,14 +74,17 @@ class DeeplinkParser { } } - private func findBranchPoint(component: DeepLinkPathComponent) throws -> DeeplinkExecutable { + /// 루트는 아니지만 스타팅 포인트가 될 수 있는 지점을 의미합니다. + private func findFirstStartPointAboveApp(component: DeepLinkPathComponent) throws -> DeeplinkExecutable { switch component { - case .centerMainPage: - return CenterMainPageDeeplink() case .postApplicantPage: + return PostApplicantDeeplink() - case .workerMainPage: - return CenterMainPageDeeplink() + + case .postDetailForWorkerPage: + + return PostDetailForWorkerDeepLink() + default: throw DeeplinkParserError.startPointNotFound } From 3f1ccf5eb90dd0a2a29e62bda103bb36013279ae Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 13 Nov 2024 16:55:47 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[IDLE-515]=20=EC=9D=B8=EC=95=B1=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=8B=A0=EC=8B=9C=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84,=20=EC=B6=94=ED=9B=84?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20UI=EA=B5=AC=ED=98=84=ED=9B=84=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=EC=98=88=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DefaultNotificationsRepository.swift | 2 +- .../VO/Notifications/NotificationVO.swift | 7 +- .../RemoteNotificationHelper.swift | 2 +- .../RemoteNotificationHelper.swift | 70 ++++++++++++++++--- 4 files changed, 65 insertions(+), 16 deletions(-) diff --git a/project/Projects/Data/Repository/DefaultNotificationsRepository.swift b/project/Projects/Data/Repository/DefaultNotificationsRepository.swift index d0669076..2910d61c 100644 --- a/project/Projects/Data/Repository/DefaultNotificationsRepository.swift +++ b/project/Projects/Data/Repository/DefaultNotificationsRepository.swift @@ -66,7 +66,7 @@ extension NotificationItemDTO: EntityRepresentable { printIfDebug("\(NotificationItemDTO.self): 생성날짜 디코딩 실패") } - var notificationDetail: NotificationDetailVO? + var notificationDetail: NotificationDestinationForInApp? switch notificationType { case .APPLICANT: if let postId = (notificationDetails as? ApplicantInfluxDTO)?.toEntity() { diff --git a/project/Projects/Domain/Sources/Entity/VO/Notifications/NotificationVO.swift b/project/Projects/Domain/Sources/Entity/VO/Notifications/NotificationVO.swift index 5b6524f9..dbe0088d 100644 --- a/project/Projects/Domain/Sources/Entity/VO/Notifications/NotificationVO.swift +++ b/project/Projects/Domain/Sources/Entity/VO/Notifications/NotificationVO.swift @@ -15,7 +15,7 @@ public struct NotificationVO { public let body: String public let createdDate: Date public let imageDownloadInfo: ImageDownLoadInfo? - public let notificationDetails: NotificationDetailVO? + public let notificationDetails: NotificationDestinationForInApp? public init( id: String, @@ -24,7 +24,7 @@ public struct NotificationVO { body: String, createdDate: Date, imageDownloadInfo: ImageDownLoadInfo?, - notificationDetails: NotificationDetailVO? + notificationDetails: NotificationDestinationForInApp? ) { self.id = id self.isRead = isRead @@ -36,6 +36,7 @@ public struct NotificationVO { } } -public enum NotificationDetailVO { +public enum NotificationDestinationForInApp { case applicant(id: String) + case postDetailForWorker(info: RecruitmentPostInfo) } diff --git a/project/Projects/Presentation/Feature/Base/Sources/Notifications/RemoteNotificationHelper.swift b/project/Projects/Presentation/Feature/Base/Sources/Notifications/RemoteNotificationHelper.swift index 09d67e13..fe29c02b 100644 --- a/project/Projects/Presentation/Feature/Base/Sources/Notifications/RemoteNotificationHelper.swift +++ b/project/Projects/Presentation/Feature/Base/Sources/Notifications/RemoteNotificationHelper.swift @@ -16,7 +16,7 @@ public protocol RemoteNotificationHelper { var deeplinks: BehaviorSubject { get } /// 인앱에서 발생한 Notification을 처리합니다. - func handleNotificationInApp(detail: NotificationDetailVO) + func handleNotificationInApp(detail: NotificationDestinationForInApp) } public enum DeepLinkPathComponent { diff --git a/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/RemoteNotificationHelper.swift b/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/RemoteNotificationHelper.swift index b64f28a8..fbed6e7c 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/RemoteNotificationHelper.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/RemoteNotification/RemoteNotificationHelper.swift @@ -28,7 +28,7 @@ public class DefaultRemoteNotificationHelper: NSObject, RemoteNotificationHelper UNUserNotificationCenter.current().delegate = self } - public func handleNotificationInApp(detail: Domain.NotificationDetailVO) { + public func handleNotificationInApp(detail: Domain.NotificationDestinationForInApp) { switch detail { case .applicant(let id): let desination: PreDefinedDeeplinkPath = .postApplicant @@ -41,32 +41,80 @@ public class DefaultRemoteNotificationHelper: NSObject, RemoteNotificationHelper } catch { printIfDebug("딥링크 파싱실패 \(error.localizedDescription)") } + case .postDetailForWorker(let info): + + // 미구현 기능 + if info.type == .workNet { return } + + let desination: PreDefinedDeeplinkPath = .newJobPostingForWorker + do { + let parsedLinks = try deeplinkParser.makeDeeplinkList(components: desination.insideLinks, startFromRoot: false) + deeplinks.onNext(.init( + deeplinks: parsedLinks, + userInfo: ["jobPostingId": info.id] + )) + } catch { + printIfDebug("딥링크 파싱실패 \(error.localizedDescription)") + } } } } extension DefaultRemoteNotificationHelper: UNUserNotificationCenterDelegate { + private func parseNotificationToDestination(_ notification: UNNotification) -> PreDefinedDeeplinkPath? { + + let userInfo = notification.request.content.userInfo + + let _ = userInfo["notificationId"] as? String + let notificationType = userInfo["notificationType"] as? String + + guard let notificationType, let desination = PreDefinedDeeplinkPath(rawValue: notificationType) else { return nil } + + return desination + } + /// 앱이 포그라운드에 있는 경우, 노티페이케이션이 도착하기만 하면 호출된다. public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - - // 유저 인터렉션 불가 내부 이벤트로 처리야해야함 + +// guard let destination = parseNotificationToDestination(notification) else { +// return +// } +// +// let userInfo = notification.request.content.userInfo +// +// var inAppDestination: NotificationDestinationForInApp? +// +// switch destination { +// case .postApplicant: +// guard let id = userInfo["jobPostingId"] as? String else { return } +// inAppDestination = .applicant(id: id) +// case .newJobPostingForWorker: +// guard let id = userInfo["jobPostingId"] as? String else { return } +// inAppDestination = .postDetailForWorker(info: .init(type: .native, id: id)) +// } +// +// // 유저 인터렉션 불가 내부 이벤트로 처리야해야함 +// if let inAppDestination { +// +// handleNotificationInApp(detail: inAppDestination) +// } } /// 앱이 백그라운드에 있는 경우, 유저가 노티피케이션을 통해 액션을 선택한 경우 호출 public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - handleNotification(notification: response.notification) - } - - private func handleNotification(notification: UNNotification) { - + let notification = response.notification let userInfo = notification.request.content.userInfo - let _ = userInfo["notificationId"] as? String - let notificationType = userInfo["notificationType"] as? String + guard let destination = parseNotificationToDestination(notification) else { + return + } - guard let notificationType, let desination = PreDefinedDeeplinkPath(rawValue: notificationType) else { return } + handleNotification(desination: destination, userInfo: userInfo) + } + + private func handleNotification(desination: PreDefinedDeeplinkPath, userInfo: [AnyHashable: Any]?) { do { let parsedLinks = try deeplinkParser.makeDeeplinkList(components: desination.outsideLinks) From da8ebebb58fe6878bc93131830672d94b277e831 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 13 Nov 2024 17:03:25 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[IDLE-515]=20NotificationBellView=20DSKit?= =?UTF-8?q?=EC=9C=BC=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 요야보호사와 해당 기능이 중복됨으로 공통 모듈로 이동시켰습니다. --- .../NotificationBell}/NotificationBellView.swift | 16 +++++++--------- .../View/Component/WorkerMainTopView.swift | 8 +------- 2 files changed, 8 insertions(+), 16 deletions(-) rename project/Projects/Presentation/{Feature/CenterMainPage/Sources/PostBoardPage/View/Components => DSKit/Sources/CommonUI/NotificationBell}/NotificationBellView.swift (87%) diff --git a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostBoardPage/View/Components/NotificationBellView.swift b/project/Projects/Presentation/DSKit/Sources/CommonUI/NotificationBell/NotificationBellView.swift similarity index 87% rename from project/Projects/Presentation/Feature/CenterMainPage/Sources/PostBoardPage/View/Components/NotificationBellView.swift rename to project/Projects/Presentation/DSKit/Sources/CommonUI/NotificationBell/NotificationBellView.swift index aaac4c27..8e924737 100644 --- a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostBoardPage/View/Components/NotificationBellView.swift +++ b/project/Projects/Presentation/DSKit/Sources/CommonUI/NotificationBell/NotificationBellView.swift @@ -1,24 +1,22 @@ // // NotificationBellView.swift -// CenterMainPageFeature +// DSKit // -// Created by choijunios on 10/22/24. +// Created by choijunios on 11/13/24. // import UIKit -import DSKit - -class NotificationBellView: UIView { +public class NotificationBellView: UIView { - let button: UIButton = { + public let button: UIButton = { let button = UIButton() button.setImage(DSIcon.notiBell.image, for: .normal) button.imageView?.tintColor = DSColor.gray200.color return button }() - let unreadPoint: UIView = { + public let unreadPoint: UIView = { let view: UIView = .init() view.backgroundColor = DSColor.red200.color view.layer.cornerRadius = 3 @@ -27,7 +25,7 @@ class NotificationBellView: UIView { return view }() - init() { + public init() { super.init(frame: .zero) setAutoLayout() @@ -62,7 +60,7 @@ class NotificationBellView: UIView { ]) } - func setUnreadState(_ showUnreadPoint: Bool) { + public func setUnreadState(_ showUnreadPoint: Bool) { UIView.animate(withDuration: 0.35) { self.unreadPoint.alpha = showUnreadPoint ? 1 : 0 } diff --git a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/Component/WorkerMainTopView.swift b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/Component/WorkerMainTopView.swift index d7eba0b7..80ae721a 100644 --- a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/Component/WorkerMainTopView.swift +++ b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/Component/WorkerMainTopView.swift @@ -32,13 +32,7 @@ class WorkerMainTopView: UIView { return imageView }() - let notificationImageView: UIImageView = { - let imageView = UIImageView() - imageView.image = DSIcon.bell.image - imageView.tintColor = DSColor.gray200.color - imageView.isHidden = true - return imageView - }() + let notificationBellView: NotificationBellView = .init() private let disposeBag = DisposeBag() From 7e3f3a5b18b22fc1c665c771084ef951e3e82fc7 Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 13 Nov 2024 17:10:35 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[IDLE-515]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=EC=95=8C=EB=A6=BC=ED=99=95=EC=9D=B8=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/Component/WorkerMainTopView.swift | 6 +-- .../View/MainPostBoardViewController.swift | 42 ++++++++++++--- .../ViewModel/MainPostBoardViewModel.swift | 51 +++++++++++++++++++ 3 files changed, 88 insertions(+), 11 deletions(-) diff --git a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/Component/WorkerMainTopView.swift b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/Component/WorkerMainTopView.swift index 80ae721a..cbee1dfe 100644 --- a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/Component/WorkerMainTopView.swift +++ b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/Component/WorkerMainTopView.swift @@ -70,7 +70,7 @@ class WorkerMainTopView: UIView { Spacer(width: 4), locationLabel, Spacer(), - notificationImageView + notificationBellView ], innerViews ].flatMap { $0 }, @@ -89,8 +89,8 @@ class WorkerMainTopView: UIView { locationImageView.widthAnchor.constraint(equalToConstant: 32), locationImageView.heightAnchor.constraint(equalTo: locationImageView.widthAnchor), - notificationImageView.widthAnchor.constraint(equalToConstant: 32), - notificationImageView.heightAnchor.constraint(equalTo: notificationImageView.widthAnchor), + notificationBellView.widthAnchor.constraint(equalToConstant: 32), + notificationBellView.heightAnchor.constraint(equalTo: notificationBellView.widthAnchor), mainStack.leftAnchor.constraint(equalTo: self.layoutMarginsGuide.leftAnchor), mainStack.rightAnchor.constraint(equalTo: self.layoutMarginsGuide.rightAnchor), diff --git a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/MainPostBoardViewController.swift b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/MainPostBoardViewController.swift index 7878e079..3e42de15 100644 --- a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/MainPostBoardViewController.swift +++ b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/View/MainPostBoardViewController.swift @@ -21,7 +21,7 @@ class MainPostBoardViewController: BaseViewController { typealias WorknetCell = WorkerWorknetEmployCardCell // View - fileprivate let topContainer: WorkerMainTopView = { + fileprivate let titleView: WorkerMainTopView = { let container = WorkerMainTopView(innerViews: []) return container }() @@ -56,11 +56,17 @@ class MainPostBoardViewController: BaseViewController { super.bind(viewModel: viewModel) + + // ------------ 임시 설정 (RemoteConfig) + titleView.notificationBellView.isHidden = !viewModel.showNotificationButton + // ------------------------ + + // Output viewModel .workerLocationTitleText? .drive(onNext: { [weak self] titleText in - self?.topContainer.locationLabel.textString = titleText + self?.titleView.locationLabel.textString = titleText }) .disposed(by: disposeBag) @@ -99,7 +105,27 @@ class MainPostBoardViewController: BaseViewController { }) .disposed(by: disposeBag) + viewModel + .unreadNotificationExist + .drive(onNext: { [weak self] isExist in + self?.titleView.notificationBellView + .setUnreadState(isExist) + }) + .disposed(by: disposeBag) + + // Input + self.rx.viewWillAppear + .mapToVoid() + .bind(to: viewModel.viewWillAppear) + .disposed(by: disposeBag) + + // 알림 페이지 버튼 클릭 + titleView.notificationBellView.button.rx.tap + .bind(to: viewModel.notificationButtonClicked) + .disposed(by: disposeBag) + + self.emptyScreen .editProfile.rx.tap .bind(to: viewModel.editProfileButtonClicked) @@ -143,7 +169,7 @@ class MainPostBoardViewController: BaseViewController { private func setLayout() { [ - topContainer, + titleView, postTableView, emptyScreen, ].forEach { @@ -152,16 +178,16 @@ class MainPostBoardViewController: BaseViewController { } NSLayoutConstraint.activate([ - topContainer.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - topContainer.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), - topContainer.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), + titleView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + titleView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), + titleView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), - postTableView.topAnchor.constraint(equalTo: topContainer.bottomAnchor), + postTableView.topAnchor.constraint(equalTo: titleView.bottomAnchor), postTableView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), postTableView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), postTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor), - emptyScreen.topAnchor.constraint(equalTo: topContainer.bottomAnchor), + emptyScreen.topAnchor.constraint(equalTo: titleView.bottomAnchor), emptyScreen.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor), emptyScreen.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor), emptyScreen.bottomAnchor.constraint(equalTo: view.bottomAnchor), diff --git a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/ViewModel/MainPostBoardViewModel.swift b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/ViewModel/MainPostBoardViewModel.swift index b127afa9..7aa06157 100644 --- a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/ViewModel/MainPostBoardViewModel.swift +++ b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/PostBoard/ViewModel/MainPostBoardViewModel.swift @@ -50,6 +50,15 @@ protocol WorkerRecruitmentPostBoardVMable: WorkerAppliablePostBoardVMable { /// 요양보호사 위치 정보를 전달합니다. var workerLocationTitleText: Driver? { get } + + var viewWillAppear: PublishSubject { get } + + /// 읽지않은 알림이 있는 경우 + var unreadNotificationExist: Driver { get } + + /// ‼️임시조치: 알림 확인창 오픈 여부를 설정합니다. + var showNotificationButton: Bool { get } + var notificationButtonClicked: PublishSubject { get } } class MainPostBoardViewModel: BaseViewModel, WorkerRecruitmentPostBoardVMable { @@ -57,23 +66,30 @@ class MainPostBoardViewModel: BaseViewModel, WorkerRecruitmentPostBoardVMable { @Injected var recruitmentPostUseCase: RecruitmentPostUseCase @Injected var workerProfileUseCase: WorkerProfileUseCase + @Injected var remoteConfigService: RemoteConfigService + @Injected var norificationsRepository: NotificationsRepository + // Navigation var presentMyProfile: (() -> ())? var presentSnackBar: ((IdleSnackBarRO, CGFloat) -> ())? var presentPostDetailPage: ((String, PostOriginType) -> ())? + var presentNotificationPage: (() -> ())? // Output var postBoardData: Driver<(isRefreshed: Bool, postData: [RecruitmentPostForWorkerRepresentable])>? var workerLocationTitleText: Driver? var idleAlertVM: RxCocoa.Driver? + var unreadNotificationExist: Driver = .empty() // Input + var viewWillAppear: PublishSubject = .init() var editProfileButtonClicked: PublishRelay = .init() var requestInitialPageRequest: PublishRelay = .init() var requestWorkerLocation: PublishRelay = .init() var requestNextPage: PublishRelay = .init() var applyButtonClicked: PublishRelay<(postId: String, postTitle: String)> = .init() + var notificationButtonClicked: PublishSubject = .init() // Paging /// 값이 nil이라면 요청을 보내지 않습니다. @@ -84,10 +100,36 @@ class MainPostBoardViewModel: BaseViewModel, WorkerRecruitmentPostBoardVMable { // Observable let dispostBag = DisposeBag() + var showNotificationButton: Bool = false + override init() { super.init() + // Notification버튼 활성화 여부 + do { + let value = try remoteConfigService.getBoolProperty(key: "show_notification_button") + self.showNotificationButton = value + } catch { + fatalError(error.localizedDescription) + } + // ----------------------------------------------- + + + // 읽지 않은 알람 분기 + self.unreadNotificationExist = viewWillAppear + .unretained(self) + .flatMap { (vm, _) in + vm.norificationsRepository + .unreadNotificationCount() + } + .compactMap { $0.value } + .map { count in + count > 0 + } + .asDriver(onErrorDriveWith: .never()) + + // MARK: 상단 위치정보 불러오기 workerLocationTitleText = requestWorkerLocation .flatMap { [workerProfileUseCase] _ in @@ -287,6 +329,15 @@ class MainPostBoardViewModel: BaseViewModel, WorkerRecruitmentPostBoardVMable { }) .disposed(by: dispostBag) + + // MARK: Notification page + notificationButtonClicked + .unretained(self) + .subscribe(onNext: { (obj, _) in + obj.presentNotificationPage?() + }) + .disposed(by: disposeBag) + Observable .merge( applyRequestFailureAlert, From 88d47971177918809c05be380b8c63a644c916ce Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 13 Nov 2024 17:12:20 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[IDLE-515]=20=EC=9A=94=EC=96=91=EB=B3=B4?= =?UTF-8?q?=ED=98=B8=EC=82=AC=20=EC=95=8C=EB=9E=8C=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Root/Sources/Application/AppCoordinator.swift | 2 ++ .../WorkerMainPage/Sources/WorkerMainPageCoordinator.swift | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/project/Projects/Presentation/Feature/Root/Sources/Application/AppCoordinator.swift b/project/Projects/Presentation/Feature/Root/Sources/Application/AppCoordinator.swift index de80905b..666be721 100644 --- a/project/Projects/Presentation/Feature/Root/Sources/Application/AppCoordinator.swift +++ b/project/Projects/Presentation/Feature/Root/Sources/Application/AppCoordinator.swift @@ -166,6 +166,8 @@ extension AppCoordinator { coordinator.startFlow = { [weak self] destination in guard let self else { return } switch destination { + case .notificationPage: + userNotifications() case .accountDeregisterPage: accountDeregister(userType: .worker) case .authFlow: diff --git a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/WorkerMainPageCoordinator.swift b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/WorkerMainPageCoordinator.swift index f0cb3a6a..7c3288bb 100644 --- a/project/Projects/Presentation/Feature/WorkerMainPage/Sources/WorkerMainPageCoordinator.swift +++ b/project/Projects/Presentation/Feature/WorkerMainPage/Sources/WorkerMainPageCoordinator.swift @@ -15,6 +15,7 @@ public enum WorkerMainPageCoordinatorDestination { case authFlow case myProfilePage case accountDeregisterPage + case notificationPage } public class WorkerMainPageCoordinator: BaseCoordinator { @@ -86,6 +87,9 @@ public extension WorkerMainPageCoordinator { func presentPostBoardPage(controller: UINavigationController) { let viewModel = MainPostBoardViewModel() + viewModel.presentNotificationPage = { [weak self] in + self?.startFlow(.notificationPage) + } viewModel.presentMyProfile = { [weak self] in self?.startFlow(.myProfilePage) } From 38751a01912313875a2a759653774fecef533a3b Mon Sep 17 00:00:00 2001 From: J0onYEong Date: Wed, 13 Nov 2024 17:26:24 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[IDLE-000]=20=EA=B3=B5=EA=B3=A0=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=EC=8B=9C=20=EC=8A=A4=ED=85=8C=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=B2=94=EC=9D=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreatePost/View/CreatePostViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostRegisterPage/CreatePost/View/CreatePostViewController.swift b/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostRegisterPage/CreatePost/View/CreatePostViewController.swift index 972f9ce2..4d4b3d3f 100644 --- a/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostRegisterPage/CreatePost/View/CreatePostViewController.swift +++ b/project/Projects/Presentation/Feature/CenterMainPage/Sources/PostRegisterPage/CreatePost/View/CreatePostViewController.swift @@ -59,7 +59,7 @@ public class CreatePostViewController: BaseViewController { }() lazy var statusBar: ProcessStatusBar = { let view = ProcessStatusBar( - processCount: RegisterRecruitmentPage.allCases.count, + processCount: RegisterRecruitmentPage.stages.count, startIndex: 0 ) return view