From bdf171c514fc5acb50f42beea3cf6c8819b58d51 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 12:07:07 +0900 Subject: [PATCH 01/23] =?UTF-8?q?:recycle:=20::=20[#101]=20DesignSystem=20?= =?UTF-8?q?/=20=EC=8B=A0=EC=B2=AD=ED=95=9C=20=ED=95=99=EC=83=9D=EC=9D=98?= =?UTF-8?q?=20CardView=20=EC=9E=AC=EC=82=AC=EC=9A=A9=EC=84=B1=20=EC=A3=BC?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Projects/Feature/BaseFeature/Project.swift | 1 - .../Sources/Scene/View/SelfStudyCell.swift | 71 +++-------- .../UserInterface/DesignSystem/Project.swift | 1 + .../CardView/AppliedStudentCardView.swift | 116 ++++++++++++++++++ 4 files changed, 132 insertions(+), 57 deletions(-) create mode 100644 Projects/UserInterface/DesignSystem/Sources/CardView/AppliedStudentCardView.swift diff --git a/Projects/Feature/BaseFeature/Project.swift b/Projects/Feature/BaseFeature/Project.swift index d31e8a8b..3c64319d 100644 --- a/Projects/Feature/BaseFeature/Project.swift +++ b/Projects/Feature/BaseFeature/Project.swift @@ -6,7 +6,6 @@ let project = Project.module( name: ModulePaths.Feature.BaseFeature.rawValue, targets: [ .implements(module: .feature(.BaseFeature), product: .framework, dependencies: [ - .SPM.MSGLayout, .SPM.Moordinator, .SPM.Store, .SPM.IQKeyboardManagerSwift, diff --git a/Projects/Feature/SelfStudyFeature/Sources/Scene/View/SelfStudyCell.swift b/Projects/Feature/SelfStudyFeature/Sources/Scene/View/SelfStudyCell.swift index ba6748f4..d3f69d76 100644 --- a/Projects/Feature/SelfStudyFeature/Sources/Scene/View/SelfStudyCell.swift +++ b/Projects/Feature/SelfStudyFeature/Sources/Scene/View/SelfStudyCell.swift @@ -12,32 +12,7 @@ protocol SelfStudyCellDelegate: AnyObject { final class SelfStudyCell: BaseTableViewCell { weak var delegate: (any SelfStudyCellDelegate)? - private let containerView = UIView() - private let rankLabel = DotoriLabel(textColor: .neutral(.n20), font: .caption) - private let selfStudyCheckBox = DotoriCheckBox() - .set(\.isHidden, true) - private let userImageView = DotoriIconView( - size: .custom(.init(width: 64, height: 64)), - image: .Dotori.personRectangle - ) - private let nameLabel = DotoriLabel(textColor: .neutral(.n10), font: .body1) - private let genderImageView = DotoriIconView( - size: .custom(.init(width: 16, height: 16)), - image: .Dotori.men - ) - private let stuNumLabel = DotoriLabel(textColor: .neutral(.n20), font: .caption) - private lazy var profileStackView = VStackView(spacing: 8) { - userImageView - - HStackView(spacing: 2) { - nameLabel - - genderImageView - } - .alignment(.center) - - stuNumLabel - }.alignment(.center) + private let appliedStudentCardView = AppliedStudentCardView() private let medalImageView = DotoriIconView( size: .custom(.init(width: 56, height: 80)), image: nil @@ -60,36 +35,19 @@ final class SelfStudyCell: BaseTableViewCell { override func addView() { contentView.addSubviews { - containerView + appliedStudentCardView } - containerView.addSubviews { - rankLabel - selfStudyCheckBox - profileStackView + appliedStudentCardView.addSubviews { medalImageView } } override func setLayout() { MSGLayout.buildLayout { - containerView.layout + appliedStudentCardView.layout .horizontal(.toSuperview(), .equal(20)) .vertical(.toSuperview(), .equal(12)) - rankLabel.layout - .top(.toSuperview(), .equal(12)) - .leading(.toSuperview(), .equal(16)) - - selfStudyCheckBox.layout - .top(.toSuperview(), .equal(12)) - .trailing(.toSuperview(), .equal(-12)) - .size(24) - - profileStackView.layout - .centerX(.toSuperview()) - .top(.toSuperview(), .equal(28)) - .bottom(.toSuperview(), .equal(-28)) - medalImageView.layout .trailing(.toSuperview(), .equal(-8)) .bottom(.toSuperview(), .equal(20)) @@ -97,31 +55,32 @@ final class SelfStudyCell: BaseTableViewCell { } override func configureView() { - self.containerView.backgroundColor = .dotori(.background(.card)) - self.containerView.cornerRadius = 16 - DotoriShadow.cardShadow(card: self.containerView) self.backgroundColor = .clear self.selectionStyle = .none } override func adapt(model: SelfStudyRankModel) { super.adapt(model: model) - self.rankLabel.text = "\(model.rank)" - self.nameLabel.text = model.memberName - self.genderImageView.image = model.gender == .man ? .Dotori.men : .Dotori.women - self.stuNumLabel.text = model.stuNum - self.selfStudyCheckBox.isChecked = model.selfStudyCheck + self.appliedStudentCardView.updateContent( + with: .init( + rank: model.rank, + name: model.memberName, + gender: model.gender == .man ? .man : .woman, + stuNum: model.stuNum, + isChecked: model.selfStudyCheck + ) + ) self.medalImageView.image = self.rankToImage(rank: model.rank) } func setUserRole(userRole: UserRoleType) { - selfStudyCheckBox.isHidden = userRole == .member + appliedStudentCardView.setIsHiddenAttendanceCheckBox(userRole == .member) } } private extension SelfStudyCell { func bindAction() { - selfStudyCheckBox.checkBoxDidTapPublisher + appliedStudentCardView.attendanceCheckBoxPublisher .throttle(for: 1, scheduler: RunLoop.main, latest: true) .compactMap { [weak self] (checked) -> (Int, Bool)? in guard let id = self?.model?.id else { return nil } diff --git a/Projects/UserInterface/DesignSystem/Project.swift b/Projects/UserInterface/DesignSystem/Project.swift index 4fafa9e7..ba7c8817 100644 --- a/Projects/UserInterface/DesignSystem/Project.swift +++ b/Projects/UserInterface/DesignSystem/Project.swift @@ -12,6 +12,7 @@ let project = Project.module( resources: ["Resources/**"], dependencies: [ .SPM.Anim, + .SPM.MSGLayout, .userInterface(target: .DWebKit), .shared(target: .GlobalThirdPartyLibrary), .shared(target: .UIKitUtil) diff --git a/Projects/UserInterface/DesignSystem/Sources/CardView/AppliedStudentCardView.swift b/Projects/UserInterface/DesignSystem/Sources/CardView/AppliedStudentCardView.swift new file mode 100644 index 00000000..68a2d280 --- /dev/null +++ b/Projects/UserInterface/DesignSystem/Sources/CardView/AppliedStudentCardView.swift @@ -0,0 +1,116 @@ +import Combine +import MSGLayout +import UIKit + +public struct AppliedStudentViewModel: Equatable { + public let rank: Int + public let name: String + public let gender: Gender + public let stuNum: String + public let isChecked: Bool + + public enum Gender { + case man + case woman + } + + public init(rank: Int, name: String, gender: Gender, stuNum: String, isChecked: Bool) { + self.rank = rank + self.name = name + self.gender = gender + self.stuNum = stuNum + self.isChecked = isChecked + } +} + +public protocol AppliedStudentCardViewActionProtocol { + var attendanceCheckBoxPublisher: AnyPublisher { get } +} + +public final class AppliedStudentCardView: UIView { + private let rankLabel = DotoriLabel(textColor: .neutral(.n20), font: .caption) + private let attendanceCheckBox = DotoriCheckBox() + .set(\.isHidden, true) + private let userImageView = DotoriIconView( + size: .custom(.init(width: 64, height: 64)), + image: .Dotori.personRectangle + ) + private let nameLabel = DotoriLabel(textColor: .neutral(.n10), font: .body1) + private let genderImageView = DotoriIconView( + size: .custom(.init(width: 16, height: 16)), + image: .Dotori.men + ) + private let stuNumLabel = DotoriLabel(textColor: .neutral(.n20), font: .caption) + private lazy var profileStackView = VStackView(spacing: 8) { + userImageView + + HStackView(spacing: 2) { + nameLabel + + genderImageView + } + .alignment(.center) + + stuNumLabel + }.alignment(.center) + + public init() { + super.init(frame: .zero) + configureView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func updateContent( + with viewModel: AppliedStudentViewModel + ) { + rankLabel.text = "\(viewModel.rank)" + nameLabel.text = viewModel.name + genderImageView.image = (viewModel.gender == .man ? UIImage.Dotori.men : .Dotori.women) + .withTintColor(.dotori(.neutral(.n10)), renderingMode: .alwaysOriginal) + stuNumLabel.text = viewModel.stuNum + attendanceCheckBox.isChecked = viewModel.isChecked + } + + public func setIsHiddenAttendanceCheckBox(_ isHidden: Bool) { + attendanceCheckBox.isHidden = isHidden + } +} + +private extension AppliedStudentCardView { + func configureView() { + self.addSubviews { + rankLabel + attendanceCheckBox + profileStackView + } + + MSGLayout.buildLayout { + rankLabel.layout + .top(.toSuperview(), .equal(12)) + .leading(.toSuperview(), .equal(16)) + + attendanceCheckBox.layout + .top(.toSuperview(), .equal(12)) + .trailing(.toSuperview(), .equal(-12)) + .size(24) + + profileStackView.layout + .centerX(.toSuperview()) + .top(.toSuperview(), .equal(28)) + .bottom(.toSuperview(), .equal(-28)) + } + + self.backgroundColor = .dotori(.background(.card)) + self.cornerRadius = 16 + DotoriShadow.cardShadow(card: self) + } +} + +extension AppliedStudentCardView: AppliedStudentCardViewActionProtocol { + public var attendanceCheckBoxPublisher: AnyPublisher { + attendanceCheckBox.checkBoxDidTapPublisher + } +} From 8947f94bd16679bdd782422d26943ff75cd1e37e Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 12:51:44 +0900 Subject: [PATCH 02/23] :lipstick: :: [#101] MassageFeature / Massage UI --- .../Sources/Scene/MassageStore.swift | 33 ++++++++ .../Sources/Scene/MassageViewController.swift | 53 ++++++++++++ .../Sources/Scene/View/MassageCell.swift | 82 +++++++++++++++++++ .../Resources/en.lproj/Massage.strings | 2 + .../Resources/ko.lproj/Massage.strings | 2 + 5 files changed, 172 insertions(+) create mode 100644 Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift create mode 100644 Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift create mode 100644 Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift new file mode 100644 index 00000000..39c83c49 --- /dev/null +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift @@ -0,0 +1,33 @@ +import BaseFeature +import Combine +import Moordinator +import Store + +final class MassageStore: BaseStore { + var route: PassthroughSubject = .init() + var subscription: Set = .init() + var initialState: State + var stateSubject: CurrentValueSubject + + init() { + self.initialState = .init() + self.stateSubject = .init(initialState) + } + + struct State {} + enum Action{} + enum Mutation {} +} + +extension MassageStore { + func mutate(state: State, action: Action) -> SideEffect { + .none + } +} + +extension MassageStore { + func reduce(state: State, mutate: Mutation) -> State { + var newState = state + return newState + } +} diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift new file mode 100644 index 00000000..f79b7a24 --- /dev/null +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift @@ -0,0 +1,53 @@ +import BaseFeature +import DesignSystem +import Localization +import MSGLayout +import UIKit +import UIKitUtil + +final class MassageViewController: BaseStoredViewController { + private let massageNavigationBarLabel = DotoriNavigationBarLabel(text: L10n.Massage.massageTitle) + private let massageTableView = UITableView() + .set(\.backgroundColor, .clear) + .set(\.separatorStyle, .none) + .set(\.isHidden, true) + .then { + $0.register(cellType: MassageCell.self) + } + private let massageRefreshContorol = UIRefreshControl() + private lazy var massageTableAdapter = TableViewAdapter>( + tableView: massageTableView + ) { [store] tableView, indexPath, item in + let cell: MassageCell = tableView.dequeueReusableCell(for: indexPath) + cell.adapt(model: item) +// cell.setUserRole(userRole: store.currentState.currentUserRole) +// cell.delegate = store + return cell + } + + override func addView() { + view.addSubviews { + massageTableView + } + massageTableView.refreshControl = massageRefreshContorol + } + + override func setLayout() { + MSGLayout.buildLayout { + massageTableView.layout + .top(.toSuperview(), .equal(24)) + .horizontal(.toSuperview()) + .bottom(.to(view.safeAreaLayoutGuide)) + } + } + + override func configureNavigation() { + self.navigationItem.setLeftBarButton(massageNavigationBarLabel, animated: true) + } + + override func bindState() { + massageTableAdapter.updateSections(sections: [ + .init(items: [(), ()]) + ]) + } +} diff --git a/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift b/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift new file mode 100644 index 00000000..f276d703 --- /dev/null +++ b/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift @@ -0,0 +1,82 @@ +import BaseDomainInterface +import BaseFeature +import Combine +import DesignSystem +import MSGLayout +import MassageDomainInterface +import UIKit + +protocol MassageCellDelegate: AnyObject { + func massageCheckBoxDidTap(id: Int, isChecked: Bool) +} + +final class MassageCell: BaseTableViewCell { + weak var delegate: (any MassageCellDelegate)? + private let appliedStudentCardView = AppliedStudentCardView() + private var subscription = Set() + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + bindAction() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func prepareForReuse() { + super.prepareForReuse() + self.subscription.removeAll() + } + + override func addView() { + contentView.addSubviews { + appliedStudentCardView + } + } + + override func setLayout() { + MSGLayout.buildLayout { + appliedStudentCardView.layout + .horizontal(.toSuperview(), .equal(20)) + .vertical(.toSuperview(), .equal(12)) + } + } + + override func configureView() { + self.backgroundColor = .clear + self.selectionStyle = .none + } + + override func adapt(model: Void) { + super.adapt(model: model) +// self.appliedStudentCardView.updateContent( +// with: .init( +// rank: model.rank, +// name: model.memberName, +// gender: model.gender == .man ? .man : .woman, +// stuNum: model.stuNum, +// isChecked: model.selfStudyCheck +// ) +// ) + } + + func setUserRole(userRole: UserRoleType) { + appliedStudentCardView.setIsHiddenAttendanceCheckBox(userRole == .member) + } +} + +private extension MassageCell { + func bindAction() { +// appliedStudentCardView.attendanceCheckBoxPublisher +// .throttle(for: 1, scheduler: RunLoop.main, latest: true) +// .compactMap { [weak self] (checked) -> (Int, Bool)? in +// guard let id = self?.model?.id else { return nil } +// return (id, checked) +// } +// .sink(with: self, receiveValue: { owner, checked in +// owner.delegate?.selfStudyCheckBoxDidTap(id: checked.0, isChecked: checked.1) +// }) +// .store(in: &subscription) + } +} diff --git a/Projects/UserInterface/Localization/Resources/en.lproj/Massage.strings b/Projects/UserInterface/Localization/Resources/en.lproj/Massage.strings index d5d0213e..5944d7c7 100644 --- a/Projects/UserInterface/Localization/Resources/en.lproj/Massage.strings +++ b/Projects/UserInterface/Localization/Resources/en.lproj/Massage.strings @@ -5,3 +5,5 @@ Created by 최형우 on 2023/06/07. Copyright © 2023 com.msg. All rights reserved. */ + +"massage_title" = "Massage"; diff --git a/Projects/UserInterface/Localization/Resources/ko.lproj/Massage.strings b/Projects/UserInterface/Localization/Resources/ko.lproj/Massage.strings index d5d0213e..fac40e31 100644 --- a/Projects/UserInterface/Localization/Resources/ko.lproj/Massage.strings +++ b/Projects/UserInterface/Localization/Resources/ko.lproj/Massage.strings @@ -5,3 +5,5 @@ Created by 최형우 on 2023/06/07. Copyright © 2023 com.msg. All rights reserved. */ + +"massage_title" = "안마의자"; From 8b4c7528600f3e79ad783ef40d4415e479b71089 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 12:54:01 +0900 Subject: [PATCH 03/23] =?UTF-8?q?:sparkles:=20::=20[#101]=20MassageFeature?= =?UTF-8?q?=20/=20Demo=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Demo/Resources/LaunchScreen.storyboard | 25 +++++++++++++++++++ .../Demo/Sources/AppDelegate.swift | 19 ++++++++++++++ Projects/Feature/MassageFeature/Project.swift | 12 +++++++-- 3 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 Projects/Feature/MassageFeature/Demo/Resources/LaunchScreen.storyboard create mode 100644 Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift diff --git a/Projects/Feature/MassageFeature/Demo/Resources/LaunchScreen.storyboard b/Projects/Feature/MassageFeature/Demo/Resources/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/Projects/Feature/MassageFeature/Demo/Resources/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift new file mode 100644 index 00000000..ef2bae04 --- /dev/null +++ b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift @@ -0,0 +1,19 @@ +import UIKit + +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + window = UIWindow(frame: UIScreen.main.bounds) + let viewController = UIViewController() + viewController.view.backgroundColor = .yellow + window?.rootViewController = viewController + window?.makeKeyAndVisible() + + return true + } +} diff --git a/Projects/Feature/MassageFeature/Project.swift b/Projects/Feature/MassageFeature/Project.swift index 4a304664..57857e75 100644 --- a/Projects/Feature/MassageFeature/Project.swift +++ b/Projects/Feature/MassageFeature/Project.swift @@ -7,10 +7,18 @@ let project = Project.module( targets: [ .implements(module: .feature(.MassageFeature), dependencies: [ .feature(target: .BaseFeature), - .domain(target: .AuthDomain, type: .interface) + .domain(target: .MassageDomain, type: .interface), + .domain(target: .UserDomain, type: .interface) ]), .tests(module: .feature(.MassageFeature), dependencies: [ - .feature(target: .MassageFeature) + .feature(target: .MassageFeature), + .domain(target: .MassageDomain, type: .testing), + .domain(target: .UserDomain, type: .testing) + ]), + .demo(module: .feature(.NoticeFeature), dependencies: [ + .feature(target: .NoticeFeature), + .domain(target: .MassageDomain, type: .testing), + .domain(target: .UserDomain, type: .testing) ]) ] ) From 47fe397c3c39504d3e522b1d0da2269899b9ce05 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 12:56:01 +0900 Subject: [PATCH 04/23] :pencil2: :: [#101] MassageFeature / NoticeFeatureDemo -> MassageFeatureDemo --- Projects/Feature/MassageFeature/Project.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/MassageFeature/Project.swift b/Projects/Feature/MassageFeature/Project.swift index 57857e75..5ce9455f 100644 --- a/Projects/Feature/MassageFeature/Project.swift +++ b/Projects/Feature/MassageFeature/Project.swift @@ -15,8 +15,8 @@ let project = Project.module( .domain(target: .MassageDomain, type: .testing), .domain(target: .UserDomain, type: .testing) ]), - .demo(module: .feature(.NoticeFeature), dependencies: [ - .feature(target: .NoticeFeature), + .demo(module: .feature(.MassageFeature), dependencies: [ + .feature(target: .MassageFeature), .domain(target: .MassageDomain, type: .testing), .domain(target: .UserDomain, type: .testing) ]) From 7db3ef12e4f982bfb0579a6b0797cac20dc923ab Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 13:20:08 +0900 Subject: [PATCH 05/23] :truck: :: [#101] BaseDomainInterface / SelfStudyDomainInterface GenderType -> BaseDomainInterface GenderType --- .../Interface/Enums/GenderType.swift | 0 .../Interface/Entity/MassageRankEntity.swift | 9 +++++++++ .../Interface/UseCase/FetchMassageRankListUseCase.swift | 9 +++++++++ 3 files changed, 18 insertions(+) rename Projects/Domain/{SelfStudyDomain => BaseDomain}/Interface/Enums/GenderType.swift (100%) create mode 100644 Projects/Domain/MassageDomain/Interface/Entity/MassageRankEntity.swift create mode 100644 Projects/Domain/MassageDomain/Interface/UseCase/FetchMassageRankListUseCase.swift diff --git a/Projects/Domain/SelfStudyDomain/Interface/Enums/GenderType.swift b/Projects/Domain/BaseDomain/Interface/Enums/GenderType.swift similarity index 100% rename from Projects/Domain/SelfStudyDomain/Interface/Enums/GenderType.swift rename to Projects/Domain/BaseDomain/Interface/Enums/GenderType.swift diff --git a/Projects/Domain/MassageDomain/Interface/Entity/MassageRankEntity.swift b/Projects/Domain/MassageDomain/Interface/Entity/MassageRankEntity.swift new file mode 100644 index 00000000..ed426c0e --- /dev/null +++ b/Projects/Domain/MassageDomain/Interface/Entity/MassageRankEntity.swift @@ -0,0 +1,9 @@ +// +// MassageRankEntity.swift +// MassageDomainInterface +// +// Created by 최형우 on 2023/07/26. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation diff --git a/Projects/Domain/MassageDomain/Interface/UseCase/FetchMassageRankListUseCase.swift b/Projects/Domain/MassageDomain/Interface/UseCase/FetchMassageRankListUseCase.swift new file mode 100644 index 00000000..40ba55e5 --- /dev/null +++ b/Projects/Domain/MassageDomain/Interface/UseCase/FetchMassageRankListUseCase.swift @@ -0,0 +1,9 @@ +// +// FetchMassageRankListUseCase.swift +// MassageDomainInterface +// +// Created by 최형우 on 2023/07/26. +// Copyright © 2023 com.msg. All rights reserved. +// + +import Foundation From 3d591c2c29a7869262c418ec92e96faf356b17c8 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 13:25:00 +0900 Subject: [PATCH 06/23] :sparkles: :: [#101] MassageDomain / FetchMassageRankListUseCase Interface --- .../DataSource/RemoteMassageDataSource.swift | 1 + .../Interface/Entity/MassageRankEntity.swift | 25 +++++++++++++------ .../Interface/Model/MassageRankModel.swift | 1 + .../Repository/MassageRepository.swift | 1 + .../UseCase/FetchMassageRankListUseCase.swift | 12 +++------ .../FetchSelfStudyRankSearchRequestDTO.swift | 1 + .../Entity/SelfStudyRankEntity.swift | 1 + .../FecthSelfStudyRankListResponseDTO.swift | 1 + .../Demo/Sources/AppDelegate.swift | 8 ++++-- .../Sources/Scene/MassageStore.swift | 2 +- 10 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 Projects/Domain/MassageDomain/Interface/Model/MassageRankModel.swift diff --git a/Projects/Domain/MassageDomain/Interface/DataSource/RemoteMassageDataSource.swift b/Projects/Domain/MassageDomain/Interface/DataSource/RemoteMassageDataSource.swift index 4a75eaa3..1f8fa6a0 100644 --- a/Projects/Domain/MassageDomain/Interface/DataSource/RemoteMassageDataSource.swift +++ b/Projects/Domain/MassageDomain/Interface/DataSource/RemoteMassageDataSource.swift @@ -2,4 +2,5 @@ public protocol RemoteMassageDataSource { func fetchMassageInfo() async throws -> MassageInfoEntity func applyMassage() async throws func cancelMassage() async throws + func fetchMassageRankList() async throws -> [MassageRankEntity] } diff --git a/Projects/Domain/MassageDomain/Interface/Entity/MassageRankEntity.swift b/Projects/Domain/MassageDomain/Interface/Entity/MassageRankEntity.swift index ed426c0e..bcdb6b1c 100644 --- a/Projects/Domain/MassageDomain/Interface/Entity/MassageRankEntity.swift +++ b/Projects/Domain/MassageDomain/Interface/Entity/MassageRankEntity.swift @@ -1,9 +1,18 @@ -// -// MassageRankEntity.swift -// MassageDomainInterface -// -// Created by 최형우 on 2023/07/26. -// Copyright © 2023 com.msg. All rights reserved. -// - +import BaseDomainInterface import Foundation + +public struct MassageRankEntity: Equatable { + public let id: Int + public let rank: Int + public let stuNum: String + public let memberName: String + public let gender: GenderType + + public init(id: Int, rank: Int, stuNum: String, memberName: String, gender: GenderType) { + self.id = id + self.rank = rank + self.stuNum = stuNum + self.memberName = memberName + self.gender = gender + } +} diff --git a/Projects/Domain/MassageDomain/Interface/Model/MassageRankModel.swift b/Projects/Domain/MassageDomain/Interface/Model/MassageRankModel.swift new file mode 100644 index 00000000..ce190127 --- /dev/null +++ b/Projects/Domain/MassageDomain/Interface/Model/MassageRankModel.swift @@ -0,0 +1 @@ +public typealias MassageRankModel = MassageRankEntity diff --git a/Projects/Domain/MassageDomain/Interface/Repository/MassageRepository.swift b/Projects/Domain/MassageDomain/Interface/Repository/MassageRepository.swift index 1537a46c..58d94827 100644 --- a/Projects/Domain/MassageDomain/Interface/Repository/MassageRepository.swift +++ b/Projects/Domain/MassageDomain/Interface/Repository/MassageRepository.swift @@ -2,4 +2,5 @@ public protocol MassageRepository { func fetchMassageInfo() async throws -> MassageInfoEntity func applyMassage() async throws func cancelMassage() async throws + func fetchMassageRankList() async throws -> [MassageRankEntity] } diff --git a/Projects/Domain/MassageDomain/Interface/UseCase/FetchMassageRankListUseCase.swift b/Projects/Domain/MassageDomain/Interface/UseCase/FetchMassageRankListUseCase.swift index 40ba55e5..c4ba2980 100644 --- a/Projects/Domain/MassageDomain/Interface/UseCase/FetchMassageRankListUseCase.swift +++ b/Projects/Domain/MassageDomain/Interface/UseCase/FetchMassageRankListUseCase.swift @@ -1,9 +1,3 @@ -// -// FetchMassageRankListUseCase.swift -// MassageDomainInterface -// -// Created by 최형우 on 2023/07/26. -// Copyright © 2023 com.msg. All rights reserved. -// - -import Foundation +public protocol FetchMassageRankListUseCase { + func callAsFunction() async throws -> [MassageRankModel] +} diff --git a/Projects/Domain/SelfStudyDomain/Interface/DTO/Request/FetchSelfStudyRankSearchRequestDTO.swift b/Projects/Domain/SelfStudyDomain/Interface/DTO/Request/FetchSelfStudyRankSearchRequestDTO.swift index 18291f52..5c3740a4 100644 --- a/Projects/Domain/SelfStudyDomain/Interface/DTO/Request/FetchSelfStudyRankSearchRequestDTO.swift +++ b/Projects/Domain/SelfStudyDomain/Interface/DTO/Request/FetchSelfStudyRankSearchRequestDTO.swift @@ -1,3 +1,4 @@ +import BaseDomainInterface import Foundation public struct FetchSelfStudyRankSearchRequestDTO { diff --git a/Projects/Domain/SelfStudyDomain/Interface/Entity/SelfStudyRankEntity.swift b/Projects/Domain/SelfStudyDomain/Interface/Entity/SelfStudyRankEntity.swift index 840e8265..d625807a 100644 --- a/Projects/Domain/SelfStudyDomain/Interface/Entity/SelfStudyRankEntity.swift +++ b/Projects/Domain/SelfStudyDomain/Interface/Entity/SelfStudyRankEntity.swift @@ -1,3 +1,4 @@ +import BaseDomainInterface import Foundation public struct SelfStudyRankEntity: Equatable { diff --git a/Projects/Domain/SelfStudyDomain/Sources/DTO/Response/FecthSelfStudyRankListResponseDTO.swift b/Projects/Domain/SelfStudyDomain/Sources/DTO/Response/FecthSelfStudyRankListResponseDTO.swift index 7e15e5ef..dd5b7035 100644 --- a/Projects/Domain/SelfStudyDomain/Sources/DTO/Response/FecthSelfStudyRankListResponseDTO.swift +++ b/Projects/Domain/SelfStudyDomain/Sources/DTO/Response/FecthSelfStudyRankListResponseDTO.swift @@ -1,3 +1,4 @@ +import BaseDomainInterface import Foundation import SelfStudyDomainInterface diff --git a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift index ef2bae04..6b89b4a7 100644 --- a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift @@ -1,4 +1,6 @@ import UIKit +import Inject +@testable import MassageFeature @main final class AppDelegate: UIResponder, UIApplicationDelegate { @@ -9,8 +11,10 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) - let viewController = UIViewController() - viewController.view.backgroundColor = .yellow + let store = MassageStore() + let viewController = Inject.ViewControllerHost( + UINavigationController(rootViewController: MassageViewController(store: store)) + ) window?.rootViewController = viewController window?.makeKeyAndVisible() diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift index 39c83c49..58bdfa7a 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift @@ -15,7 +15,7 @@ final class MassageStore: BaseStore { } struct State {} - enum Action{} + enum Action {} enum Mutation {} } From 0350d1a8fd58b7159397f2616d227cd111231cba Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 13:37:32 +0900 Subject: [PATCH 07/23] :sparkles: :: [#101] MassageDomain / FetchMassageRankListUseCase Implementation --- .../Assembly/MassageDomainAssembly.swift | 4 +++ .../FetchMassageRankListResponseDTO.swift | 28 +++++++++++++++++++ .../DataSource/Remote/MassageEndpoint.swift | 4 +++ .../Remote/RemoteMassageDataSourceImpl.swift | 8 ++++++ .../Repository/MassageRepositoryImpl.swift | 4 +++ .../FetchMassageRankListUseCaseImpl.swift | 13 +++++++++ 6 files changed, 61 insertions(+) create mode 100644 Projects/Domain/MassageDomain/Sources/DTO/Response/FetchMassageRankListResponseDTO.swift create mode 100644 Projects/Domain/MassageDomain/Sources/UseCase/FetchMassageRankListUseCaseImpl.swift diff --git a/Projects/Domain/MassageDomain/Sources/Assembly/MassageDomainAssembly.swift b/Projects/Domain/MassageDomain/Sources/Assembly/MassageDomainAssembly.swift index fa19b721..4abd8e72 100644 --- a/Projects/Domain/MassageDomain/Sources/Assembly/MassageDomainAssembly.swift +++ b/Projects/Domain/MassageDomain/Sources/Assembly/MassageDomainAssembly.swift @@ -26,5 +26,9 @@ public final class MassageDomainAssembly: Assembly { container.register(CancelMassageUseCase.self) { resolver in CancelMassageUseCaseImpl(massageRepository: resolver.resolve(MassageRepository.self)!) } + + container.register(FetchMassageRankListUseCase.self) { resolver in + FetchMassageRankListUseCaseImpl(massageRepository: resolver.resolve(MassageRepository.self)!) + } } } diff --git a/Projects/Domain/MassageDomain/Sources/DTO/Response/FetchMassageRankListResponseDTO.swift b/Projects/Domain/MassageDomain/Sources/DTO/Response/FetchMassageRankListResponseDTO.swift new file mode 100644 index 00000000..85e81ce9 --- /dev/null +++ b/Projects/Domain/MassageDomain/Sources/DTO/Response/FetchMassageRankListResponseDTO.swift @@ -0,0 +1,28 @@ +import BaseDomainInterface +import MassageDomainInterface +import Foundation + +struct FetchMassageRankListResponseDTO: Decodable { + let list: [MassageRankResponseDTO] + + struct MassageRankResponseDTO: Decodable { + let id: Int + let rank: Int + let stuNum: String + let memberName: String + let gender: GenderType + } +} + +extension FetchMassageRankListResponseDTO { + func toDomain() -> [MassageRankEntity] { + self.list + .map { $0.toDomain() } + } +} + +extension FetchMassageRankListResponseDTO.MassageRankResponseDTO { + func toDomain() -> MassageRankEntity { + MassageRankEntity(id: id, rank: rank, stuNum: stuNum, memberName: memberName, gender: gender) + } +} diff --git a/Projects/Domain/MassageDomain/Sources/DataSource/Remote/MassageEndpoint.swift b/Projects/Domain/MassageDomain/Sources/DataSource/Remote/MassageEndpoint.swift index 0f6aa081..2a6c5608 100644 --- a/Projects/Domain/MassageDomain/Sources/DataSource/Remote/MassageEndpoint.swift +++ b/Projects/Domain/MassageDomain/Sources/DataSource/Remote/MassageEndpoint.swift @@ -7,6 +7,7 @@ public enum MassageEndpoint { case fetchMassageInfo case applyMassage case cancelMassage + case fetchMassageRankList } extension MassageEndpoint: DotoriEndpoint { @@ -24,6 +25,9 @@ extension MassageEndpoint: DotoriEndpoint { case .cancelMassage: return .delete("") + + case .fetchMassageRankList: + return .get("/rank") } } diff --git a/Projects/Domain/MassageDomain/Sources/DataSource/Remote/RemoteMassageDataSourceImpl.swift b/Projects/Domain/MassageDomain/Sources/DataSource/Remote/RemoteMassageDataSourceImpl.swift index c1c1a4de..f2b7599c 100644 --- a/Projects/Domain/MassageDomain/Sources/DataSource/Remote/RemoteMassageDataSourceImpl.swift +++ b/Projects/Domain/MassageDomain/Sources/DataSource/Remote/RemoteMassageDataSourceImpl.swift @@ -23,4 +23,12 @@ final class RemoteMassageDataSourceImpl: RemoteMassageDataSource { func cancelMassage() async throws { try await networking.request(MassageEndpoint.cancelMassage) } + + func fetchMassageRankList() async throws -> [MassageRankEntity] { + try await networking.request( + MassageEndpoint.fetchMassageRankList, + dto: FetchMassageRankListResponseDTO.self + ) + .toDomain() + } } diff --git a/Projects/Domain/MassageDomain/Sources/Repository/MassageRepositoryImpl.swift b/Projects/Domain/MassageDomain/Sources/Repository/MassageRepositoryImpl.swift index 4c2f86f7..ea5cea3e 100644 --- a/Projects/Domain/MassageDomain/Sources/Repository/MassageRepositoryImpl.swift +++ b/Projects/Domain/MassageDomain/Sources/Repository/MassageRepositoryImpl.swift @@ -18,4 +18,8 @@ final class MassageRepositoryImpl: MassageRepository { func cancelMassage() async throws { try await remoteMassageDataSource.cancelMassage() } + + func fetchMassageRankList() async throws -> [MassageRankEntity] { + try await remoteMassageDataSource.fetchMassageRankList() + } } diff --git a/Projects/Domain/MassageDomain/Sources/UseCase/FetchMassageRankListUseCaseImpl.swift b/Projects/Domain/MassageDomain/Sources/UseCase/FetchMassageRankListUseCaseImpl.swift new file mode 100644 index 00000000..eb88ad0f --- /dev/null +++ b/Projects/Domain/MassageDomain/Sources/UseCase/FetchMassageRankListUseCaseImpl.swift @@ -0,0 +1,13 @@ +import MassageDomainInterface + +struct FetchMassageRankListUseCaseImpl: FetchMassageRankListUseCase { + private let massageRepository: any MassageRepository + + init(massageRepository: any MassageRepository) { + self.massageRepository = massageRepository + } + + func callAsFunction() async throws -> [MassageRankModel] { + try await massageRepository.fetchMassageRankList() + } +} From ad2deb67cf19a3931bc784dd32aa1345b014bcc0 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 13:46:32 +0900 Subject: [PATCH 08/23] :sparkles: :: [#101] MassageDomain / FetchMassageRankListUseCase Spy --- .../DataSource/RemoteMassageDataSourceSpy.swift | 7 +++++++ .../Testing/Repository/MassageRepositorySpy.swift | 7 +++++++ .../UseCase/FetchMassageRankListUseCaseSpy.swift | 10 ++++++++++ 3 files changed, 24 insertions(+) create mode 100644 Projects/Domain/MassageDomain/Testing/UseCase/FetchMassageRankListUseCaseSpy.swift diff --git a/Projects/Domain/MassageDomain/Testing/DataSource/RemoteMassageDataSourceSpy.swift b/Projects/Domain/MassageDomain/Testing/DataSource/RemoteMassageDataSourceSpy.swift index b32fe604..7d8cb06b 100644 --- a/Projects/Domain/MassageDomain/Testing/DataSource/RemoteMassageDataSourceSpy.swift +++ b/Projects/Domain/MassageDomain/Testing/DataSource/RemoteMassageDataSourceSpy.swift @@ -17,4 +17,11 @@ final class RemoteMassageDataSourceSpy: RemoteMassageDataSource { func cancelMassage() async throws { cancelMassageCallCount += 1 } + + var fetchMassageRankListCallCount = 0 + var fetchMassageRankListHandler: () async throws -> [MassageRankEntity] = { [] } + func fetchMassageRankList() async throws -> [MassageRankEntity] { + fetchMassageRankListCallCount += 1 + return try await fetchMassageRankListHandler() + } } diff --git a/Projects/Domain/MassageDomain/Testing/Repository/MassageRepositorySpy.swift b/Projects/Domain/MassageDomain/Testing/Repository/MassageRepositorySpy.swift index ebf674ee..51d1bdc4 100644 --- a/Projects/Domain/MassageDomain/Testing/Repository/MassageRepositorySpy.swift +++ b/Projects/Domain/MassageDomain/Testing/Repository/MassageRepositorySpy.swift @@ -17,4 +17,11 @@ final class MassageRepositorySpy: MassageRepository { func cancelMassage() async throws { cancelMassageCallCount += 1 } + + var fetchMassageRankListCallCount = 0 + var fetchMassageRankListHandler: () async throws -> [MassageRankEntity] = { [] } + func fetchMassageRankList() async throws -> [MassageRankEntity] { + fetchMassageRankListCallCount += 1 + return try await fetchMassageRankListHandler() + } } diff --git a/Projects/Domain/MassageDomain/Testing/UseCase/FetchMassageRankListUseCaseSpy.swift b/Projects/Domain/MassageDomain/Testing/UseCase/FetchMassageRankListUseCaseSpy.swift new file mode 100644 index 00000000..9c660bef --- /dev/null +++ b/Projects/Domain/MassageDomain/Testing/UseCase/FetchMassageRankListUseCaseSpy.swift @@ -0,0 +1,10 @@ +import MassageDomainInterface + +final class FetchMassageRankListUseCaseSpy: FetchMassageRankListUseCase { + var fetchMassageRankListCallCount = 0 + var fetchMassageRankListHandler: () async throws -> [MassageRankEntity] = { [] } + func callAsFunction() async throws -> [MassageRankModel] { + fetchMassageRankListCallCount += 1 + return try await fetchMassageRankListHandler() + } +} From 71982cdafa404caca5d75d6cddbad04a8e6ceb0e Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 13:51:29 +0900 Subject: [PATCH 09/23] :white_check_mark: :: [#101] MassageDomain / FetchMassageRankListUseCaseTests --- .../FetchMassageRankListUseCaseTests.swift | 32 +++++++++++++++++++ .../Tests/MassageRepositoryTests.swift | 20 ++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 Projects/Domain/MassageDomain/Tests/FetchMassageRankListUseCaseTests.swift diff --git a/Projects/Domain/MassageDomain/Tests/FetchMassageRankListUseCaseTests.swift b/Projects/Domain/MassageDomain/Tests/FetchMassageRankListUseCaseTests.swift new file mode 100644 index 00000000..306a49ca --- /dev/null +++ b/Projects/Domain/MassageDomain/Tests/FetchMassageRankListUseCaseTests.swift @@ -0,0 +1,32 @@ +import MassageDomainInterface +import XCTest +@testable import MassageDomain +@testable import MassageDomainTesting + +final class FetchMassageRankListUseCaseTests: XCTestCase { + var massageRepository: MassageRepositorySpy! + var sut: FetchMassageRankListUseCaseImpl! + + override func setUp() { + massageRepository = .init() + sut = .init(massageRepository: massageRepository) + } + + override func tearDown() { + massageRepository = nil + sut = nil + } + + func testFetchMassageInfo() async throws { + XCTAssertEqual(massageRepository.fetchMassageRankListCallCount, 0) + let expected = [ + MassageRankModel(id: 1, rank: 2, stuNum: "1111", memberName: "김시훈", gender: .man) + ] + massageRepository.fetchMassageRankListHandler = { expected } + + let actual = try await sut() + + XCTAssertEqual(massageRepository.fetchMassageRankListCallCount, 1) + XCTAssertEqual(actual, expected) + } +} diff --git a/Projects/Domain/MassageDomain/Tests/MassageRepositoryTests.swift b/Projects/Domain/MassageDomain/Tests/MassageRepositoryTests.swift index f24dfe06..4570ea24 100644 --- a/Projects/Domain/MassageDomain/Tests/MassageRepositoryTests.swift +++ b/Projects/Domain/MassageDomain/Tests/MassageRepositoryTests.swift @@ -34,4 +34,24 @@ final class MassageRepositoryTests: XCTestCase { XCTAssertEqual(remoteMassageDataSource.applyMassageCallCount, 1) } + + func testCancelMassage() async throws { + XCTAssertEqual(remoteMassageDataSource.cancelMassageCallCount, 0) + try await sut.cancelMassage() + + XCTAssertEqual(remoteMassageDataSource.cancelMassageCallCount, 1) + } + + func testFetchMassageRankList() async throws { + XCTAssertEqual(remoteMassageDataSource.fetchMassageRankListCallCount, 0) + let expected = [ + MassageRankModel(id: 1, rank: 2, stuNum: "3218", memberName: "전승원", gender: .man) + ] + remoteMassageDataSource.fetchMassageRankListHandler = { expected } + + let actual = try await sut.fetchMassageRankList() + + XCTAssertEqual(remoteMassageDataSource.fetchMassageRankListCallCount, 1) + XCTAssertEqual(actual, expected) + } } From b9059b3d8ca38f857c0fbe28322a58bd8659c774 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 13:55:08 +0900 Subject: [PATCH 10/23] :sparkles: :: [#101] MassageFeature / FetchMassageRankListUseCase DI --- .../Sources/Assembly/MassageAssembly.swift | 7 +++++-- .../Sources/Factory/MassageFactoryImpl.swift | 11 ++++++++++- .../Sources/Moordinator/MassageMoordinator.swift | 16 ++++++++++++---- .../Sources/Scene/MassageStore.swift | 5 ++++- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift b/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift index 39e15888..879ee7d2 100644 --- a/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift +++ b/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift @@ -1,10 +1,13 @@ +import MassageDomainInterface import Swinject public final class MassageAssembly: Assembly { public init() {} public func assemble(container: Container) { - container.register(MassageFactory.self) { _ in - MassageFactoryImpl() + container.register(MassageFactory.self) { resolver in + MassageFactoryImpl( + fetchMassageRankListUseCase: resolver.resolve(FetchMassageRankListUseCase.self)! + ) } } } diff --git a/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift b/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift index 17e1be07..cbc79b84 100644 --- a/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift +++ b/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift @@ -1,7 +1,16 @@ +import MassageDomainInterface import Moordinator struct MassageFactoryImpl: MassageFactory { + private let fetchMassageRankListUseCase: any FetchMassageRankListUseCase + + init(fetchMassageRankListUseCase: any FetchMassageRankListUseCase) { + self.fetchMassageRankListUseCase = fetchMassageRankListUseCase + } + func makeMoordinator() -> Moordinator { - MassageMoordinator() + let store = MassageStore(fetchMassageRankListUseCase: fetchMassageRankListUseCase) + let viewController = MassageViewController(store: store) + return MassageMoordinator(massageViewController: viewController) } } diff --git a/Projects/Feature/MassageFeature/Sources/Moordinator/MassageMoordinator.swift b/Projects/Feature/MassageFeature/Sources/Moordinator/MassageMoordinator.swift index d8bea064..8e809d68 100644 --- a/Projects/Feature/MassageFeature/Sources/Moordinator/MassageMoordinator.swift +++ b/Projects/Feature/MassageFeature/Sources/Moordinator/MassageMoordinator.swift @@ -5,11 +5,15 @@ import UIKit final class MassageMoordinator: Moordinator { private let rootVC = UINavigationController() - + private let massageViewController: any StoredViewControllable var root: Presentable { rootVC } + init(massageViewController: any StoredViewControllable) { + self.massageViewController = massageViewController + } + func route(to path: RoutePath) -> MoordinatorContributors { guard let path = path.asDotori else { return .none } switch path { @@ -25,8 +29,12 @@ final class MassageMoordinator: Moordinator { private extension MassageMoordinator { func coordinateToMassage() -> MoordinatorContributors { - let noticeWebViewController = DWebViewController(urlString: "https://www.dotori-gsm.com/massage") - self.rootVC.setViewControllers([noticeWebViewController], animated: true) - return .none + self.rootVC.setViewControllers([self.massageViewController], animated: true) + return .one( + .contribute( + withNextPresentable: self.massageViewController, + withNextRouter: self.massageViewController.store + ) + ) } } diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift index 58bdfa7a..9cc63d7d 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift @@ -2,16 +2,19 @@ import BaseFeature import Combine import Moordinator import Store +import MassageDomainInterface final class MassageStore: BaseStore { var route: PassthroughSubject = .init() var subscription: Set = .init() var initialState: State var stateSubject: CurrentValueSubject + private let fetchMassageRankListUseCase: any FetchMassageRankListUseCase - init() { + init(fetchMassageRankListUseCase: any FetchMassageRankListUseCase) { self.initialState = .init() self.stateSubject = .init(initialState) + self.fetchMassageRankListUseCase = fetchMassageRankListUseCase } struct State {} From a524eb42b71d6806b3d72e2cb3a83419ffa61c17 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 14:11:50 +0900 Subject: [PATCH 11/23] =?UTF-8?q?:sparkles:=20::=20[#101]=20MassageFeature?= =?UTF-8?q?=20/=20MassageStore=20massageRank=20=EC=A0=95=EB=B3=B4=20fetch?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Demo/Sources/AppDelegate.swift | 11 ++++- .../Sources/Scene/MassageStore.swift | 33 ++++++++++++-- .../Tests/MassageFeatureTest.swift | 43 +++++++++++++++++-- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift index 6b89b4a7..231fc4a4 100644 --- a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift @@ -1,6 +1,7 @@ import UIKit import Inject @testable import MassageFeature +@testable import MassageDomainTesting @main final class AppDelegate: UIResponder, UIApplicationDelegate { @@ -11,7 +12,15 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) - let store = MassageStore() + let fetchMassageRankListUseCase = FetchMassageRankListUseCaseSpy() + fetchMassageRankListUseCase.fetchMassageRankListHandler = { + [ + .init(id: 1, rank: 1, stuNum: "1234", memberName: "대충이름", gender: .man), + .init(id: 2, rank: 2, stuNum: "1235", memberName: "대이충름", gender: .man), + .init(id: 3, rank: 3, stuNum: "1236", memberName: "이름대충", gender: .woman) + ] + } + let store = MassageStore(fetchMassageRankListUseCase: fetchMassageRankListUseCase) let viewController = Inject.ViewControllerHost( UINavigationController(rootViewController: MassageViewController(store: store)) ) diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift index 9cc63d7d..54fb9da4 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift @@ -17,20 +17,45 @@ final class MassageStore: BaseStore { self.fetchMassageRankListUseCase = fetchMassageRankListUseCase } - struct State {} - enum Action {} - enum Mutation {} + struct State { + var massageRankList: [MassageRankModel] = [] + } + enum Action { + case fetchMassageRankList + } + enum Mutation { + case updateMassageRankList([MassageRankModel]) + } } extension MassageStore { func mutate(state: State, action: Action) -> SideEffect { - .none + switch action { + case .fetchMassageRankList: + return fetchMassageRankList() + } } } extension MassageStore { func reduce(state: State, mutate: Mutation) -> State { var newState = state + switch mutate { + case let .updateMassageRankList(rankList): + newState.massageRankList = rankList + } return newState } } + +private extension MassageStore { + func fetchMassageRankList() -> SideEffect { + return SideEffect<[MassageRankModel], Error> + .tryAsync { [fetchMassageRankListUseCase] in + try await fetchMassageRankListUseCase() + } + .map(Mutation.updateMassageRankList) + .eraseToSideEffect() + .catchToNever() + } +} diff --git a/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift b/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift index 68263acd..5bea724e 100644 --- a/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift +++ b/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift @@ -1,11 +1,46 @@ +import Combine +import MassageDomainInterface import XCTest +@testable import MassageFeature +@testable import MassageDomainTesting final class MassageFeatureTests: XCTestCase { - override func setUpWithError() throws {} + var fetchMassageRankListUseCase: FetchMassageRankListUseCaseSpy! + var sut: MassageStore! + var subscription: Set! - override func tearDownWithError() throws {} + override func setUp() { + fetchMassageRankListUseCase = .init() + sut = .init(fetchMassageRankListUseCase: fetchMassageRankListUseCase) + subscription = [] + } + + override func tearDown() { + fetchMassageRankListUseCase = nil + sut = nil + subscription = nil + } + + func testFetchMassageRankList() { + let expectation = XCTestExpectation(description: "Test_Fetch_Massage_Rank_List") + expectation.expectedFulfillmentCount = 2 + let expectedMassageRankList = [ + MassageRankModel(id: 1, rank: 1, stuNum: "2222", memberName: "익명", gender: .woman) + ] + fetchMassageRankListUseCase.fetchMassageRankListHandler = { expectedMassageRankList } + + sut.state.map(\.massageRankList).sink { _ in + expectation.fulfill() + } + .store(in: &subscription) + + XCTAssertEqual(fetchMassageRankListUseCase.fetchMassageRankListCallCount, 0) + + sut.send(.fetchMassageRankList) + + wait(for: [expectation], timeout: 1.0) - func testExample() { - XCTAssertEqual(1, 1) + XCTAssertEqual(fetchMassageRankListUseCase.fetchMassageRankListCallCount, 1) + XCTAssertEqual(sut.currentState.massageRankList, expectedMassageRankList) } } From d1d16721b2f1f5364a3763c03e633b71dadabdf4 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 14:14:38 +0900 Subject: [PATCH 12/23] :sparkles: :: [#101] MassageFeature / MassageStore LoadCurrentUserRoleUseCase DI --- .../MassageFeature/Demo/Sources/AppDelegate.swift | 7 ++++++- .../Sources/Assembly/MassageAssembly.swift | 4 +++- .../Sources/Factory/MassageFactoryImpl.swift | 13 +++++++++++-- .../MassageFeature/Sources/Scene/MassageStore.swift | 10 ++++++++-- .../MassageFeature/Tests/MassageFeatureTest.swift | 9 ++++++++- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift index 231fc4a4..452fd8d6 100644 --- a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift @@ -2,6 +2,7 @@ import UIKit import Inject @testable import MassageFeature @testable import MassageDomainTesting +@testable import UserDomainTesting @main final class AppDelegate: UIResponder, UIApplicationDelegate { @@ -20,7 +21,11 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { .init(id: 3, rank: 3, stuNum: "1236", memberName: "이름대충", gender: .woman) ] } - let store = MassageStore(fetchMassageRankListUseCase: fetchMassageRankListUseCase) + let loadCurrentUserRoleUseCase = LoadCurrentUserRoleUseCaseSpy() + let store = MassageStore( + fetchMassageRankListUseCase: fetchMassageRankListUseCase, + loadCurrentUserRoleUseCase: loadCurrentUserRoleUseCase + ) let viewController = Inject.ViewControllerHost( UINavigationController(rootViewController: MassageViewController(store: store)) ) diff --git a/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift b/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift index 879ee7d2..1a11bfe6 100644 --- a/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift +++ b/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift @@ -1,12 +1,14 @@ import MassageDomainInterface import Swinject +import UserDomainInterface public final class MassageAssembly: Assembly { public init() {} public func assemble(container: Container) { container.register(MassageFactory.self) { resolver in MassageFactoryImpl( - fetchMassageRankListUseCase: resolver.resolve(FetchMassageRankListUseCase.self)! + fetchMassageRankListUseCase: resolver.resolve(FetchMassageRankListUseCase.self)!, + loadCurrentUserRoleUseCase: resolver.resolve(LoadCurrentUserRoleUseCase.self)! ) } } diff --git a/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift b/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift index cbc79b84..c187081e 100644 --- a/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift +++ b/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift @@ -1,15 +1,24 @@ import MassageDomainInterface import Moordinator +import UserDomainInterface struct MassageFactoryImpl: MassageFactory { private let fetchMassageRankListUseCase: any FetchMassageRankListUseCase + private let loadCurrentUserRoleUseCase: any LoadCurrentUserRoleUseCase - init(fetchMassageRankListUseCase: any FetchMassageRankListUseCase) { + init( + fetchMassageRankListUseCase: any FetchMassageRankListUseCase, + loadCurrentUserRoleUseCase: any LoadCurrentUserRoleUseCase + ) { self.fetchMassageRankListUseCase = fetchMassageRankListUseCase + self.loadCurrentUserRoleUseCase = loadCurrentUserRoleUseCase } func makeMoordinator() -> Moordinator { - let store = MassageStore(fetchMassageRankListUseCase: fetchMassageRankListUseCase) + let store = MassageStore( + fetchMassageRankListUseCase: fetchMassageRankListUseCase, + loadCurrentUserRoleUseCase: <#T##LoadCurrentUserRoleUseCase#> + ) let viewController = MassageViewController(store: store) return MassageMoordinator(massageViewController: viewController) } diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift index 54fb9da4..d7ae9be6 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift @@ -1,8 +1,9 @@ import BaseFeature import Combine +import MassageDomainInterface import Moordinator import Store -import MassageDomainInterface +import UserDomainInterface final class MassageStore: BaseStore { var route: PassthroughSubject = .init() @@ -10,11 +11,16 @@ final class MassageStore: BaseStore { var initialState: State var stateSubject: CurrentValueSubject private let fetchMassageRankListUseCase: any FetchMassageRankListUseCase + private let loadCurrentUserRoleUseCase: any LoadCurrentUserRoleUseCase - init(fetchMassageRankListUseCase: any FetchMassageRankListUseCase) { + init( + fetchMassageRankListUseCase: any FetchMassageRankListUseCase, + loadCurrentUserRoleUseCase: any LoadCurrentUserRoleUseCase + ) { self.initialState = .init() self.stateSubject = .init(initialState) self.fetchMassageRankListUseCase = fetchMassageRankListUseCase + self.loadCurrentUserRoleUseCase = loadCurrentUserRoleUseCase } struct State { diff --git a/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift b/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift index 5bea724e..0a1490ec 100644 --- a/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift +++ b/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift @@ -3,20 +3,27 @@ import MassageDomainInterface import XCTest @testable import MassageFeature @testable import MassageDomainTesting +@testable import UserDomainTesting final class MassageFeatureTests: XCTestCase { var fetchMassageRankListUseCase: FetchMassageRankListUseCaseSpy! + var loadCurrentUserRoleUseCase: LoadCurrentUserRoleUseCaseSpy! var sut: MassageStore! var subscription: Set! override func setUp() { fetchMassageRankListUseCase = .init() - sut = .init(fetchMassageRankListUseCase: fetchMassageRankListUseCase) + loadCurrentUserRoleUseCase = .init() + sut = .init( + fetchMassageRankListUseCase: fetchMassageRankListUseCase, + loadCurrentUserRoleUseCase: loadCurrentUserRoleUseCase + ) subscription = [] } override func tearDown() { fetchMassageRankListUseCase = nil + loadCurrentUserRoleUseCase = nil sut = nil subscription = nil } From 26c72387c8d508536c4139dd9cfc3a762e8b3d7f Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 14:32:20 +0900 Subject: [PATCH 13/23] :sparkles: :: [#101] MassageFeature / MassageStore - viewDidLoad action --- .../Sources/Scene/MassageStore.swift | 49 ++++++++++++++++++- .../Sources/Scene/SelfStudyStore.swift | 5 +- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift index d7ae9be6..e573a359 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift @@ -1,3 +1,4 @@ +import BaseDomainInterface import BaseFeature import Combine import MassageDomainInterface @@ -25,21 +26,30 @@ final class MassageStore: BaseStore { struct State { var massageRankList: [MassageRankModel] = [] + var currentUserRole = UserRoleType.member + var isRefreshing = false } enum Action { + case viewDidLoad case fetchMassageRankList } enum Mutation { case updateMassageRankList([MassageRankModel]) + case updateCurrentUserRole(UserRoleType) + case updateIsRefreshing(Bool) } } extension MassageStore { func mutate(state: State, action: Action) -> SideEffect { switch action { + case .viewDidLoad: + return viewDidLoad() + case .fetchMassageRankList: return fetchMassageRankList() } + return .none } } @@ -49,19 +59,56 @@ extension MassageStore { switch mutate { case let .updateMassageRankList(rankList): newState.massageRankList = rankList + + case let .updateCurrentUserRole(userRole): + newState.currentUserRole = userRole + + case let .updateIsRefreshing(isRefreshing): + newState.isRefreshing = isRefreshing } return newState } } +// MARK: - Mutate private extension MassageStore { + func viewDidLoad() -> SideEffect { + let userRoleEffect = SideEffect + .just(try? loadCurrentUserRoleUseCase()) + .replaceNil(with: .member) + .setFailureType(to: Never.self) + .map(Mutation.updateCurrentUserRole) + .eraseToSideEffect() + return .merge( + userRoleEffect, + fetchMassageRankList() + ) + } + func fetchMassageRankList() -> SideEffect { - return SideEffect<[MassageRankModel], Error> + let massageRankListEffect = SideEffect<[MassageRankModel], Error> .tryAsync { [fetchMassageRankListUseCase] in try await fetchMassageRankListUseCase() } .map(Mutation.updateMassageRankList) .eraseToSideEffect() .catchToNever() + return self.makeRefreshingSideEffect(massageRankListEffect) + } +} + +// MARK: - Reusable +private extension MassageStore { + func makeRefreshingSideEffect( + _ publisher: SideEffect + ) -> SideEffect { + let startLoadingPublisher = SideEffect + .just(Mutation.updateIsRefreshing(true)) + let endLoadingPublisher = SideEffect + .just(Mutation.updateIsRefreshing(false)) + return startLoadingPublisher + .append(publisher) + .append(endLoadingPublisher) + .eraseToSideEffect() } } diff --git a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyStore.swift b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyStore.swift index 7e1d10f5..9709360e 100644 --- a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyStore.swift +++ b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyStore.swift @@ -57,7 +57,7 @@ extension SelfStudyStore { return .none } } -import Foundation + extension SelfStudyStore { func reduce(state: State, mutate: Mutation) -> State { var newState = state @@ -81,6 +81,7 @@ extension SelfStudyStore: SelfStudyCellDelegate { } } +// MARK: - Mutate private extension SelfStudyStore { func fetchSelfStudyRank() -> SideEffect { let selfStudyEffect = SideEffect<[SelfStudyRankModel], Error> @@ -114,6 +115,7 @@ private extension SelfStudyStore { } } +// MARK: - Reduce private extension SelfStudyStore { func updateSelfStudyCheck(id: Int, isChecked: Bool) -> [SelfStudyRankModel] { currentState.selfStudyRankList.map { @@ -130,6 +132,7 @@ private extension SelfStudyStore { } } +// MARK: - Reusable private extension SelfStudyStore { func makeRefreshingSideEffect( _ publisher: SideEffect From bb7a7b0be96c262898acfd5291338721d27dff3f Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 14:35:25 +0900 Subject: [PATCH 14/23] :sparkles: :: [#101] MassageFeature / MassageCell Databinding adapt --- .../Sources/Scene/MassageViewController.swift | 12 ++---- .../Sources/Scene/View/MassageCell.swift | 43 +++++-------------- 2 files changed, 14 insertions(+), 41 deletions(-) diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift index f79b7a24..5d50f553 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift @@ -2,6 +2,7 @@ import BaseFeature import DesignSystem import Localization import MSGLayout +import MassageDomainInterface import UIKit import UIKitUtil @@ -15,13 +16,12 @@ final class MassageViewController: BaseStoredViewController { $0.register(cellType: MassageCell.self) } private let massageRefreshContorol = UIRefreshControl() - private lazy var massageTableAdapter = TableViewAdapter>( + private lazy var massageTableAdapter = TableViewAdapter>( tableView: massageTableView ) { [store] tableView, indexPath, item in let cell: MassageCell = tableView.dequeueReusableCell(for: indexPath) cell.adapt(model: item) -// cell.setUserRole(userRole: store.currentState.currentUserRole) -// cell.delegate = store + cell.setUserRole(userRole: store.currentState.currentUserRole) return cell } @@ -44,10 +44,4 @@ final class MassageViewController: BaseStoredViewController { override func configureNavigation() { self.navigationItem.setLeftBarButton(massageNavigationBarLabel, animated: true) } - - override func bindState() { - massageTableAdapter.updateSections(sections: [ - .init(items: [(), ()]) - ]) - } } diff --git a/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift b/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift index f276d703..2774b167 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift @@ -6,18 +6,12 @@ import MSGLayout import MassageDomainInterface import UIKit -protocol MassageCellDelegate: AnyObject { - func massageCheckBoxDidTap(id: Int, isChecked: Bool) -} - -final class MassageCell: BaseTableViewCell { - weak var delegate: (any MassageCellDelegate)? +final class MassageCell: BaseTableViewCell { private let appliedStudentCardView = AppliedStudentCardView() private var subscription = Set() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) - bindAction() } required init?(coder: NSCoder) { @@ -48,35 +42,20 @@ final class MassageCell: BaseTableViewCell { self.selectionStyle = .none } - override func adapt(model: Void) { + override func adapt(model: MassageRankModel) { super.adapt(model: model) -// self.appliedStudentCardView.updateContent( -// with: .init( -// rank: model.rank, -// name: model.memberName, -// gender: model.gender == .man ? .man : .woman, -// stuNum: model.stuNum, -// isChecked: model.selfStudyCheck -// ) -// ) + self.appliedStudentCardView.updateContent( + with: .init( + rank: model.rank, + name: model.memberName, + gender: model.gender == .man ? .man : .woman, + stuNum: model.stuNum, + isChecked: false + ) + ) } func setUserRole(userRole: UserRoleType) { appliedStudentCardView.setIsHiddenAttendanceCheckBox(userRole == .member) } } - -private extension MassageCell { - func bindAction() { -// appliedStudentCardView.attendanceCheckBoxPublisher -// .throttle(for: 1, scheduler: RunLoop.main, latest: true) -// .compactMap { [weak self] (checked) -> (Int, Bool)? in -// guard let id = self?.model?.id else { return nil } -// return (id, checked) -// } -// .sink(with: self, receiveValue: { owner, checked in -// owner.delegate?.selfStudyCheckBoxDidTap(id: checked.0, isChecked: checked.1) -// }) -// .store(in: &subscription) - } -} From 00a3dab2296e562909ba0455cf53da50e2be5857 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 14:48:46 +0900 Subject: [PATCH 15/23] :sparkles: :: [#101] MassageFeature / MassageViewController, MassageStore Data Binding --- .../Demo/Sources/AppDelegate.swift | 4 +- .../Sources/Assembly/MassageAssembly.swift | 3 +- .../Sources/Factory/MassageFactoryImpl.swift | 8 +--- .../Sources/Scene/MassageStore.swift | 21 +--------- .../Sources/Scene/MassageViewController.swift | 38 +++++++++++++++++-- .../Sources/Scene/View/MassageCell.swift | 18 --------- .../Tests/MassageFeatureTest.swift | 6 +-- 7 files changed, 42 insertions(+), 56 deletions(-) diff --git a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift index 452fd8d6..9286fd66 100644 --- a/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Feature/MassageFeature/Demo/Sources/AppDelegate.swift @@ -21,10 +21,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { .init(id: 3, rank: 3, stuNum: "1236", memberName: "이름대충", gender: .woman) ] } - let loadCurrentUserRoleUseCase = LoadCurrentUserRoleUseCaseSpy() let store = MassageStore( - fetchMassageRankListUseCase: fetchMassageRankListUseCase, - loadCurrentUserRoleUseCase: loadCurrentUserRoleUseCase + fetchMassageRankListUseCase: fetchMassageRankListUseCase ) let viewController = Inject.ViewControllerHost( UINavigationController(rootViewController: MassageViewController(store: store)) diff --git a/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift b/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift index 1a11bfe6..7fcc24da 100644 --- a/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift +++ b/Projects/Feature/MassageFeature/Sources/Assembly/MassageAssembly.swift @@ -7,8 +7,7 @@ public final class MassageAssembly: Assembly { public func assemble(container: Container) { container.register(MassageFactory.self) { resolver in MassageFactoryImpl( - fetchMassageRankListUseCase: resolver.resolve(FetchMassageRankListUseCase.self)!, - loadCurrentUserRoleUseCase: resolver.resolve(LoadCurrentUserRoleUseCase.self)! + fetchMassageRankListUseCase: resolver.resolve(FetchMassageRankListUseCase.self)! ) } } diff --git a/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift b/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift index c187081e..2e8488ef 100644 --- a/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift +++ b/Projects/Feature/MassageFeature/Sources/Factory/MassageFactoryImpl.swift @@ -4,20 +4,16 @@ import UserDomainInterface struct MassageFactoryImpl: MassageFactory { private let fetchMassageRankListUseCase: any FetchMassageRankListUseCase - private let loadCurrentUserRoleUseCase: any LoadCurrentUserRoleUseCase init( - fetchMassageRankListUseCase: any FetchMassageRankListUseCase, - loadCurrentUserRoleUseCase: any LoadCurrentUserRoleUseCase + fetchMassageRankListUseCase: any FetchMassageRankListUseCase ) { self.fetchMassageRankListUseCase = fetchMassageRankListUseCase - self.loadCurrentUserRoleUseCase = loadCurrentUserRoleUseCase } func makeMoordinator() -> Moordinator { let store = MassageStore( - fetchMassageRankListUseCase: fetchMassageRankListUseCase, - loadCurrentUserRoleUseCase: <#T##LoadCurrentUserRoleUseCase#> + fetchMassageRankListUseCase: fetchMassageRankListUseCase ) let viewController = MassageViewController(store: store) return MassageMoordinator(massageViewController: viewController) diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift index e573a359..e54c349a 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageStore.swift @@ -12,21 +12,17 @@ final class MassageStore: BaseStore { var initialState: State var stateSubject: CurrentValueSubject private let fetchMassageRankListUseCase: any FetchMassageRankListUseCase - private let loadCurrentUserRoleUseCase: any LoadCurrentUserRoleUseCase init( - fetchMassageRankListUseCase: any FetchMassageRankListUseCase, - loadCurrentUserRoleUseCase: any LoadCurrentUserRoleUseCase + fetchMassageRankListUseCase: any FetchMassageRankListUseCase ) { self.initialState = .init() self.stateSubject = .init(initialState) self.fetchMassageRankListUseCase = fetchMassageRankListUseCase - self.loadCurrentUserRoleUseCase = loadCurrentUserRoleUseCase } struct State { var massageRankList: [MassageRankModel] = [] - var currentUserRole = UserRoleType.member var isRefreshing = false } enum Action { @@ -35,7 +31,6 @@ final class MassageStore: BaseStore { } enum Mutation { case updateMassageRankList([MassageRankModel]) - case updateCurrentUserRole(UserRoleType) case updateIsRefreshing(Bool) } } @@ -60,9 +55,6 @@ extension MassageStore { case let .updateMassageRankList(rankList): newState.massageRankList = rankList - case let .updateCurrentUserRole(userRole): - newState.currentUserRole = userRole - case let .updateIsRefreshing(isRefreshing): newState.isRefreshing = isRefreshing } @@ -73,16 +65,7 @@ extension MassageStore { // MARK: - Mutate private extension MassageStore { func viewDidLoad() -> SideEffect { - let userRoleEffect = SideEffect - .just(try? loadCurrentUserRoleUseCase()) - .replaceNil(with: .member) - .setFailureType(to: Never.self) - .map(Mutation.updateCurrentUserRole) - .eraseToSideEffect() - return .merge( - userRoleEffect, - fetchMassageRankList() - ) + return self.fetchMassageRankList() } func fetchMassageRankList() -> SideEffect { diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift index 5d50f553..25ad425a 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift @@ -1,4 +1,5 @@ import BaseFeature +import CombineUtility import DesignSystem import Localization import MSGLayout @@ -11,17 +12,16 @@ final class MassageViewController: BaseStoredViewController { private let massageTableView = UITableView() .set(\.backgroundColor, .clear) .set(\.separatorStyle, .none) - .set(\.isHidden, true) + .set(\.sectionHeaderHeight, 0) .then { $0.register(cellType: MassageCell.self) } private let massageRefreshContorol = UIRefreshControl() private lazy var massageTableAdapter = TableViewAdapter>( tableView: massageTableView - ) { [store] tableView, indexPath, item in + ) { tableView, indexPath, item in let cell: MassageCell = tableView.dequeueReusableCell(for: indexPath) cell.adapt(model: item) - cell.setUserRole(userRole: store.currentState.currentUserRole) return cell } @@ -44,4 +44,36 @@ final class MassageViewController: BaseStoredViewController { override func configureNavigation() { self.navigationItem.setLeftBarButton(massageNavigationBarLabel, animated: true) } + + override func bindAction() { + viewDidLoadPublisher + .map { Store.Action.viewDidLoad } + .sink(receiveValue: store.send(_:)) + .store(in: &subscription) + + massageRefreshContorol.controlPublisher(for: .valueChanged) + .map { _ in Store.Action.fetchMassageRankList } + .sink(receiveValue: store.send(_:)) + .store(in: &subscription) + } + + override func bindState() { + let sharedState = store.state.share() + .receive(on: DispatchQueue.main) + + sharedState + .map(\.massageRankList) + .map { [GenericSectionModel(items: $0)] } + .sink(receiveValue: massageTableAdapter.updateSections(sections:)) + .store(in: &subscription) + + sharedState + .map(\.isRefreshing) + .removeDuplicates() + .dropFirst(2) + .sink(with: massageRefreshContorol, receiveValue: { refreshControl, isRefreshing in + isRefreshing ? refreshControl.beginRefreshing() : refreshControl.endRefreshing() + }) + .store(in: &subscription) + } } diff --git a/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift b/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift index 2774b167..106b19e4 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/View/MassageCell.swift @@ -8,20 +8,6 @@ import UIKit final class MassageCell: BaseTableViewCell { private let appliedStudentCardView = AppliedStudentCardView() - private var subscription = Set() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func prepareForReuse() { - super.prepareForReuse() - self.subscription.removeAll() - } override func addView() { contentView.addSubviews { @@ -54,8 +40,4 @@ final class MassageCell: BaseTableViewCell { ) ) } - - func setUserRole(userRole: UserRoleType) { - appliedStudentCardView.setIsHiddenAttendanceCheckBox(userRole == .member) - } } diff --git a/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift b/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift index 0a1490ec..81a5af8b 100644 --- a/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift +++ b/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift @@ -7,23 +7,19 @@ import XCTest final class MassageFeatureTests: XCTestCase { var fetchMassageRankListUseCase: FetchMassageRankListUseCaseSpy! - var loadCurrentUserRoleUseCase: LoadCurrentUserRoleUseCaseSpy! var sut: MassageStore! var subscription: Set! override func setUp() { fetchMassageRankListUseCase = .init() - loadCurrentUserRoleUseCase = .init() sut = .init( - fetchMassageRankListUseCase: fetchMassageRankListUseCase, - loadCurrentUserRoleUseCase: loadCurrentUserRoleUseCase + fetchMassageRankListUseCase: fetchMassageRankListUseCase ) subscription = [] } override func tearDown() { fetchMassageRankListUseCase = nil - loadCurrentUserRoleUseCase = nil sut = nil subscription = nil } From 25c300ba35fd2b346a5feaff43f63bc4d23231b3 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 15:03:58 +0900 Subject: [PATCH 16/23] =?UTF-8?q?:sparkles:=20::=20[#101]=20MassageFeature?= =?UTF-8?q?=20/=20Massage=EC=8B=A0=EC=B2=AD=ED=95=9C=20=EC=82=AC=EB=9E=8C?= =?UTF-8?q?=EC=9D=B4=20=EC=97=86=EC=9D=84=20=EB=95=8C=20UI=20=EB=B0=8F=20B?= =?UTF-8?q?inding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/MassageViewController.swift | 25 +++++++++++++++++++ .../Scene/SelfStudyViewController.swift | 3 +-- .../Coffee.imageset/Coffee-dark.svg | 5 ++++ .../Coffee.imageset/Coffee.svg | 5 ++++ .../Coffee.imageset/Contents.json | 22 ++++++++++++++++ .../Sources/Icons/DotoriIcon.swift | 1 + .../Resources/en.lproj/Massage.strings | 2 ++ .../Resources/ko.lproj/Massage.strings | 2 ++ 8 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Coffee-dark.svg create mode 100644 Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Coffee.svg create mode 100644 Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Contents.json diff --git a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift index 25ad425a..e9d9a532 100644 --- a/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift +++ b/Projects/Feature/MassageFeature/Sources/Scene/MassageViewController.swift @@ -11,6 +11,7 @@ final class MassageViewController: BaseStoredViewController { private let massageNavigationBarLabel = DotoriNavigationBarLabel(text: L10n.Massage.massageTitle) private let massageTableView = UITableView() .set(\.backgroundColor, .clear) + .set(\.isHidden, true) .set(\.separatorStyle, .none) .set(\.sectionHeaderHeight, 0) .then { @@ -24,10 +25,21 @@ final class MassageViewController: BaseStoredViewController { cell.adapt(model: item) return cell } + private let emptySelfStudyStackView = VStackView(spacing: 8) { + DotoriIconView( + size: .custom(.init(width: 73.5, height: 120)), + image: .Dotori.coffee + ) + + DotoriLabel(L10n.Massage.emptyMassageTitle, font: .subtitle2) + + DotoriLabel(L10n.Massage.applyFromHomeMassageTitle, textColor: .neutral(.n20), font: .caption) + }.alignment(.center) override func addView() { view.addSubviews { massageTableView + emptySelfStudyStackView } massageTableView.refreshControl = massageRefreshContorol } @@ -38,6 +50,9 @@ final class MassageViewController: BaseStoredViewController { .top(.toSuperview(), .equal(24)) .horizontal(.toSuperview()) .bottom(.to(view.safeAreaLayoutGuide)) + + emptySelfStudyStackView.layout + .center(.toSuperview()) } } @@ -75,5 +90,15 @@ final class MassageViewController: BaseStoredViewController { isRefreshing ? refreshControl.beginRefreshing() : refreshControl.endRefreshing() }) .store(in: &subscription) + + sharedState + .map(\.massageRankList) + .map(\.isEmpty) + .removeDuplicates() + .sink(with: self, receiveValue: { owner, massageIsEmpty in + owner.massageTableView.isHidden = massageIsEmpty + owner.emptySelfStudyStackView.isHidden = !massageIsEmpty + }) + .store(in: &subscription) } } diff --git a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift index 9f458384..f94708f3 100644 --- a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift +++ b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift @@ -91,8 +91,7 @@ final class SelfStudyViewController: BaseStoredViewController { sharedState .map(\.selfStudyRankList) - .map(\.count) - .map { $0 == 0 } + .map(\.isEmpty) .removeDuplicates() .sink(with: self, receiveValue: { owner, selfStudyIsEmpty in owner.selfStudyTableView.isHidden = selfStudyIsEmpty diff --git a/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Coffee-dark.svg b/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Coffee-dark.svg new file mode 100644 index 00000000..c6a0ab17 --- /dev/null +++ b/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Coffee-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Coffee.svg b/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Coffee.svg new file mode 100644 index 00000000..1cd33e60 --- /dev/null +++ b/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Coffee.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Contents.json b/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Contents.json new file mode 100644 index 00000000..fa199fef --- /dev/null +++ b/Projects/UserInterface/DesignSystem/Resources/DotoriIcon/DotoriIcon.xcassets/Coffee.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "Coffee.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Coffee-dark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Projects/UserInterface/DesignSystem/Sources/Icons/DotoriIcon.swift b/Projects/UserInterface/DesignSystem/Sources/Icons/DotoriIcon.swift index 97a2456d..873597f9 100644 --- a/Projects/UserInterface/DesignSystem/Sources/Icons/DotoriIcon.swift +++ b/Projects/UserInterface/DesignSystem/Sources/Icons/DotoriIcon.swift @@ -8,6 +8,7 @@ public extension UIImage { public static let chevronDown = DesignSystemAsset.DotoriIcon.chevronDown.image public static let chevronLeft = DesignSystemAsset.DotoriIcon.chevronLeft.image public static let chevronRight = DesignSystemAsset.DotoriIcon.chevronRight.image + public static let coffee = DesignSystemAsset.DotoriIcon.coffee.image public static let dotori = DesignSystemAsset.DotoriIcon.dotori.image public static let dotoriSigninLogo = DesignSystemAsset.DotoriIcon.dotoriSigninLogo.image public static let dotoriHomeLogo = DesignSystemAsset.DotoriIcon.dotoriHomeLogo.image diff --git a/Projects/UserInterface/Localization/Resources/en.lproj/Massage.strings b/Projects/UserInterface/Localization/Resources/en.lproj/Massage.strings index 5944d7c7..73894a87 100644 --- a/Projects/UserInterface/Localization/Resources/en.lproj/Massage.strings +++ b/Projects/UserInterface/Localization/Resources/en.lproj/Massage.strings @@ -7,3 +7,5 @@ */ "massage_title" = "Massage"; +"empty_massage_title" = "No one has applied for the Massage.."; +"apply_from_home_massage_title" = "Apply for the Massage from Home!"; diff --git a/Projects/UserInterface/Localization/Resources/ko.lproj/Massage.strings b/Projects/UserInterface/Localization/Resources/ko.lproj/Massage.strings index fac40e31..c7c1ee42 100644 --- a/Projects/UserInterface/Localization/Resources/ko.lproj/Massage.strings +++ b/Projects/UserInterface/Localization/Resources/ko.lproj/Massage.strings @@ -7,3 +7,5 @@ */ "massage_title" = "안마의자"; +"empty_massage_title" = "안마의자를 신청한 인원이 없습니다.."; +"apply_from_home_massage_title" = "홈에서 안마의자 신청을 해보세요!"; From 8d2e3d8831d9e931af120cb7e8c092463a335e69 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 15:06:34 +0900 Subject: [PATCH 17/23] =?UTF-8?q?:dizzy:=20::=20[#101]=20NoticeFeature=20/?= =?UTF-8?q?=20=EC=B2=AB=EB=B2=88=EC=A7=B8=EB=A1=9C=20=EA=B3=B5=EC=A7=80?= =?UTF-8?q?=EB=93=A4=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=AC=EB=95=8C?= =?UTF-8?q?=EB=8A=94=20refresh=EA=B0=80=20=EB=8F=99=EC=9E=91=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NoticeFeature/Sources/Scene/NoticeViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Projects/Feature/NoticeFeature/Sources/Scene/NoticeViewController.swift b/Projects/Feature/NoticeFeature/Sources/Scene/NoticeViewController.swift index 65fa177c..de9186b9 100644 --- a/Projects/Feature/NoticeFeature/Sources/Scene/NoticeViewController.swift +++ b/Projects/Feature/NoticeFeature/Sources/Scene/NoticeViewController.swift @@ -164,9 +164,11 @@ final class NoticeViewController: BaseStoredViewController { sharedState .map(\.isRefreshing) - .sink(with: noticeRefreshControl) { refreshControl, isRefreshing in + .removeDuplicates() + .dropFirst(2) + .sink(with: noticeRefreshControl, receiveValue: { refreshControl, isRefreshing in isRefreshing ? refreshControl.beginRefreshing() : refreshControl.endRefreshing() - } + }) .store(in: &subscription) } } From cbecb6e61a56f65466006753ada949439dae5d7b Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 15:07:09 +0900 Subject: [PATCH 18/23] =?UTF-8?q?:sparkles:=20::=20[#101]=20SelfStudyFeatu?= =?UTF-8?q?re=20/=20=EC=B2=AB=EB=B2=88=EC=A7=B8=EB=A1=9C=20=EC=9E=90?= =?UTF-8?q?=EC=8A=B5=EC=8B=A0=EC=B2=AD=ED=95=9C=20=EC=82=AC=EB=9E=8C?= =?UTF-8?q?=EB=93=A4=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=AC=EB=95=8C?= =?UTF-8?q?=EB=8A=94=20RefreshControl=EC=9D=B4=20=EC=95=88=EB=B3=B4?= =?UTF-8?q?=EC=9D=B4=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Scene/SelfStudyViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift index f94708f3..4dda896c 100644 --- a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift +++ b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift @@ -101,6 +101,8 @@ final class SelfStudyViewController: BaseStoredViewController { sharedState .map(\.isRefreshing) + .removeDuplicates() + .dropFirst(2) .sink(with: selfStudyRefreshContorol, receiveValue: { refreshControl, isRefreshing in isRefreshing ? refreshControl.beginRefreshing() : refreshControl.endRefreshing() }) From ea0ee865882297d89844e359059ad210d9048615 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 15:13:18 +0900 Subject: [PATCH 19/23] =?UTF-8?q?:white=5Fcheck=5Fmark:=20::=20[#101]=20Ma?= =?UTF-8?q?ssageFeature=20/=20=ED=95=B4=EB=8B=B9=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EB=A7=8C=20sink=ED=95=98=EB=8F=84=EB=A1=9D=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 --- Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift b/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift index 81a5af8b..97e288e5 100644 --- a/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift +++ b/Projects/Feature/MassageFeature/Tests/MassageFeatureTest.swift @@ -32,7 +32,7 @@ final class MassageFeatureTests: XCTestCase { ] fetchMassageRankListUseCase.fetchMassageRankListHandler = { expectedMassageRankList } - sut.state.map(\.massageRankList).sink { _ in + sut.state.map(\.massageRankList).removeDuplicates().sink { _ in expectation.fulfill() } .store(in: &subscription) From 0c4489a4371294217261ab8dde1ae5b8f0c54ec5 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 15:31:10 +0900 Subject: [PATCH 20/23] :heavy_plus_sign: :: [#101] SelfStudyDomain / BaseDomainInterface -> SelfStudyDomainInterface --- Projects/Domain/SelfStudyDomain/Project.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Projects/Domain/SelfStudyDomain/Project.swift b/Projects/Domain/SelfStudyDomain/Project.swift index a82d5572..b817463c 100644 --- a/Projects/Domain/SelfStudyDomain/Project.swift +++ b/Projects/Domain/SelfStudyDomain/Project.swift @@ -5,7 +5,9 @@ import DependencyPlugin let project = Project.module( name: ModulePaths.Domain.SelfStudyDomain.rawValue, targets: [ - .interface(module: .domain(.SelfStudyDomain)), + .interface(module: .domain(.SelfStudyDomain), dependencies: [ + .domain(target: .BaseDomain, type: .interface) + ]), .implements(module: .domain(.SelfStudyDomain), dependencies: [ .domain(target: .SelfStudyDomain, type: .interface), .domain(target: .BaseDomain) From f44ecde4b655097d4417c50a2bdc065a72ae08c1 Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 15:35:47 +0900 Subject: [PATCH 21/23] :heavy_plus_sign: :: [#101] MassageDomain / BaseDomainInterface -> MassageDomainInterface --- Projects/Domain/MassageDomain/Project.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Projects/Domain/MassageDomain/Project.swift b/Projects/Domain/MassageDomain/Project.swift index db84dc3d..e5c4132c 100644 --- a/Projects/Domain/MassageDomain/Project.swift +++ b/Projects/Domain/MassageDomain/Project.swift @@ -5,7 +5,9 @@ import DependencyPlugin let project = Project.module( name: ModulePaths.Domain.MassageDomain.rawValue, targets: [ - .interface(module: .domain(.MassageDomain)), + .interface(module: .domain(.MassageDomain), dependencies: [ + .domain(target: .BaseDomain, type: .interface) + ]), .implements(module: .domain(.MassageDomain), dependencies: [ .domain(target: .MassageDomain, type: .interface), .domain(target: .BaseDomain) From 46fe4d26368843ac95bcc5fa0cfc9aeb9951e4ca Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 15:42:38 +0900 Subject: [PATCH 22/23] :truck: :: [#101] SelfStudyFeature / dotoriNavigationBarLabel -> selfStudyNavigationBarLabel --- .../Sources/Scene/SelfStudyViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift index d964bf4a..b5b5c818 100644 --- a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift +++ b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift @@ -11,7 +11,7 @@ import UIKit import UIKitUtil final class SelfStudyViewController: BaseStoredViewController { - private let dotoriNavigationBarLabel = DotoriNavigationBarLabel(text: L10n.SelfStudy.selfStudyTitle) + private let selfStudyNavigationBarLabel = DotoriNavigationBarLabel(text: L10n.SelfStudy.selfStudyTitle) private let filterBarButton = UIBarButtonItem( image: .Dotori.filter.tintColor(color: .dotori(.neutral(.n20))), style: .done, From 5eb3ce42fd15005d0ed1653d32a2dba3be92605d Mon Sep 17 00:00:00 2001 From: baegteun Date: Wed, 26 Jul 2023 15:47:46 +0900 Subject: [PATCH 23/23] =?UTF-8?q?:pencil2:=20::=20[#101]=20SelfStudyFeatur?= =?UTF-8?q?e=20/=20=EB=B3=80=EC=88=98=EB=AA=85=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20=EC=95=88=ED=95=9C=EA=B2=83=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 --- .../Sources/Scene/SelfStudyViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift index b5b5c818..7585b7a4 100644 --- a/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift +++ b/Projects/Feature/SelfStudyFeature/Sources/Scene/SelfStudyViewController.swift @@ -69,7 +69,7 @@ final class SelfStudyViewController: BaseStoredViewController { } override func configureNavigation() { - self.navigationItem.setLeftBarButton(dotoriNavigationBarLabel, animated: true) + self.navigationItem.setLeftBarButton(selfStudyNavigationBarLabel, animated: true) self.navigationItem.setRightBarButton(filterBarButton, animated: true) }