From d521d0ebd06f978f60bc335ce3969b0fb69271ad Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 01:41:33 +0900 Subject: [PATCH 01/47] =?UTF-8?q?[iOS]=20refactor:=20clickedButton=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailAchievementViewController.swift | 6 +++--- .../ManageCategory/ManageCategoryViewController.swift | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/DetailAchievement/DetailAchievementViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/DetailAchievement/DetailAchievementViewController.swift index aae0e076..62e512c5 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/DetailAchievement/DetailAchievementViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/DetailAchievement/DetailAchievementViewController.swift @@ -54,18 +54,18 @@ final class DetailAchievementViewController: BaseViewController override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() - setupManageCategoryDataSource() + setupManageCategoryCollectionView() } private func setupNavigationBar() { @@ -64,6 +64,11 @@ final class ManageCategoryViewController: BaseViewController delegate?.doneButtonDidClicked() } + private func setupManageCategoryCollectionView() { + setupManageCategoryDataSource() + + } + private func setupManageCategoryDataSource() { layoutView.manageCategoryCollectionView.delegate = self let dataSource = ManageCategoryViewModel.CategoryDataSource.DataSource( From ef1ebb1861b3510664dcbfccd7fb66aef60f94ea Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 04:05:37 +0900 Subject: [PATCH 02/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageCategoryViewController.swift | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift index 8d4de56b..79615705 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift @@ -66,7 +66,7 @@ final class ManageCategoryViewController: BaseViewController private func setupManageCategoryCollectionView() { setupManageCategoryDataSource() - + setupManageCategoryDragDrop() } private func setupManageCategoryDataSource() { @@ -86,8 +86,71 @@ final class ManageCategoryViewController: BaseViewController let diffableDataSource = ManageCategoryViewModel.CategoryDataSource(dataSource: dataSource) viewModel.setupDataSource(diffableDataSource) } + + private func setupManageCategoryDragDrop() { + layoutView.manageCategoryCollectionView.dragDelegate = self + layoutView.manageCategoryCollectionView.dropDelegate = self + layoutView.manageCategoryCollectionView.dragInteractionEnabled = true + } } extension ManageCategoryViewController: UICollectionViewDelegate { } + +extension ManageCategoryViewController: UICollectionViewDragDelegate { + func collectionView( + _ collectionView: UICollectionView, + itemsForBeginning session: UIDragSession, + at indexPath: IndexPath + ) -> [UIDragItem] { + return [UIDragItem(itemProvider: NSItemProvider())] + } +} + +extension ManageCategoryViewController: UICollectionViewDropDelegate { + func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { + var destinationIndexPath: IndexPath + if let indexPath = coordinator.destinationIndexPath { + destinationIndexPath = indexPath + } else { + let row = collectionView.numberOfItems(inSection: 0) + destinationIndexPath = IndexPath(item: row - 1, section: 0) + } + + guard coordinator.proposal.operation == .move else { return } + move(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView) + } + + private func move(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) { + guard + let sourceItem = coordinator.items.first, + let sourceIndexPath = sourceItem.sourceIndexPath + else { return } + + collectionView.performBatchUpdates { [weak self] in + self?.move(sourceIndexPath: sourceIndexPath, destinationIndexPath: destinationIndexPath) + } completion: { finish in + coordinator.drop(sourceItem.dragItem, toItemAt: destinationIndexPath) + } + } + + private func move(sourceIndexPath: IndexPath, destinationIndexPath: IndexPath) { + let sourceItem = viewModel.categories[sourceIndexPath.item] + + // dataSource 이동 + viewModel.categories.remove(at: sourceIndexPath.item) + viewModel.categories.insert(sourceItem, at: destinationIndexPath.item) + } + + func collectionView( + _ collectionView: UICollectionView, + dropSessionDidUpdate session: UIDropSession, + withDestinationIndexPath destinationIndexPath: IndexPath? + ) -> UICollectionViewDropProposal { + guard collectionView.hasActiveDrag else { + return UICollectionViewDropProposal(operation: .forbidden) + } + return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } +} From 9aef51d2be07fe3afc93a816fc43a7ed3a8765ee Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 04:09:08 +0900 Subject: [PATCH 03/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - launch -> fetchCategoryList --- .../Sources/Presentation/Home/HomeActionState.swift | 2 +- .../Sources/Presentation/Home/HomeViewController.swift | 2 +- .../Presentation/Sources/Presentation/Home/HomeViewModel.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeActionState.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeActionState.swift index d91af804..70201328 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeActionState.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeActionState.swift @@ -10,7 +10,7 @@ import Domain extension HomeViewModel { enum HomeViewModelAction { - case launch + case fetchCategoryList case fetchCurrentCategoryInfo case fetchCategoryInfo(categoryId: Int) case addCategory(name: String) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewController.swift index 029d55b3..a15e7d84 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewController.swift @@ -45,7 +45,7 @@ final class HomeViewController: BaseViewController, LoadingIndicator, setupAchievementDataSource() setupCategoryDataSource() - viewModel.action(.launch) + viewModel.action(.fetchCategoryList) } override func viewWillAppear(_ animated: Bool) { diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewModel.swift index fe0958a6..92514329 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewModel.swift @@ -92,7 +92,7 @@ final class HomeViewModel { func action(_ action: HomeViewModelAction) { switch action { - case .launch: + case .fetchCategoryList: fetchCategories() case .fetchCurrentCategoryInfo: guard let currentCategory else { return } From 3342001d2715aca1f4df32b10ed920e7eb789d38 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:24:38 +0900 Subject: [PATCH 04/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iOS/moti/moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift b/iOS/moti/moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift index 84f8ccaa..bb756e31 100644 --- a/iOS/moti/moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift +++ b/iOS/moti/moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift @@ -20,6 +20,7 @@ enum MotiAPI: EndpointProtocol { case fetchCategory(categoryId: Int) case fetchCategoryList case addCategory(requestValue: AddCategoryRequestValue) + case reorderCategories(requestValue: ReorderCategoriesRequestValue) case fetchDetailAchievement(requestValue: FetchDetailAchievementRequestValue) case deleteAchievement(achievementId: Int) case updateAchievement(requestValue: UpdateAchievementRequestValue) @@ -71,6 +72,7 @@ extension MotiAPI { case .fetchCategory(let categoryId): return "/categories/\(categoryId)" case .fetchCategoryList: return "/categories" case .addCategory: return "/categories" + case .reorderCategories: return "/categories" case .fetchDetailAchievement(let requestValue): return "/achievements/\(requestValue.id)" case .saveImage: return "/images" case .deleteAchievement(let achievementId): return "/achievements/\(achievementId)" @@ -125,6 +127,7 @@ extension MotiAPI { case .fetchCategory: return .get case .fetchCategoryList: return .get case .addCategory: return .post + case .reorderCategories: return .put case .fetchDetailAchievement: return .get case .saveImage: return .post case .deleteAchievement: return .delete @@ -173,6 +176,8 @@ extension MotiAPI { return requestValue case .addCategory(let requestValue): return requestValue + case .reorderCategories(let requestValue): + return requestValue case .updateAchievement(let requestValue): return requestValue.body case .postAchievement(let requestValue): From 4263349891114f11f3dc2b440800773591751a1b Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:25:31 +0900 Subject: [PATCH 05/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moti/Data/Sources/Repository/CategoryRepository.swift | 8 ++++++++ .../Data/Sources/Repository/GroupCategoryRepository.swift | 8 ++++++++ .../RepositoryProtocol/CategoryRepositoryProtocol.swift | 1 + 3 files changed, 17 insertions(+) diff --git a/iOS/moti/moti/Data/Sources/Repository/CategoryRepository.swift b/iOS/moti/moti/Data/Sources/Repository/CategoryRepository.swift index 13efbdd5..39688dcc 100644 --- a/iOS/moti/moti/Data/Sources/Repository/CategoryRepository.swift +++ b/iOS/moti/moti/Data/Sources/Repository/CategoryRepository.swift @@ -38,4 +38,12 @@ public struct CategoryRepository: CategoryRepositoryProtocol { guard let categoryDTO = responseDTO.data else { throw NetworkError.decode } return CategoryItem(dto: categoryDTO) } + + public func reorderCategories(requestValue: ReorderCategoriesRequestValue) async throws -> Bool { + let endpoint = MotiAPI.reorderCategories(requestValue: requestValue) + let responseDTO = try await provider.request(with: endpoint, type: SimpleResponseDTO.self) + + guard let isSuccess = responseDTO.success else { throw NetworkError.decode } + return isSuccess + } } diff --git a/iOS/moti/moti/Data/Sources/Repository/GroupCategoryRepository.swift b/iOS/moti/moti/Data/Sources/Repository/GroupCategoryRepository.swift index c2f31dbf..e8664932 100644 --- a/iOS/moti/moti/Data/Sources/Repository/GroupCategoryRepository.swift +++ b/iOS/moti/moti/Data/Sources/Repository/GroupCategoryRepository.swift @@ -42,4 +42,12 @@ public struct GroupCategoryRepository: CategoryRepositoryProtocol { guard let categoryDTO = responseDTO.data else { throw NetworkError.decode } return CategoryItem(dto: categoryDTO) } + + public func reorderCategories(requestValue: ReorderCategoriesRequestValue) async throws -> Bool { + let endpoint = MotiAPI.reorderCategories(requestValue: requestValue) + let responseDTO = try await provider.request(with: endpoint, type: SimpleResponseDTO.self) + + guard let isSuccess = responseDTO.success else { throw NetworkError.decode } + return isSuccess + } } diff --git a/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/CategoryRepositoryProtocol.swift b/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/CategoryRepositoryProtocol.swift index e58aeaa8..078021c1 100644 --- a/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/CategoryRepositoryProtocol.swift +++ b/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/CategoryRepositoryProtocol.swift @@ -11,4 +11,5 @@ public protocol CategoryRepositoryProtocol { func fetchCategory(categoryId: Int) async throws -> CategoryItem func fetchCategoryList() async throws -> [CategoryItem] func addCategory(requestValue: AddCategoryRequestValue) async throws -> CategoryItem + func reorderCategories(requestValue: ReorderCategoriesRequestValue) async throws -> Bool } From 3883f40884d79d1e7634d15c6bbd351514149f71 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:25:51 +0900 Subject: [PATCH 06/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?UseCase=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UseCase/ReorderCategoriesUseCase.swift | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 iOS/moti/moti/Domain/Sources/Domain/UseCase/ReorderCategoriesUseCase.swift diff --git a/iOS/moti/moti/Domain/Sources/Domain/UseCase/ReorderCategoriesUseCase.swift b/iOS/moti/moti/Domain/Sources/Domain/UseCase/ReorderCategoriesUseCase.swift new file mode 100644 index 00000000..b80c1efd --- /dev/null +++ b/iOS/moti/moti/Domain/Sources/Domain/UseCase/ReorderCategoriesUseCase.swift @@ -0,0 +1,28 @@ +// +// ReorderCategoriesUseCase.swift +// +// +// Created by Kihyun Lee on 12/27/23. +// + +import Foundation + +public struct ReorderCategoriesRequestValue: RequestValue { + public let order: [Int] + + public init(order: [Int]) { + self.order = order + } +} + +public struct ReorderCategoriesUseCase { + private let repository: CategoryRepositoryProtocol + + public init(repository: CategoryRepositoryProtocol) { + self.repository = repository + } + + public func execute(requestValue: ReorderCategoriesRequestValue) async throws -> Bool { + return try await repository.reorderCategories(requestValue: requestValue) + } +} From 3dc797d41566ae724020e1ed4468bbb497e2b3d6 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:27:18 +0900 Subject: [PATCH 07/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20refetch=20in?= =?UTF-8?q?=20=EA=B0=9C=EC=9D=B8=20=ED=99=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Presentation/Home/HomeCoordinator.swift | 8 ++++++++ .../Sources/Presentation/Home/HomeViewController.swift | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeCoordinator.swift index 0a155ec6..2f8201fa 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeCoordinator.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeCoordinator.swift @@ -65,6 +65,7 @@ public final class HomeCoordinator: Coordinator { func moveToManageCategoryViewController(categories: [CategoryItem]) { let manageCategoryCoordinator = ManageCategoryCoordinator(navigationController, self) + manageCategoryCoordinator.delegate = self childCoordinators.append(manageCategoryCoordinator) manageCategoryCoordinator.start(categories: categories) } @@ -107,3 +108,10 @@ extension HomeCoordinator: EditAchievementCoordinatorDelegate { // MARK: - CaptureCoordinatorDelegate extension HomeCoordinator: CaptureCoordinatorDelegate { } + +// MARK: - ManageCategoryCoordinatorDelegate +extension HomeCoordinator: ManageCategoryCoordinatorDelegate { + func doneButtonDidClicked() { + currentViewController?.fetchCategoryList() + } +} diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewController.swift index a15e7d84..141165b8 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Home/HomeViewController.swift @@ -76,6 +76,10 @@ final class HomeViewController: BaseViewController, LoadingIndicator, showCelebrate(with: newAchievement) } + func fetchCategoryList() { + viewModel.action(.fetchCategoryList) + } + private func addTargets() { if let tabBarController = navigationController?.tabBarController as? TabBarViewController { tabBarController.captureButton.addTarget(self, action: #selector(captureButtonDidClicked), for: .touchUpInside) From 412c997fdd187a606491d7f1aa970522b1ebbfbf Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:28:28 +0900 Subject: [PATCH 08/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageCategoryViewController.swift | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift index 79615705..19004357 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift @@ -37,6 +37,7 @@ final class ManageCategoryViewController: BaseViewController override func viewDidLoad() { super.viewDidLoad() setupNavigationBar() + bind() setupManageCategoryCollectionView() } @@ -60,8 +61,7 @@ final class ManageCategoryViewController: BaseViewController } @objc private func doneButtonDidClicked() { - // viewModel.action() - delegate?.doneButtonDidClicked() + viewModel.action(.reorderCategories) } private func setupManageCategoryCollectionView() { @@ -94,6 +94,24 @@ final class ManageCategoryViewController: BaseViewController } } +private extension ManageCategoryViewController { + func bind() { + viewModel.reorderCategoriesState + .receive(on: DispatchQueue.main) + .sink { [weak self] state in + guard let self else { return } + switch state { + case .success: + delegate?.doneButtonDidClicked() + case .failed(let message): + showErrorAlert(message: message) + } + + } + .store(in: &cancellables) + } +} + extension ManageCategoryViewController: UICollectionViewDelegate { } From d21a51843354b7aa815afcbd7bb3dcfb1d5dc7de Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:28:47 +0900 Subject: [PATCH 09/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?in=20VM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageCategoryViewModel.swift | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewModel.swift index 6651d0f8..c4a5f862 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewModel.swift @@ -12,7 +12,12 @@ import Combine final class ManageCategoryViewModel { enum ManageCategoryViewModelAction { - + case reorderCategories + } + + enum ReorderCategoriesState { + case success + case failed(message: String) } typealias CategoryDataSource = ListDiffableDataSource @@ -25,14 +30,47 @@ final class ManageCategoryViewModel { } } + private let reorderCategoriesUseCase: ReorderCategoriesUseCase + + private(set) var reorderCategoriesState = PassthroughSubject() + init( - categories: [CategoryItem] + categories: [CategoryItem], + reorderCategoriesUseCase: ReorderCategoriesUseCase ) { self.categories = categories.filter { !$0.isWhole && !$0.isUnset } + self.reorderCategoriesUseCase = reorderCategoriesUseCase } func setupDataSource(_ dataSource: CategoryDataSource) { self.categoryDataSource = dataSource categoryDataSource?.update(data: categories) } + + func action(_ action: ManageCategoryViewModelAction) { + switch action { + case .reorderCategories: + reorderCategories() + } + } + + private func reorderCategories() { + Task { + do { + let requestValue = ReorderCategoriesRequestValue(order: categories.map { $0.id }) + let isSuccess = try await reorderCategoriesUseCase.execute(requestValue: requestValue) + if isSuccess { + reorderCategoriesState.send(.success) + } else { + reorderCategoriesState.send(.failed(message: "카테고리 순서 변경에 실패했습니다.")) + } + } catch { + Logger.debug("reorder categories error: \(error)") + + // response가 비어있어서 204가 오는데, catch 문으로 넘어온다. 응답을 넣어주시면 없앤다. + reorderCategoriesState.send(.success) +// reorderCategoriesState.send(.failed(message: error.localizedDescription)) + } + } + } } From 27d68160625dda29ba1bd21ea38a170947848120 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:29:05 +0900 Subject: [PATCH 10/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?UseCase=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageCategory/ManageCategoryCoordinator.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryCoordinator.swift index cb104c4e..673a5500 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryCoordinator.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryCoordinator.swift @@ -10,10 +10,15 @@ import Core import Domain import Data +protocol ManageCategoryCoordinatorDelegate: AnyObject { + func doneButtonDidClicked() +} + final class ManageCategoryCoordinator: Coordinator { var parentCoordinator: Coordinator? var childCoordinators: [Coordinator] = [] var navigationController: UINavigationController + weak var delegate: ManageCategoryCoordinatorDelegate? init( _ navigationController: UINavigationController, @@ -26,8 +31,10 @@ final class ManageCategoryCoordinator: Coordinator { func start() { } func start(categories: [CategoryItem]) { + let categoryRepository = CategoryRepository() let manageCategoryVM = ManageCategoryViewModel( - categories: categories + categories: categories, + reorderCategoriesUseCase: .init(repository: categoryRepository) ) let manageCategoryVC = ManageCategoryViewController(viewModel: manageCategoryVM) manageCategoryVC.coordinator = self @@ -43,6 +50,7 @@ extension ManageCategoryCoordinator: ManageCategoryViewControllerDelegate { } func doneButtonDidClicked() { + delegate?.doneButtonDidClicked() parentCoordinator?.dismiss(child: self, animated: true) } } From 51eb673a97766f0e6d78ecfe4e24aff01f89e96b Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:37:35 +0900 Subject: [PATCH 11/47] =?UTF-8?q?[iOS]=20refactor:=20=EA=B7=B8=EB=A3=B9?= =?UTF-8?q?=ED=99=88=20launch=20=EC=95=A1=EC=85=98=EB=AA=85=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 - launch -> fetchCategories --- .../Sources/Presentation/GroupHome/GroupHomeActionState.swift | 2 +- .../Presentation/GroupHome/GroupHomeViewController.swift | 2 +- .../Sources/Presentation/GroupHome/GroupHomeViewModel.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeActionState.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeActionState.swift index 92b23502..92558626 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeActionState.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeActionState.swift @@ -10,7 +10,7 @@ import Domain extension GroupHomeViewModel { enum GroupHomeViewModelAction { - case launch + case fetchCategories case fetchCurrentCategoryInfo case fetchCategoryInfo(categoryId: Int) case addCategory(name: String) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewController.swift index ffb2a8dd..4a2113f4 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewController.swift @@ -39,7 +39,7 @@ final class GroupHomeViewController: BaseViewController, LoadingIndica setupAchievementDataSource() setupCategoryDataSource() - viewModel.action(.launch) + viewModel.action(.fetchCategories) } override func viewWillAppear(_ animated: Bool) { diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewModel.swift index 76d377d8..ad63213b 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewModel.swift @@ -112,7 +112,7 @@ final class GroupHomeViewModel { func action(_ action: GroupHomeViewModelAction) { switch action { - case .launch: + case .fetchCategories: fetchCategories() case .fetchCurrentCategoryInfo: guard let currentCategory else { return } From 399ec8c6d4c53fc93778830899ccb0d11bcb2cc6 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 05:47:03 +0900 Subject: [PATCH 12/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?in=20=EA=B7=B8=EB=A3=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/GroupHome/GroupHomeActionState.swift | 2 +- .../Presentation/GroupHome/GroupHomeCoordinator.swift | 7 +++++++ .../Presentation/GroupHome/GroupHomeViewController.swift | 6 +++++- .../Presentation/GroupHome/GroupHomeViewModel.swift | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeActionState.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeActionState.swift index 92558626..7726740f 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeActionState.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeActionState.swift @@ -10,7 +10,7 @@ import Domain extension GroupHomeViewModel { enum GroupHomeViewModelAction { - case fetchCategories + case fetchCategoryList case fetchCurrentCategoryInfo case fetchCategoryInfo(categoryId: Int) case addCategory(name: String) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeCoordinator.swift index d451a435..3805543d 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeCoordinator.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeCoordinator.swift @@ -63,6 +63,7 @@ final class GroupHomeCoordinator: Coordinator { func moveToManageCategoryViewController(categories: [CategoryItem]) { let manageCategoryCoordinator = ManageCategoryCoordinator(navigationController, self) + manageCategoryCoordinator.delegate = self childCoordinators.append(manageCategoryCoordinator) manageCategoryCoordinator.start(categories: categories) } @@ -120,3 +121,9 @@ extension GroupHomeCoordinator: GroupDetailAchievementCoordinatorDelegate { // MARK: - CaptureCoordinatorDelegate extension GroupHomeCoordinator: CaptureCoordinatorDelegate { } + +extension GroupHomeCoordinator: ManageCategoryCoordinatorDelegate { + func doneButtonDidClicked() { + currentViewController?.fetchCategoryList() + } +} diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewController.swift index 4a2113f4..e57bb858 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewController.swift @@ -39,7 +39,7 @@ final class GroupHomeViewController: BaseViewController, LoadingIndica setupAchievementDataSource() setupCategoryDataSource() - viewModel.action(.fetchCategories) + viewModel.action(.fetchCategoryList) } override func viewWillAppear(_ animated: Bool) { @@ -164,6 +164,10 @@ final class GroupHomeViewController: BaseViewController, LoadingIndica viewModel.action(.deleteUserDataSourceItem(userCode: userCode)) } + func fetchCategoryList() { + viewModel.action(.fetchCategoryList) + } + private func showCelebrate(with achievement: Achievement) { let celebrateVC = CelebrateViewController(achievement: achievement) celebrateVC.modalPresentationStyle = .overFullScreen diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewModel.swift index ad63213b..02b87e58 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupHome/GroupHomeViewModel.swift @@ -112,7 +112,7 @@ final class GroupHomeViewModel { func action(_ action: GroupHomeViewModelAction) { switch action { - case .fetchCategories: + case .fetchCategoryList: fetchCategories() case .fetchCurrentCategoryInfo: guard let currentCategory else { return } From 43a5b040571155cb3b4e1b066004f986a6928779 Mon Sep 17 00:00:00 2001 From: Dltmd202 Date: Wed, 27 Dec 2023 13:25:39 +0900 Subject: [PATCH 13/47] =?UTF-8?q?[BE]=20feat:=20CategoryNotFoundException?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/category/exception/category-not-found.exception.ts | 8 ++++++++ BE/src/common/exception/error-code.ts | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 BE/src/category/exception/category-not-found.exception.ts diff --git a/BE/src/category/exception/category-not-found.exception.ts b/BE/src/category/exception/category-not-found.exception.ts new file mode 100644 index 00000000..1226b7bc --- /dev/null +++ b/BE/src/category/exception/category-not-found.exception.ts @@ -0,0 +1,8 @@ +import { MotimateException } from '../../common/exception/motimate.excpetion'; +import { ERROR_INFO } from '../../common/exception/error-code'; + +export class CategoryNotFoundException extends MotimateException { + constructor() { + super(ERROR_INFO.CATEGORY_NOT_FOUND); + } +} diff --git a/BE/src/common/exception/error-code.ts b/BE/src/common/exception/error-code.ts index 93ab7a78..0a40b49b 100644 --- a/BE/src/common/exception/error-code.ts +++ b/BE/src/common/exception/error-code.ts @@ -147,4 +147,8 @@ export const ERROR_INFO = { statusCode: 400, message: '잘못된 카테고리 순서 변경 요청입니다.', }, + CATEGORY_NOT_FOUND: { + statusCode: 404, + message: '카테고리를 찾을 수 없습니다.', + }, } as const; From a288ef65ab6ffc16edc146211ba7a0abb3bff81c Mon Sep 17 00:00:00 2001 From: Dltmd202 Date: Wed, 27 Dec 2023 13:26:41 +0900 Subject: [PATCH 14/47] =?UTF-8?q?[BE]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=82=AD=EC=A0=9C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/category/application/category.service.ts | 13 +++++++++++++ .../category/controller/category.controller.ts | 17 +++++++++++++++++ BE/src/category/entities/category.repository.ts | 5 +++++ BE/src/users/domain/user.domain.ts | 4 ++++ 4 files changed, 39 insertions(+) diff --git a/BE/src/category/application/category.service.ts b/BE/src/category/application/category.service.ts index 1dd4c94c..bc96596f 100644 --- a/BE/src/category/application/category.service.ts +++ b/BE/src/category/application/category.service.ts @@ -9,6 +9,7 @@ import { NotFoundCategoryException } from '../exception/not-found-category.excep import { CategoryRelocateRequest } from '../dto/category-relocate.request'; import { UserRepository } from '../../users/entities/user.repository'; import { InvalidCategoryRelocateException } from '../exception/Invalid-Category-Relocate.exception'; +import { CategoryNotFoundException } from '../exception/category-not-found.exception'; @Injectable() export class CategoryService { @@ -61,4 +62,16 @@ export class CategoryService { await this.categoryRepository.saveCategory(category); } } + + @Transactional() + async deleteCategory(user: User, categoryId: number){ + const category = await this.categoryRepository.findByIdAndUser( + user.id, + categoryId, + ); + if (!category) throw new CategoryNotFoundException(); + user.deleteCategory(); + await this.userRepository.updateUser(user); + await this.categoryRepository.deleteCategory(category); + } } diff --git a/BE/src/category/controller/category.controller.ts b/BE/src/category/controller/category.controller.ts index f408f50f..8b7d5f64 100644 --- a/BE/src/category/controller/category.controller.ts +++ b/BE/src/category/controller/category.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, HttpCode, HttpStatus, @@ -109,4 +110,20 @@ export class CategoryController { ) { return this.categoryService.relocateCategory(user, categoryRelocateRequest); } + + @ApiBearerAuth('accessToken') + @ApiOperation({ + summary: '카테고리 순서 변경 API', + description: + '변경될 카테고리 순서로 카테고리 아이디를 배열의 형태로 요청한다.', + }) + @Delete('/:categoryId') + @UseGuards(AccessTokenGuard) + @HttpCode(HttpStatus.NO_CONTENT) + async deleteCategory( + @Param('categoryId', ParseIntPipe) categoryId: number, + @AuthenticatedUser() user: User, + ) { + return this.categoryService.deleteCategory(user, categoryId); + } } diff --git a/BE/src/category/entities/category.repository.ts b/BE/src/category/entities/category.repository.ts index fa7bb875..b42b7c17 100644 --- a/BE/src/category/entities/category.repository.ts +++ b/BE/src/category/entities/category.repository.ts @@ -46,6 +46,7 @@ export class CategoryRepository extends TransactionalRepository .addSelect('COUNT(achievement.id)', 'achievementCount') .leftJoin('category.achievements', 'achievement') .where('category.user_id = :user', { user: user.id }) + .andWhere('category.deletedAt is NULL') .orderBy('category.seq', 'ASC') .groupBy('category.id') .getRawMany(); @@ -127,4 +128,8 @@ export class CategoryRepository extends TransactionalRepository return categories.map((c) => c.toModel()); } + + async deleteCategory(category: Category) { + await this.repository.softDelete(category.id); + } } diff --git a/BE/src/users/domain/user.domain.ts b/BE/src/users/domain/user.domain.ts index 862c33bd..b9ef90b1 100644 --- a/BE/src/users/domain/user.domain.ts +++ b/BE/src/users/domain/user.domain.ts @@ -38,4 +38,8 @@ export class User { ++this.categoryCount; return new Category(this, categoryName, ++this.categorySequence); } + + deleteCategory() { + this.categoryCount--; + } } From 9608bc91e6814e6d7cd71a1a2efe941ea5589e0c Mon Sep 17 00:00:00 2001 From: Dltmd202 Date: Wed, 27 Dec 2023 13:27:10 +0900 Subject: [PATCH 15/47] =?UTF-8?q?[BE]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=82=AD=EC=A0=9C=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/category.service.spec.ts | 167 ++++++++++++++---- .../controller/category.controller.spec.ts | 72 ++++++++ BE/test/category/category-fixture.ts | 9 +- BE/test/category/category-test.module.ts | 6 +- 4 files changed, 212 insertions(+), 42 deletions(-) diff --git a/BE/src/category/application/category.service.spec.ts b/BE/src/category/application/category.service.spec.ts index aebf0e97..44a8872e 100644 --- a/BE/src/category/application/category.service.spec.ts +++ b/BE/src/category/application/category.service.spec.ts @@ -18,6 +18,9 @@ import { transactionTest } from '../../../test/common/transaction-test'; import { NotFoundCategoryException } from '../exception/not-found-category.exception'; import { CategoryRelocateRequest } from '../dto/category-relocate.request'; import { InvalidCategoryRelocateException } from '../exception/Invalid-Category-Relocate.exception'; +import { CategoryMetaData } from '../dto/category-metadata'; +import { CategoryNotFoundException } from '../exception/category-not-found.exception'; +import { UserRepository } from '../../users/entities/user.repository'; describe('CategoryService', () => { let categoryService: CategoryService; @@ -25,6 +28,7 @@ describe('CategoryService', () => { let usersFixture: UsersFixture; let categoryFixture: CategoryFixture; let achievementFixture: AchievementFixture; + let userRepository: UserRepository; beforeAll(async () => { const app: TestingModule = await Test.createTestingModule({ @@ -40,6 +44,7 @@ describe('CategoryService', () => { providers: [], }).compile(); + userRepository = app.get(UserRepository); categoryFixture = app.get(CategoryFixture); achievementFixture = app.get(AchievementFixture); usersFixture = app.get(UsersFixture); @@ -107,52 +112,56 @@ describe('CategoryService', () => { describe('getCategoriesByUsers는 카테고리를 조회할 수 있다', () => { it('user에 대한 카테고리가 없을 때 빈 배열을 반환한다.', async () => { - // given - const user = await usersFixture.getUser(1); + await transactionTest(dataSource, async () => { + // given + const user = await usersFixture.getUser(1); - // when - const retrievedCategories = - await categoryService.getCategoriesByUser(user); + // when + const retrievedCategories = + await categoryService.getCategoriesByUser(user); - // then - expect(retrievedCategories).toBeDefined(); - expect(retrievedCategories.length).toBe(1); - expect(retrievedCategories[0].categoryId).toEqual(-1); + // then + expect(retrievedCategories).toBeDefined(); + expect(retrievedCategories.length).toBe(1); + expect(retrievedCategories[0].categoryId).toEqual(-1); + }); }); it('user에 대한 카테고리가 있을 때 카테고리를 반환한다.', async () => { - // given - const user = await usersFixture.getUser(1); - const categories: Category[] = await categoryFixture.getCategories( - 4, - user, - ); - await achievementFixture.getAchievements(4, user, categories[0]); - await achievementFixture.getAchievements(5, user, categories[1]); - await achievementFixture.getAchievements(7, user, categories[2]); - await achievementFixture.getAchievements(10, user, categories[3]); + await transactionTest(dataSource, async () => { + // given + const user = await usersFixture.getUser(1); + const categories: Category[] = await categoryFixture.getCategories( + 4, + user, + ); + await achievementFixture.getAchievements(4, user, categories[0]); + await achievementFixture.getAchievements(5, user, categories[1]); + await achievementFixture.getAchievements(7, user, categories[2]); + await achievementFixture.getAchievements(10, user, categories[3]); - // when - const retrievedCategories = - await categoryService.getCategoriesByUser(user); + // when + const retrievedCategories = + await categoryService.getCategoriesByUser(user); - // then - expect(retrievedCategories.length).toBe(5); - expect(retrievedCategories[0].categoryId).toEqual(-1); - expect(retrievedCategories[0].insertedAt).toBeNull(); - expect(retrievedCategories[0].achievementCount).toBe(0); - expect(retrievedCategories[1].categoryId).toEqual(categories[0].id); - expect(retrievedCategories[1].insertedAt).toBeInstanceOf(Date); - expect(retrievedCategories[1].achievementCount).toBe(4); - expect(retrievedCategories[2].categoryId).toEqual(categories[1].id); - expect(retrievedCategories[2].insertedAt).toBeInstanceOf(Date); - expect(retrievedCategories[2].achievementCount).toBe(5); - expect(retrievedCategories[3].categoryId).toEqual(categories[2].id); - expect(retrievedCategories[3].insertedAt).toBeInstanceOf(Date); - expect(retrievedCategories[3].achievementCount).toBe(7); - expect(retrievedCategories[4].categoryId).toEqual(categories[3].id); - expect(retrievedCategories[4].insertedAt).toBeInstanceOf(Date); - expect(retrievedCategories[4].achievementCount).toBe(10); + // then + expect(retrievedCategories.length).toBe(5); + expect(retrievedCategories[0].categoryId).toEqual(-1); + expect(retrievedCategories[0].insertedAt).toBeNull(); + expect(retrievedCategories[0].achievementCount).toBe(0); + expect(retrievedCategories[1].categoryId).toEqual(categories[0].id); + expect(retrievedCategories[1].insertedAt).toBeInstanceOf(Date); + expect(retrievedCategories[1].achievementCount).toBe(4); + expect(retrievedCategories[2].categoryId).toEqual(categories[1].id); + expect(retrievedCategories[2].insertedAt).toBeInstanceOf(Date); + expect(retrievedCategories[2].achievementCount).toBe(5); + expect(retrievedCategories[3].categoryId).toEqual(categories[2].id); + expect(retrievedCategories[3].insertedAt).toBeInstanceOf(Date); + expect(retrievedCategories[3].achievementCount).toBe(7); + expect(retrievedCategories[4].categoryId).toEqual(categories[3].id); + expect(retrievedCategories[4].insertedAt).toBeInstanceOf(Date); + expect(retrievedCategories[4].achievementCount).toBe(10); + }); }); }); @@ -373,4 +382,84 @@ describe('CategoryService', () => { }); }); }); + + describe('deleteCategory는 카테고리를 삭제할 수 있다.', () => { + it('본인의 카테고리를 삭제할 수 있다.', async () => { + await transactionTest(dataSource, async () => { + // given + const user = await usersFixture.getUser('ABCD'); + const category = await categoryFixture.getCategory(user, '카테고리1'); + + // when + await categoryService.deleteCategory(user, category.id); + const categoryMetaData: CategoryMetaData[] = + await categoryService.getCategoriesByUser(user); + + // then + expect(categoryMetaData.length).toBe(1); + expect(categoryMetaData[0].categoryId).toBe(-1); + }); + }); + + it('본인의 카테고리가 아닌 카테고리는 삭제할 수 없다.', async () => { + await transactionTest(dataSource, async () => { + // given + const otherUser = await usersFixture.getUser('ABC'); + const category = await categoryFixture.getCategory( + otherUser, + '카테고리1', + ); + + const user = await usersFixture.getUser('ABCD'); + + // when + await expect( + categoryService.deleteCategory(user, category.id), + ).rejects.toThrow(new CategoryNotFoundException()); + }); + }); + + it('카테고리를 삭제해도 기존 순서가 유지된다.', async () => { + await transactionTest(dataSource, async () => { + // given + const user = await usersFixture.getUser('ABCD'); + const category1 = await categoryFixture.getCategory(user, '카테고리1'); + const category2 = await categoryFixture.getCategory(user, '카테고리2'); + const category3 = await categoryFixture.getCategory(user, '카테고리3'); + + // when + await categoryService.deleteCategory(user, category2.id); + const categoryMetaData: CategoryMetaData[] = + await categoryService.getCategoriesByUser(user); + + // then + expect(categoryMetaData.length).toBe(3); + expect(categoryMetaData[0].categoryId).toBe(-1); + expect(categoryMetaData[1].categoryId).toBe(category1.id); + expect(categoryMetaData[2].categoryId).toBe(category3.id); + }); + }); + + it('카테고리를 삭제하면 유저의 카테고리 카운트가 반영된다.', async () => { + await transactionTest(dataSource, async () => { + // given + const user = await usersFixture.getUser('ABCD'); + await categoryFixture.getCategory(user, '카테고리1'); + const category2 = await categoryFixture.getCategory(user, '카테고리2'); + await categoryFixture.getCategory(user, '카테고리3'); + + // when + await categoryService.deleteCategory(user, category2.id); + const userEntity = await userRepository.repository.findOne({ + where: { + id: user.id, + }, + }); + + // then + expect(userEntity.categoryCount).toBe(2); + expect(userEntity.categorySequence).toBe(3); + }); + }); + }); }); diff --git a/BE/src/category/controller/category.controller.spec.ts b/BE/src/category/controller/category.controller.spec.ts index beaf66dc..80689253 100644 --- a/BE/src/category/controller/category.controller.spec.ts +++ b/BE/src/category/controller/category.controller.spec.ts @@ -17,6 +17,7 @@ import { GroupCategoryMetadata } from '../../group/category/dto/group-category-m import { NotFoundCategoryException } from '../exception/not-found-category.exception'; import { CategoryRelocateRequest } from '../dto/category-relocate.request'; import { InvalidCategoryRelocateException } from '../exception/Invalid-Category-Relocate.exception'; +import { CategoryNotFoundException } from '../exception/category-not-found.exception'; describe('CategoryController Test', () => { let app: INestApplication; @@ -414,4 +415,75 @@ describe('CategoryController Test', () => { .expect(400); }); }); + + describe('deleteCategory는 카테고리를 삭제할 수 있다.', () => { + it('카테고리 삭제에 성공하면 204을 반환한다.', async () => { + // given + const { accessToken } = await authFixture.getAuthenticatedUser('ABC'); + + when(mockCategoryService.deleteCategory(anyOfClass(User), 1)).thenResolve( + undefined, + ); + + // when + // then + return request(app.getHttpServer()) + .delete(`/api/v1/categories/1`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(204); + }); + + it('카테고리 삭제에 실패하면 400을 반환한다.', async () => { + // given + const { accessToken } = await authFixture.getAuthenticatedUser('ABC'); + + when(mockCategoryService.deleteCategory(anyOfClass(User), 1)).thenThrow( + new CategoryNotFoundException(), + ); + + // when + // then + return request(app.getHttpServer()) + .delete(`/api/v1/categories/1`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(404) + .expect((res: request.Response) => { + expect(res.body.success).toBe(false); + expect(res.body.message).toBe('카테고리를 찾을 수 없습니다.'); + }); + }); + + it('잘못된 인증시 401을 반환한다.', async () => { + // given + const accessToken = 'abcd.abcd.efgh'; + + // when + // then + return request(app.getHttpServer()) + .get(`/api/v1/categories/1006`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(401) + .expect((res: request.Response) => { + expect(res.body.success).toBe(false); + expect(res.body.message).toBe('잘못된 토큰입니다.'); + }); + }); + + it('만료된 인증정보에 401을 반환한다.', async () => { + // given + const { accessToken } = + await authFixture.getExpiredAccessTokenUser('ABC'); + + // when + // then + return request(app.getHttpServer()) + .delete(`/api/v1/categories/1`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(401) + .expect((res: request.Response) => { + expect(res.body.success).toBe(false); + expect(res.body.message).toBe('만료된 토큰입니다.'); + }); + }); + }); }); diff --git a/BE/test/category/category-fixture.ts b/BE/test/category/category-fixture.ts index 6a59b6b3..d1d15888 100644 --- a/BE/test/category/category-fixture.ts +++ b/BE/test/category/category-fixture.ts @@ -2,12 +2,16 @@ import { User } from '../../src/users/domain/user.domain'; import { Injectable } from '@nestjs/common'; import { Category } from '../../src/category/domain/category.domain'; import { CategoryRepository } from '../../src/category/entities/category.repository'; +import { UserRepository } from '../../src/users/entities/user.repository'; @Injectable() export class CategoryFixture { private static id = 0; - constructor(private readonly categoryRepository: CategoryRepository) {} + constructor( + private readonly categoryRepository: CategoryRepository, + private readonly userRepository: UserRepository, + ) {} async getCategory(user: User, name?: string): Promise { user.categoryCount++; @@ -16,7 +20,8 @@ export class CategoryFixture { user, name || CategoryFixture.getDummyCategoryName(), ); - + await this.userRepository.updateUser(user); + category.seq = user.categorySequence; return await this.categoryRepository.saveCategory(category); } diff --git a/BE/test/category/category-test.module.ts b/BE/test/category/category-test.module.ts index 756ae402..dc619413 100644 --- a/BE/test/category/category-test.module.ts +++ b/BE/test/category/category-test.module.ts @@ -3,10 +3,14 @@ import { Module } from '@nestjs/common'; import { CategoryRepository } from '../../src/category/entities/category.repository'; import { CategoryModule } from '../../src/category/category.module'; import { CategoryFixture } from './category-fixture'; +import { UserRepository } from '../../src/users/entities/user.repository'; @Module({ imports: [ - CustomTypeOrmModule.forCustomRepository([CategoryRepository]), + CustomTypeOrmModule.forCustomRepository([ + CategoryRepository, + UserRepository, + ]), CategoryModule, ], providers: [CategoryFixture], From 09c8fecf6d6aefe74567d27febdb52e4c70c8fb9 Mon Sep 17 00:00:00 2001 From: Dltmd202 Date: Wed, 27 Dec 2023 13:28:03 +0900 Subject: [PATCH 16/47] =?UTF-8?q?[BE]=20chore:=20categoryService=20?= =?UTF-8?q?=EB=A6=B0=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/category/application/category.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BE/src/category/application/category.service.ts b/BE/src/category/application/category.service.ts index bc96596f..ca5c0da7 100644 --- a/BE/src/category/application/category.service.ts +++ b/BE/src/category/application/category.service.ts @@ -64,7 +64,7 @@ export class CategoryService { } @Transactional() - async deleteCategory(user: User, categoryId: number){ + async deleteCategory(user: User, categoryId: number) { const category = await this.categoryRepository.findByIdAndUser( user.id, categoryId, From 2030c08581e0475141cde63707aefbc33e980542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8C=E1=85=A5=E1=86=BC=E1=84=8C?= =?UTF-8?q?=E1=85=AE?= Date: Wed, 27 Dec 2023 16:28:01 +0900 Subject: [PATCH 17/47] =?UTF-8?q?[iOS]=20feat:=20Version=20API=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=ED=95=98=EB=A9=B4=203=EC=B4=88=20=EB=92=A4=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=9A=94=EC=B2=AD=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DetailAchievementViewController.swift | 2 +- .../Launch/LaunchViewController.swift | 26 ++++++++++++++++--- .../Presentation/Launch/LaunchViewModel.swift | 4 +-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/DetailAchievement/DetailAchievementViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/DetailAchievement/DetailAchievementViewController.swift index aae0e076..5b6b0854 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/DetailAchievement/DetailAchievementViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/DetailAchievement/DetailAchievementViewController.swift @@ -58,7 +58,7 @@ final class DetailAchievementViewController: BaseViewController { // MARK: - Properties weak var coordinator: LaunchCoodinator? weak var delegate: LaunchViewControllerDelegate? - private let viewModel: LaunchViewModel private var cancellables: Set = [] + private var retryTimer: Timer? + + // MARK: - Init init(viewModel: LaunchViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) @@ -36,10 +38,27 @@ final class LaunchViewController: BaseViewController { super.viewDidLoad() bind() - viewModel.action(.launch) + viewModel.action(.fetchVersion) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + retryTimer?.invalidate() + retryTimer = nil } - private func bind() { + // MARK: - Methods + private func startRetryVersionTimer() { + retryTimer?.invalidate() + retryTimer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { [weak self] _ in + guard let self else { return } + viewModel.action(.fetchVersion) + } + } +} + +private extension LaunchViewController { + func bind() { viewModel.$versionState .receive(on: RunLoop.main) .sink { [weak self] state in @@ -52,6 +71,7 @@ final class LaunchViewController: BaseViewController { layoutView.update(progressMessage: "자동 로그인 시도 중") viewModel.action(.autoLogin) case .error(let message): + startRetryVersionTimer() Logger.error("Launch Version Error: \(message)") } } diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift index 1185a765..ac023a7b 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift @@ -11,7 +11,7 @@ import Core final class LaunchViewModel { enum LaunchViewModelAction { - case launch + case fetchVersion case autoLogin } @@ -45,7 +45,7 @@ final class LaunchViewModel { func action(_ action: LaunchViewModelAction) { switch action { - case .launch: + case .fetchVersion: fetchVersion() case .autoLogin: requestAutoLogin() From b5c62149db79af4b92abef4ed3670aef38606c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8C=E1=85=A5=E1=86=BC=E1=84=8C?= =?UTF-8?q?=E1=85=AE?= Date: Wed, 27 Dec 2023 16:43:46 +0900 Subject: [PATCH 18/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B5=9C=EC=86=8C=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=EB=B3=B4=EB=8B=A4=20=EB=82=AE=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EA=B0=95=EC=A0=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20Alert=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Launch/LaunchViewController.swift | 18 ++++++++++++++++++ .../Presentation/Launch/LaunchViewModel.swift | 13 +++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewController.swift index d27ea4b7..dad443b0 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewController.swift @@ -55,6 +55,19 @@ final class LaunchViewController: BaseViewController { viewModel.action(.fetchVersion) } } + + private func showRequiredUpdateAlert() { + showOneButtonAlert( + title: "안내", + message: "앱 스토어에서 최신 앱으로 업데이트 하셔야만 실행이 가능합니다.", + okTitle: "업데이트", + okAction: { + let appstoreURLString = "itms-apps://itunes.apple.com/app/apple-store/6471563249" + guard let url = URL(string: appstoreURLString) else { return } + UIApplication.shared.open(url) + } + ) + } } private extension LaunchViewController { @@ -67,9 +80,14 @@ private extension LaunchViewController { case .none: break case .loading: layoutView.update(progressMessage: "버전을 가져오는 중") + case .checkVersion: + layoutView.update(progressMessage: "버전을 검사하는 중") case .finish: layoutView.update(progressMessage: "자동 로그인 시도 중") viewModel.action(.autoLogin) + case .requiredUpdate: + layoutView.update(progressMessage: "최신 앱으로 업데이트 필요") + showRequiredUpdateAlert() case .error(let message): startRetryVersionTimer() Logger.error("Launch Version Error: \(message)") diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift index ac023a7b..75ccf816 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift @@ -25,7 +25,9 @@ final class LaunchViewModel { enum VersionState { case none case loading - case finish(version: Version) + case checkVersion + case finish + case requiredUpdate case error(message: String) } @@ -60,7 +62,14 @@ final class LaunchViewModel { let version = try await fetchVersionUseCase.execute() Logger.debug("version: \(String(describing: version))") - versionState = .finish(version: version) + versionState = .checkVersion + + versionState = .requiredUpdate +// if version.isNeedForcedUpdate { +// versionState = .requiredUpdate +// } else { +// versionState = .finish +// } } catch { Logger.debug("version error: \(error)") versionState = .error(message: error.localizedDescription) From 36a82c4576b41e9e80e35a9323f18195ba216924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B2=E1=84=8C=E1=85=A5=E1=86=BC=E1=84=8C?= =?UTF-8?q?=E1=85=AE?= Date: Wed, 27 Dec 2023 16:49:28 +0900 Subject: [PATCH 19/47] =?UTF-8?q?[iOS]=20feat:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Launch/LaunchViewModel.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift index 75ccf816..c4dcbb39 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/Launch/LaunchViewModel.swift @@ -63,13 +63,11 @@ final class LaunchViewModel { Logger.debug("version: \(String(describing: version))") versionState = .checkVersion - - versionState = .requiredUpdate -// if version.isNeedForcedUpdate { -// versionState = .requiredUpdate -// } else { -// versionState = .finish -// } + if version.isNeedForcedUpdate { + versionState = .requiredUpdate + } else { + versionState = .finish + } } catch { Logger.debug("version error: \(error)") versionState = .error(message: error.localizedDescription) From d6fde89ff0a5b9c169dba964787414628d465809 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 21:21:56 +0900 Subject: [PATCH 20/47] =?UTF-8?q?[iOS]=20refactor:=20DispatchQueue.main=20?= =?UTF-8?q?->=20RunLoop.main=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageCategory/ManageCategoryViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift index 19004357..f540b04b 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift @@ -97,7 +97,7 @@ final class ManageCategoryViewController: BaseViewController private extension ManageCategoryViewController { func bind() { viewModel.reorderCategoriesState - .receive(on: DispatchQueue.main) + .receive(on: RunLoop.main) .sink { [weak self] state in guard let self else { return } switch state { From bb09e19f457cdbabe963de74652350b6313fc738 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 21:23:18 +0900 Subject: [PATCH 21/47] =?UTF-8?q?[iOS]=20refactor:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageCategory/ManageCategoryViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift index f540b04b..ea61cf1e 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift @@ -103,8 +103,8 @@ private extension ManageCategoryViewController { switch state { case .success: delegate?.doneButtonDidClicked() - case .failed(let message): - showErrorAlert(message: message) + case .failed(_): + showErrorAlert(message: "카테고리 순서 변경에 실패했습니다.") } } From d28cae3971abdc6f145e8cb020bb4aba007ef11d Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Wed, 27 Dec 2023 21:40:18 +0900 Subject: [PATCH 22/47] =?UTF-8?q?[iOS]=20refactor:=20VM=20swap=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageCategory/ManageCategoryViewController.swift | 7 ++----- .../ManageCategory/ManageCategoryViewModel.swift | 5 +++++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift index ea61cf1e..b09ebb61 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift @@ -154,11 +154,8 @@ extension ManageCategoryViewController: UICollectionViewDropDelegate { } private func move(sourceIndexPath: IndexPath, destinationIndexPath: IndexPath) { - let sourceItem = viewModel.categories[sourceIndexPath.item] - - // dataSource 이동 - viewModel.categories.remove(at: sourceIndexPath.item) - viewModel.categories.insert(sourceItem, at: destinationIndexPath.item) + let (sourceIndex, destinationIndex) = (sourceIndexPath.item, destinationIndexPath.item) + viewModel.swap(sourceIndex: sourceIndex, destinationIndex: destinationIndex) } func collectionView( diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewModel.swift index c4a5f862..7177ceff 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewModel.swift @@ -47,6 +47,11 @@ final class ManageCategoryViewModel { categoryDataSource?.update(data: categories) } + func swap(sourceIndex: Int, destinationIndex: Int) { + // dataSource 이동 + categories.swapAt(sourceIndex, destinationIndex) + } + func action(_ action: ManageCategoryViewModelAction) { switch action { case .reorderCategories: From 862dc642fbbec5622b29c38962ca23807c054d3d Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 19:25:53 +0900 Subject: [PATCH 23/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20cell=20=EC=82=AD=EC=A0=9C=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9C=BC=EB=A1=9C=20=EC=9E=84=EC=8B=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cell/ManageCategoryCollectionViewCell.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/Cell/ManageCategoryCollectionViewCell.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/Cell/ManageCategoryCollectionViewCell.swift index a3539268..a0a801b8 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/Cell/ManageCategoryCollectionViewCell.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/Cell/ManageCategoryCollectionViewCell.swift @@ -35,8 +35,11 @@ final class ManageCategoryCollectionViewCell: UICollectionViewCell { private var accessoryButton = { let button = UIButton(type: .system) - button.setImage(.init(systemName: "line.3.horizontal"), for: .normal) - button.tintColor = .lightGray +// button.setImage(.init(systemName: "line.3.horizontal"), for: .normal) +// button.tintColor = .lightGray + + button.setTitle("삭제", for: .normal) + button.setTitleColor(.red, for: .normal) return button }() From a39959edb4fd60a1acfaaf7d4f9b4a9ed16fa2a8 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 19:46:15 +0900 Subject: [PATCH 24/47] =?UTF-8?q?[iOS]=20feat:=20=EC=82=AD=EC=A0=9C=20+=20?= =?UTF-8?q?3line=20=EB=B2=84=ED=8A=BC=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cell/ManageCategoryCollectionViewCell.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/Cell/ManageCategoryCollectionViewCell.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/Cell/ManageCategoryCollectionViewCell.swift index a0a801b8..cea255fe 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/Cell/ManageCategoryCollectionViewCell.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/Cell/ManageCategoryCollectionViewCell.swift @@ -35,11 +35,15 @@ final class ManageCategoryCollectionViewCell: UICollectionViewCell { private var accessoryButton = { let button = UIButton(type: .system) -// button.setImage(.init(systemName: "line.3.horizontal"), for: .normal) -// button.tintColor = .lightGray - button.setTitle("삭제", for: .normal) button.setTitleColor(.red, for: .normal) + button.setImage(.init(systemName: "line.3.horizontal"), for: .normal) + button.tintColor = .lightGray + + button.configuration = .plain() + button.configuration?.imagePlacement = .trailing + button.configuration?.imagePadding = 10 + button.configuration?.contentInsets = .init(top: 0, leading: 0, bottom: 0, trailing: -5) return button }() From 02a891f21df0fa6250d10cda0ed7feec55e92254 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 19:47:36 +0900 Subject: [PATCH 25/47] =?UTF-8?q?[iOS]=20feat:=20=EB=82=B4=EB=B9=84=20"?= =?UTF-8?q?=EC=82=AD=EC=A0=9C"=20=EB=B2=84=ED=8A=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageCategory/ManageCategoryViewController.swift | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift index b09ebb61..a16e01c7 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/ManageCategory/ManageCategoryViewController.swift @@ -45,21 +45,14 @@ final class ManageCategoryViewController: BaseViewController navigationItem.leftBarButtonItem = UIBarButtonItem(title: "취소", style: .plain, target: self, action: #selector(cancelButtonDidClicked)) - let removeButton = UIBarButtonItem(title: "삭제", style: .plain, target: self, action: #selector(removeButtonDidClicked)) - removeButton.tintColor = .red let doneButton = UIBarButtonItem(title: "완료", style: .plain, target: self, action: #selector(doneButtonDidClicked)) - navigationItem.rightBarButtonItems = [doneButton, removeButton] - + navigationItem.rightBarButtonItems = [doneButton] } @objc private func cancelButtonDidClicked() { delegate?.cancelButtonDidClicked() } - @objc private func removeButtonDidClicked() { - print("remove ..") - } - @objc private func doneButtonDidClicked() { viewModel.action(.reorderCategories) } From 789c4285429e72f575ba63f20457f113e8088b69 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:32:45 +0900 Subject: [PATCH 26/47] =?UTF-8?q?[iOS]=20feat:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EC=97=90=EC=84=9C=20"=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC"=20=EC=85=80=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=ED=99=94=EB=A9=B4=20=EC=9D=B4=EB=8F=99=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GroupInfo/GroupInfoCoordinator.swift | 6 ++++++ .../GroupInfo/GroupInfoTableViewDataSource.swift | 12 ++++++++---- .../GroupInfo/GroupInfoViewController.swift | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift index fe891bec..b7b40206 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift @@ -39,4 +39,10 @@ final class GroupInfoCoordinator: Coordinator { groupMemberCoordinator.start(group: group, manageMode: manageMode) childCoordinators.append(groupMemberCoordinator) } + + func moveToBlockUserViewController(group: Group) { + let blockUserCoordinator = BlockUserCoordinator(navigationController, self) + blockUserCoordinator.start(group: group) + childCoordinators.append(blockUserCoordinator) + } } diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoTableViewDataSource.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoTableViewDataSource.swift index 3c9177f5..25ad2beb 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoTableViewDataSource.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoTableViewDataSource.swift @@ -9,7 +9,7 @@ import UIKit final class GroupInfoTableViewDataSource: NSObject, UITableViewDataSource { private var sectionHeaders = ["그룹"] - private var cellTexts = [["그룹원", "탈퇴"]] + private var cellTexts = [["그룹원", "차단 관리", "탈퇴"]] // MARK: Methods func appendLeaderSection() { @@ -17,12 +17,16 @@ final class GroupInfoTableViewDataSource: NSObject, UITableViewDataSource { cellTexts.append(["그룹원 관리"]) } - func isDropCell(indexPath: IndexPath) -> Bool { + func isGroupMemberCell(indexPath: IndexPath) -> Bool { + return indexPath.section == 0 && indexPath.row == 0 + } + + func isBlockCell(indexPath: IndexPath) -> Bool { return indexPath.section == 0 && indexPath.row == 1 } - func isGroupMemberCell(indexPath: IndexPath) -> Bool { - return indexPath.section == 0 && indexPath.row == 0 + func isDropCell(indexPath: IndexPath) -> Bool { + return indexPath.section == 0 && indexPath.row == 2 } func isLeaderCell(indexPath: IndexPath) -> Bool { diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoViewController.swift index 0effac06..1d4651be 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoViewController.swift @@ -66,6 +66,8 @@ extension GroupInfoViewController: UITableViewDelegate { viewModel.action(.dropGroup(groupId: group.id)) } } + } else if dataSource.isBlockCell(indexPath: indexPath) { + coordinator?.moveToBlockUserViewController(group: group) } } } From bf0db0785f682532bab1b69067329e856fc08543 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:33:20 +0900 Subject: [PATCH 27/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20VC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockUser/BlockUserViewController.swift | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewController.swift diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewController.swift new file mode 100644 index 00000000..1ecddb58 --- /dev/null +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewController.swift @@ -0,0 +1,65 @@ +// +// BlockUserViewController.swift +// +// +// Created by Kihyun Lee on 12/29/23. +// + +import UIKit +import Combine +import Core +import Domain + +final class BlockUserViewController: BaseViewController, HiddenTabBarViewController { + + // MARK: - Properties + weak var coordinator: BlockUserCoordinator? + private let viewModel: BlockUserViewModel + private var cancellables: Set = [] + + // MARK: - Init + init(viewModel: BlockUserViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError() + } + + // MARK: - Life Cycles + override func viewDidLoad() { + super.viewDidLoad() + title = "차단 관리" + setupBlockUserDataSource() + viewModel.action(.fetchBlockedUserList) + } + + private func setupBlockUserDataSource() { + layoutView.blockUserCollectionView.delegate = self + let dataSource = BlockUserViewModel.BlockUserDataSource.DataSource( + collectionView: layoutView.blockUserCollectionView, + cellProvider: { [weak self] collectionView, indexPath, item in + guard let self else { return UICollectionViewCell() } + let cell: BlockUserCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) + cell.configure(with: item) + cell.delegate = self + return cell + } + ) + + let diffableDataSource = BlockUserViewModel.BlockUserDataSource(dataSource: dataSource) + viewModel.setupDataSource(diffableDataSource) + } +} + +extension BlockUserViewController: UICollectionViewDelegate { + +} + +extension BlockUserViewController: BlockUserCollectionViewCellDelegate { + func unblockButtonDidClicked() { + print("차단 해제 버튼 눌림!") + // viewModel.action(.unblock) + } +} From 86729814ade85d36140d08519d772179ba256a5a Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:33:33 +0900 Subject: [PATCH 28/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20VM=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockUser/BlockUserViewModel.swift | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewModel.swift diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewModel.swift new file mode 100644 index 00000000..0955092a --- /dev/null +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewModel.swift @@ -0,0 +1,70 @@ +// +// BlockUserViewModel.swift +// +// +// Created by Kihyun Lee on 12/29/23. +// + +import Foundation +import Domain +import Core +import Combine + +final class BlockUserViewModel { + enum BlockUserViewModelAction { + case fetchBlockedUserList + case unblockUser + } + + enum FetchBlockedUserListState { + case success + case failed(message: String) + } + + typealias BlockUserDataSource = ListDiffableDataSource + + // MARK: - Properties + private var blockUserDataSource: BlockUserDataSource? + private var blockedUsers: [BlockedUser] = [] { + didSet { + blockUserDataSource?.update(data: blockedUsers) + } + } + + private let fetchBlockedUserListUseCase: FetchBlockedUserListUseCase + + private(set) var fetchBlockedUserListState = PassthroughSubject() + + init( + fetchBlockedUserListUseCase: FetchBlockedUserListUseCase + ) { + self.fetchBlockedUserListUseCase = fetchBlockedUserListUseCase + } + + func setupDataSource(_ dataSource: BlockUserDataSource) { + self.blockUserDataSource = dataSource + blockUserDataSource?.update(data: []) + } + + func action(_ action: BlockUserViewModelAction) { + switch action { + case .fetchBlockedUserList: + fetchBlockedUserList() + case .unblockUser: + break + } + } + + private func fetchBlockedUserList() { + Task { + do { + let blockedUsers = try await fetchBlockedUserListUseCase.execute() + self.blockedUsers = blockedUsers + fetchBlockedUserListState.send(.success) + } catch { + Logger.debug("blocked users fetch error: \(error)") + fetchBlockedUserListState.send(.failed(message: error.localizedDescription)) + } + } + } +} From 8541a6c141068f1de557a358a591995cf0ff02e1 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:34:12 +0900 Subject: [PATCH 29/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=BD=94=EB=94=94=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockUser/BlockUserCoordinator.swift | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserCoordinator.swift diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserCoordinator.swift new file mode 100644 index 00000000..47bd3ff7 --- /dev/null +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserCoordinator.swift @@ -0,0 +1,37 @@ +// +// BlockUserCoordinator.swift +// +// +// Created by Kihyun Lee on 12/29/23. +// + +import UIKit +import Core +import Domain +import Data + +final class BlockUserCoordinator: Coordinator { + var parentCoordinator: Coordinator? + var childCoordinators: [Coordinator] = [] + var navigationController: UINavigationController + + init( + _ navigationController: UINavigationController, + _ parentCoordinator: Coordinator? + ) { + self.navigationController = navigationController + self.parentCoordinator = parentCoordinator + } + + func start() { } + + func start(group: Group) { + let blockingRepository = BlockingRepository(groupId: group.id) + let blockUserVM = BlockUserViewModel( + fetchBlockedUserListUseCase: .init(blockingRepository: blockingRepository) + ) + let blockUserVC = BlockUserViewController(viewModel: blockUserVM) + blockUserVC.coordinator = self + navigationController.pushViewController(blockUserVC, animated: true) + } +} From c0712ad7c34b3a39afee7b536e304a932d206c46 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:34:38 +0900 Subject: [PATCH 30/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EC=BB=AC=EB=A0=89=EC=85=98=20=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EC=85=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockUser/BlockUserView.swift | 59 ++++++++ .../Cell/BlockUserCollectionViewCell.swift | 126 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserView.swift create mode 100644 iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/Cell/BlockUserCollectionViewCell.swift diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserView.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserView.swift new file mode 100644 index 00000000..dae0a7fe --- /dev/null +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserView.swift @@ -0,0 +1,59 @@ +// +// BlockUserView.swift +// +// +// Created by Kihyun Lee on 12/29/23. +// + +import UIKit + +final class BlockUserView: UIView { + + // MARK: - Views + private(set) lazy var blockUserCollectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeCollectionViewLayout()) + collectionView.backgroundColor = .motiBackground + collectionView.register(with: BlockUserCollectionViewCell.self) + return collectionView + }() + + // MARK: - Init + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupUI() + } + + private func setupUI() { + addSubview(blockUserCollectionView) + blockUserCollectionView.atl + .top(equalTo: safeAreaLayoutGuide.topAnchor) + .bottom(equalTo: bottomAnchor) + .horizontal(equalTo: safeAreaLayoutGuide) + } +} + +private extension BlockUserView { + func makeCollectionViewLayout() -> UICollectionViewLayout { + let itemPadding: CGFloat = 20 + let itemSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(80)) + let itemInset = NSDirectionalEdgeInsets(top: 0, leading: itemPadding, bottom: 0, trailing: itemPadding) + let groupInset = NSDirectionalEdgeInsets(top: itemPadding, leading: 0, bottom: itemPadding, trailing: 0) + let groupEdgeSpacing = NSCollectionLayoutEdgeSpacing(leading: .none, top: .fixed(5), trailing: .none, bottom: .fixed(5)) + + return CompositionalLayoutFactory.makeVerticalCompositionalLayout( + itemSize: itemSize, + itemInset: itemInset, + groupSize: itemSize, + groupInset: groupInset, + groupEdgeSpacing: groupEdgeSpacing + ) + } +} + diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/Cell/BlockUserCollectionViewCell.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/Cell/BlockUserCollectionViewCell.swift new file mode 100644 index 00000000..91f92046 --- /dev/null +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/Cell/BlockUserCollectionViewCell.swift @@ -0,0 +1,126 @@ +// +// BlockUserCollectionViewCell.swift +// +// +// Created by Kihyun Lee on 12/29/23. +// + +import UIKit +import Domain +import Jeongfisher +import Design + +protocol BlockUserCollectionViewCellDelegate: AnyObject { + func unblockButtonDidClicked() +} + +final class BlockUserCollectionViewCell: UICollectionViewCell { + + // MARK: - Properties + private let iconSize: CGFloat = 60 + weak var delegate: BlockUserCollectionViewCellDelegate? + + // MARK: - Views + private lazy var iconImageView = { + let imageView = UIImageView() + imageView.isAccessibilityElement = true + imageView.image = SymbolImage.personProfile + imageView.contentMode = .scaleAspectFit + imageView.clipsToBounds = true + imageView.layer.cornerRadius = iconSize / 2.0 + imageView.tintColor = .primaryDarkGray + return imageView + }() + + private var labelStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = 2 + stackView.distribution = .fillEqually + return stackView + }() + + private let userCodeLabel = { + let label = UILabel() + label.font = .mediumBold + label.numberOfLines = 1 + return label + }() + + private let blockDateLabel = { + let label = UILabel() + label.font = .medium + label.numberOfLines = 1 + return label + }() + + private var unblockButton = { + let button = UIButton(type: .system) + button.setTitle("차단 해제", for: .normal) + button.setTitleColor(.red, for: .normal) + return button + }() + + // MARK: - Init + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupUI() + } + + // MARK: - Methods + func configure(with blockedUser: BlockedUser) { + if let url = blockedUser.user.avatarURL { + iconImageView.jk.setImage(with: url, downsamplingScale: 1.5) + } + userCodeLabel.text = "@" + blockedUser.user.code + guard let blockDate = blockedUser.createdAt else { return } + blockDateLabel.text = blockDate.convertStringYYYY년_MM월_dd일() + " 차단" + } + + func cancelDownloadImage() { + iconImageView.jk.cancelDownloadImage() + } +} + +// MARK: - Setup +private extension BlockUserCollectionViewCell { + func setupUI() { + self.layer.cornerRadius = CornerRadius.big + self.layer.borderWidth = 1 + self.layer.borderColor = UIColor.primaryDarkGray.cgColor + + setupIconImageView() + setupStackView() + setupGradeButton() + } + + func setupIconImageView() { + addSubview(iconImageView) + iconImageView.atl + .size(width: iconSize, height: iconSize) + .centerY(equalTo: contentView.safeAreaLayoutGuide.centerYAnchor) + .left(equalTo: contentView.safeAreaLayoutGuide.leftAnchor, constant: 20) + } + + func setupStackView() { + labelStackView.addArrangedSubview(userCodeLabel) + labelStackView.addArrangedSubview(blockDateLabel) + + addSubview(labelStackView) + labelStackView.atl + .centerY(equalTo: iconImageView.centerYAnchor) + .left(equalTo: iconImageView.rightAnchor, constant: 20) + } + + func setupGradeButton() { + addSubview(unblockButton) + unblockButton.atl + .centerY(equalTo: iconImageView.centerYAnchor) + .right(equalTo: contentView.safeAreaLayoutGuide.rightAnchor, constant: -15) + } +} From 20c1c81748504e5468cf94d5d4f9df37abb8443b Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:35:09 +0900 Subject: [PATCH 31/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20UseCase=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UseCase/FetchBlockedUserListUseCase.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 iOS/moti/moti/Domain/Sources/Domain/UseCase/FetchBlockedUserListUseCase.swift diff --git a/iOS/moti/moti/Domain/Sources/Domain/UseCase/FetchBlockedUserListUseCase.swift b/iOS/moti/moti/Domain/Sources/Domain/UseCase/FetchBlockedUserListUseCase.swift new file mode 100644 index 00000000..87cccd3f --- /dev/null +++ b/iOS/moti/moti/Domain/Sources/Domain/UseCase/FetchBlockedUserListUseCase.swift @@ -0,0 +1,21 @@ +// +// FetchBlockedUserListUseCase.swift +// +// +// Created by Kihyun Lee on 12/29/23. +// + +import Foundation + +public struct FetchBlockedUserListUseCase { + private let blockingRepository: BlockingRepositoryProtocol + + public init(blockingRepository: BlockingRepositoryProtocol) { + self.blockingRepository = blockingRepository + } + + public func execute() async throws -> [BlockedUser] { + return try await blockingRepository.fetchBlockedUserList() + } +} + From e5705f74f7bb650956c47de50ed2c2f76069b23a Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:35:33 +0900 Subject: [PATCH 32/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8?= =?UTF-8?q?=EB=90=9C=20=EC=9C=A0=EC=A0=80=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Domain/Entity/BlockedUser.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 iOS/moti/moti/Domain/Sources/Domain/Entity/BlockedUser.swift diff --git a/iOS/moti/moti/Domain/Sources/Domain/Entity/BlockedUser.swift b/iOS/moti/moti/Domain/Sources/Domain/Entity/BlockedUser.swift new file mode 100644 index 00000000..bb22483d --- /dev/null +++ b/iOS/moti/moti/Domain/Sources/Domain/Entity/BlockedUser.swift @@ -0,0 +1,21 @@ +// +// BlockedUser.swift +// +// +// Created by Kihyun Lee on 12/29/23. +// + +import Foundation + +public struct BlockedUser: Hashable { + public let user: User + public var createdAt: Date? + + public init( + user: User, + createdAt: Date? = nil + ) { + self.user = user + self.createdAt = createdAt + } +} From 1309bf26021b61f7904b64a37e443988a7f2dfe1 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:36:15 +0900 Subject: [PATCH 33/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EC=B6=94=EA=B0=80=20in=20BlockingRepository?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moti/Data/Sources/Repository/BlockingRepository.swift | 8 ++++++++ .../RepositoryProtocol/BlockingRepositoryProtocol.swift | 1 + 2 files changed, 9 insertions(+) diff --git a/iOS/moti/moti/Data/Sources/Repository/BlockingRepository.swift b/iOS/moti/moti/Data/Sources/Repository/BlockingRepository.swift index 29dbe1b5..21390faf 100644 --- a/iOS/moti/moti/Data/Sources/Repository/BlockingRepository.swift +++ b/iOS/moti/moti/Data/Sources/Repository/BlockingRepository.swift @@ -28,4 +28,12 @@ public struct BlockingRepository: BlockingRepositoryProtocol { let responseDTO = try await provider.request(with: endpoint, type: BlockingDTO.self) return responseDTO.success ?? false } + + public func fetchBlockedUserList() async throws -> [BlockedUser] { + let endpoint = MotiAPI.fetchBlockedUserList + let responseDTO = try await provider.request(with: endpoint, type: FetchBlockedUserListResponseDTO.self) + + guard let blockedUserListDTO = responseDTO.data?.data else { throw NetworkError.decode } + return blockedUserListDTO.map { BlockedUser(dto: $0) } + } } diff --git a/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/BlockingRepositoryProtocol.swift b/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/BlockingRepositoryProtocol.swift index d43d4a0e..ab7c2d37 100644 --- a/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/BlockingRepositoryProtocol.swift +++ b/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/BlockingRepositoryProtocol.swift @@ -10,4 +10,5 @@ import Foundation public protocol BlockingRepositoryProtocol { func blockingUser(userCode: String) async throws -> Bool func blockingAchievement(achievementId: Int) async throws -> Bool + func fetchBlockedUserList() async throws -> [BlockedUser] } From c2eed8272a113257f0733a937561d0b70b90d06b Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:36:33 +0900 Subject: [PATCH 34/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20DTO=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Network/DTO/FetchBlockedUserListDTO.swift | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchBlockedUserListDTO.swift diff --git a/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchBlockedUserListDTO.swift b/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchBlockedUserListDTO.swift new file mode 100644 index 00000000..617dc5fe --- /dev/null +++ b/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchBlockedUserListDTO.swift @@ -0,0 +1,34 @@ +// +// FetchBlockedUserListDTO.swift +// +// +// Created by Kihyun Lee on 12/29/23. +// + +import Foundation +import Domain + +struct FetchBlockedUserListResponseDTO: ResponseDataDTO { + let success: Bool? + let message: String? + let data: FetchBlockedUserListDTO? +} + +struct FetchBlockedUserListDTO: Codable { + let data: [BlockedUserDTO]? +} + +struct BlockedUserDTO: Codable { + let userCode: String + let avatarUrl: URL? + let createdAt: Date? +} + +extension BlockedUser { + init(dto: BlockedUserDTO) { + self.init( + user: .init(code: dto.userCode, avatarURL: dto.avatarUrl), + createdAt: dto.createdAt + ) + } +} From 375f06385e76927e75651e23ba25c2b6adddf413 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Fri, 29 Dec 2023 23:36:47 +0900 Subject: [PATCH 35/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20API=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iOS/moti/moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift b/iOS/moti/moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift index bb756e31..38fb0da6 100644 --- a/iOS/moti/moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift +++ b/iOS/moti/moti/Data/Sources/Data/Network/Endpoint/MotiAPI.swift @@ -44,6 +44,7 @@ enum MotiAPI: EndpointProtocol { // 차단 case blockingUser(userCode: String) case blockingAchievement(achievementId: Int, groupId: Int) + case fetchBlockedUserList // 이모지 case fetchEmojis(achievementId: Int, groupId: Int) case toggleEmoji(achievementId: Int, groupId: Int, emojiId: String) @@ -114,6 +115,8 @@ extension MotiAPI { return "/groups/\(groupId)/participation" case .joinGroup: return "/groups/participation" + case .fetchBlockedUserList: + return "/users/reject" } } @@ -152,6 +155,7 @@ extension MotiAPI { case .toggleEmoji: return .post case .dropGroup: return .delete case .joinGroup: return .post + case .fetchBlockedUserList: return .get } } From ec8596217dae13914e863cecf58eeaf8f577c14e Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 19:26:35 +0900 Subject: [PATCH 36/47] [iOS] refactor: BlockUser -> BlockedUserList --- .../BlockedUserListCoordinator.swift} | 6 +++--- .../BlockedUserListView.swift} | 6 +++--- .../BlockedUserListViewController.swift} | 18 +++++++++--------- .../BlockedUserListViewModel.swift} | 2 +- .../BlockedUserListCollectionViewCell.swift} | 8 ++++---- .../GroupInfo/GroupInfoCoordinator.swift | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) rename iOS/moti/moti/Presentation/Sources/Presentation/{BlockUser/BlockUserCoordinator.swift => BlockedUserList/BlockedUserListCoordinator.swift} (82%) rename iOS/moti/moti/Presentation/Sources/Presentation/{BlockUser/BlockUserView.swift => BlockedUserList/BlockedUserListView.swift} (91%) rename iOS/moti/moti/Presentation/Sources/Presentation/{BlockUser/BlockUserViewController.swift => BlockedUserList/BlockedUserListViewController.swift} (64%) rename iOS/moti/moti/Presentation/Sources/Presentation/{BlockUser/BlockUserViewModel.swift => BlockedUserList/BlockedUserListViewModel.swift} (97%) rename iOS/moti/moti/Presentation/Sources/Presentation/{BlockUser/Cell/BlockUserCollectionViewCell.swift => BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift} (93%) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListCoordinator.swift similarity index 82% rename from iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserCoordinator.swift rename to iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListCoordinator.swift index 47bd3ff7..aa83169e 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserCoordinator.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListCoordinator.swift @@ -10,7 +10,7 @@ import Core import Domain import Data -final class BlockUserCoordinator: Coordinator { +final class BlockedUserListCoordinator: Coordinator { var parentCoordinator: Coordinator? var childCoordinators: [Coordinator] = [] var navigationController: UINavigationController @@ -27,10 +27,10 @@ final class BlockUserCoordinator: Coordinator { func start(group: Group) { let blockingRepository = BlockingRepository(groupId: group.id) - let blockUserVM = BlockUserViewModel( + let blockUserVM = BlockedUserListViewModel( fetchBlockedUserListUseCase: .init(blockingRepository: blockingRepository) ) - let blockUserVC = BlockUserViewController(viewModel: blockUserVM) + let blockUserVC = BlockedUserListViewController(viewModel: blockUserVM) blockUserVC.coordinator = self navigationController.pushViewController(blockUserVC, animated: true) } diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserView.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift similarity index 91% rename from iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserView.swift rename to iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift index dae0a7fe..6186a4ef 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserView.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift @@ -7,13 +7,13 @@ import UIKit -final class BlockUserView: UIView { +final class BlockedUserListView: UIView { // MARK: - Views private(set) lazy var blockUserCollectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeCollectionViewLayout()) collectionView.backgroundColor = .motiBackground - collectionView.register(with: BlockUserCollectionViewCell.self) + collectionView.register(with: BlockedUserListCollectionViewCell.self) return collectionView }() @@ -37,7 +37,7 @@ final class BlockUserView: UIView { } } -private extension BlockUserView { +private extension BlockedUserListView { func makeCollectionViewLayout() -> UICollectionViewLayout { let itemPadding: CGFloat = 20 let itemSize = NSCollectionLayoutSize( diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift similarity index 64% rename from iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewController.swift rename to iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift index 1ecddb58..d0f94416 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift @@ -10,15 +10,15 @@ import Combine import Core import Domain -final class BlockUserViewController: BaseViewController, HiddenTabBarViewController { +final class BlockedUserListViewController: BaseViewController, HiddenTabBarViewController { // MARK: - Properties - weak var coordinator: BlockUserCoordinator? - private let viewModel: BlockUserViewModel + weak var coordinator: BlockedUserListCoordinator? + private let viewModel: BlockedUserListViewModel private var cancellables: Set = [] // MARK: - Init - init(viewModel: BlockUserViewModel) { + init(viewModel: BlockedUserListViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } @@ -37,27 +37,27 @@ final class BlockUserViewController: BaseViewController, HiddenTa private func setupBlockUserDataSource() { layoutView.blockUserCollectionView.delegate = self - let dataSource = BlockUserViewModel.BlockUserDataSource.DataSource( + let dataSource = BlockedUserListViewModel.BlockUserDataSource.DataSource( collectionView: layoutView.blockUserCollectionView, cellProvider: { [weak self] collectionView, indexPath, item in guard let self else { return UICollectionViewCell() } - let cell: BlockUserCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) + let cell: BlockedUserListCollectionViewCell = collectionView.dequeueReusableCell(for: indexPath) cell.configure(with: item) cell.delegate = self return cell } ) - let diffableDataSource = BlockUserViewModel.BlockUserDataSource(dataSource: dataSource) + let diffableDataSource = BlockedUserListViewModel.BlockUserDataSource(dataSource: dataSource) viewModel.setupDataSource(diffableDataSource) } } -extension BlockUserViewController: UICollectionViewDelegate { +extension BlockedUserListViewController: UICollectionViewDelegate { } -extension BlockUserViewController: BlockUserCollectionViewCellDelegate { +extension BlockedUserListViewController: BlockedUserListCollectionViewCellDelegate { func unblockButtonDidClicked() { print("차단 해제 버튼 눌림!") // viewModel.action(.unblock) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift similarity index 97% rename from iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewModel.swift rename to iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift index 0955092a..aacc8593 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/BlockUserViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift @@ -10,7 +10,7 @@ import Domain import Core import Combine -final class BlockUserViewModel { +final class BlockedUserListViewModel { enum BlockUserViewModelAction { case fetchBlockedUserList case unblockUser diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/Cell/BlockUserCollectionViewCell.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift similarity index 93% rename from iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/Cell/BlockUserCollectionViewCell.swift rename to iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift index 91f92046..5c902df7 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockUser/Cell/BlockUserCollectionViewCell.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift @@ -10,15 +10,15 @@ import Domain import Jeongfisher import Design -protocol BlockUserCollectionViewCellDelegate: AnyObject { +protocol BlockedUserListCollectionViewCellDelegate: AnyObject { func unblockButtonDidClicked() } -final class BlockUserCollectionViewCell: UICollectionViewCell { +final class BlockedUserListCollectionViewCell: UICollectionViewCell { // MARK: - Properties private let iconSize: CGFloat = 60 - weak var delegate: BlockUserCollectionViewCellDelegate? + weak var delegate: BlockedUserListCollectionViewCellDelegate? // MARK: - Views private lazy var iconImageView = { @@ -88,7 +88,7 @@ final class BlockUserCollectionViewCell: UICollectionViewCell { } // MARK: - Setup -private extension BlockUserCollectionViewCell { +private extension BlockedUserListCollectionViewCell { func setupUI() { self.layer.cornerRadius = CornerRadius.big self.layer.borderWidth = 1 diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift index b7b40206..70909b6e 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift @@ -41,7 +41,7 @@ final class GroupInfoCoordinator: Coordinator { } func moveToBlockUserViewController(group: Group) { - let blockUserCoordinator = BlockUserCoordinator(navigationController, self) + let blockUserCoordinator = BlockedUserListCoordinator(navigationController, self) blockUserCoordinator.start(group: group) childCoordinators.append(blockUserCoordinator) } From 3898a87d38370c9c39d7c804d40d64a9e6e59e23 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 19:29:17 +0900 Subject: [PATCH 37/47] [iOS] refactor: BlockUser -> BlockedUserList --- .../BlockedUserList/BlockedUserListView.swift | 6 +++--- .../BlockedUserListViewController.swift | 14 +++++++------- .../BlockedUserList/BlockedUserListViewModel.swift | 6 +++--- .../Cell/BlockedUserListCollectionViewCell.swift | 2 +- .../GroupInfo/GroupInfoCoordinator.swift | 8 ++++---- .../GroupInfo/GroupInfoViewController.swift | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift index 6186a4ef..1534c9b6 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift @@ -10,7 +10,7 @@ import UIKit final class BlockedUserListView: UIView { // MARK: - Views - private(set) lazy var blockUserCollectionView: UICollectionView = { + private(set) lazy var blockedUserListCollectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeCollectionViewLayout()) collectionView.backgroundColor = .motiBackground collectionView.register(with: BlockedUserListCollectionViewCell.self) @@ -29,8 +29,8 @@ final class BlockedUserListView: UIView { } private func setupUI() { - addSubview(blockUserCollectionView) - blockUserCollectionView.atl + addSubview(blockedUserListCollectionView) + blockedUserListCollectionView.atl .top(equalTo: safeAreaLayoutGuide.topAnchor) .bottom(equalTo: bottomAnchor) .horizontal(equalTo: safeAreaLayoutGuide) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift index d0f94416..6cd88310 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift @@ -1,5 +1,5 @@ // -// BlockUserViewController.swift +// BlockedUserListViewController.swift // // // Created by Kihyun Lee on 12/29/23. @@ -31,14 +31,14 @@ final class BlockedUserListViewController: BaseViewController + typealias BlockedUserListDataSource = ListDiffableDataSource // MARK: - Properties - private var blockUserDataSource: BlockUserDataSource? + private var blockUserDataSource: BlockedUserListDataSource? private var blockedUsers: [BlockedUser] = [] { didSet { blockUserDataSource?.update(data: blockedUsers) @@ -41,7 +41,7 @@ final class BlockedUserListViewModel { self.fetchBlockedUserListUseCase = fetchBlockedUserListUseCase } - func setupDataSource(_ dataSource: BlockUserDataSource) { + func setupDataSource(_ dataSource: BlockedUserListDataSource) { self.blockUserDataSource = dataSource blockUserDataSource?.update(data: []) } diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift index 5c902df7..cf0764e3 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift @@ -1,5 +1,5 @@ // -// BlockUserCollectionViewCell.swift +// BlockedUserListCollectionViewCell.swift // // // Created by Kihyun Lee on 12/29/23. diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift index 70909b6e..d526b425 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoCoordinator.swift @@ -40,9 +40,9 @@ final class GroupInfoCoordinator: Coordinator { childCoordinators.append(groupMemberCoordinator) } - func moveToBlockUserViewController(group: Group) { - let blockUserCoordinator = BlockedUserListCoordinator(navigationController, self) - blockUserCoordinator.start(group: group) - childCoordinators.append(blockUserCoordinator) + func moveToBlockedUserListViewController(group: Group) { + let blockedUserListCoordinator = BlockedUserListCoordinator(navigationController, self) + blockedUserListCoordinator.start(group: group) + childCoordinators.append(blockedUserListCoordinator) } } diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoViewController.swift index 1d4651be..b031ede9 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/GroupInfo/GroupInfoViewController.swift @@ -67,7 +67,7 @@ extension GroupInfoViewController: UITableViewDelegate { } } } else if dataSource.isBlockCell(indexPath: indexPath) { - coordinator?.moveToBlockUserViewController(group: group) + coordinator?.moveToBlockedUserListViewController(group: group) } } } From be0e93d4f57b8d0eed47c12f2cac44f314ff28bf Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 19:46:30 +0900 Subject: [PATCH 38/47] [iOS] refactor: blockDateLabel -> blockedDateLabel --- .../Cell/BlockedUserListCollectionViewCell.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift index cf0764e3..f44b5fc7 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift @@ -47,7 +47,7 @@ final class BlockedUserListCollectionViewCell: UICollectionViewCell { return label }() - private let blockDateLabel = { + private let blockedDateLabel = { let label = UILabel() label.font = .medium label.numberOfLines = 1 @@ -79,7 +79,7 @@ final class BlockedUserListCollectionViewCell: UICollectionViewCell { } userCodeLabel.text = "@" + blockedUser.user.code guard let blockDate = blockedUser.createdAt else { return } - blockDateLabel.text = blockDate.convertStringYYYY년_MM월_dd일() + " 차단" + blockedDateLabel.text = blockDate.convertStringYYYY년_MM월_dd일() + " 차단" } func cancelDownloadImage() { @@ -109,7 +109,7 @@ private extension BlockedUserListCollectionViewCell { func setupStackView() { labelStackView.addArrangedSubview(userCodeLabel) - labelStackView.addArrangedSubview(blockDateLabel) + labelStackView.addArrangedSubview(blockedDateLabel) addSubview(labelStackView) labelStackView.atl From 8fe864b80828f238addd1175fab0eebf36bea3aa Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 20:09:57 +0900 Subject: [PATCH 39/47] =?UTF-8?q?[iOS]=20refactor:=20BlockedUser=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - User 엔티티에 blockedDate 속성 추가 --- .../Network/DTO/FetchBlockedUserListDTO.swift | 7 ++++--- .../Network/DTO/FetchGroupMemberListDTO.swift | 2 +- .../Sources/Data/Network/DTO/UserDTO.swift | 2 +- .../Repository/BlockingRepository.swift | 4 ++-- .../Sources/Domain/Entity/Achievement.swift | 4 ++-- .../Sources/Domain/Entity/BlockedUser.swift | 21 ------------------- .../Domain/Sources/Domain/Entity/User.swift | 5 ++++- .../BlockingRepositoryProtocol.swift | 2 +- .../UseCase/FetchBlockedUserListUseCase.swift | 2 +- .../BlockedUserListViewModel.swift | 4 ++-- .../BlockedUserListCollectionViewCell.swift | 10 ++++----- 11 files changed, 23 insertions(+), 40 deletions(-) delete mode 100644 iOS/moti/moti/Domain/Sources/Domain/Entity/BlockedUser.swift diff --git a/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchBlockedUserListDTO.swift b/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchBlockedUserListDTO.swift index 617dc5fe..63b3035a 100644 --- a/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchBlockedUserListDTO.swift +++ b/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchBlockedUserListDTO.swift @@ -24,11 +24,12 @@ struct BlockedUserDTO: Codable { let createdAt: Date? } -extension BlockedUser { +extension User { init(dto: BlockedUserDTO) { self.init( - user: .init(code: dto.userCode, avatarURL: dto.avatarUrl), - createdAt: dto.createdAt + code: dto.userCode, + avatarURL: dto.avatarUrl, + blockedDate: dto.createdAt ) } } diff --git a/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift b/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift index e7af6089..4186aa23 100644 --- a/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift +++ b/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift @@ -28,7 +28,7 @@ struct GroupMemberDTO: Codable { extension GroupMember { init(dto: GroupMemberDTO) { self.init( - user: .init(code: dto.userCode, avatarURL: dto.avatarUrl), + user: .init(code: dto.userCode, avatarURL: dto.avatarUrl, blockedDate: nil), lastChallenged: dto.lastChallenged, grade: GroupGrade(rawValue: dto.grade ?? "") ?? .participant ) diff --git a/iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift b/iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift index 1e2eeace..2ae7647d 100644 --- a/iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift +++ b/iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift @@ -30,6 +30,6 @@ extension UserToken { extension User { init(dto: UserDTO) { - self.init(code: dto.userCode ?? "", avatarURL: dto.avatarUrl) + self.init(code: dto.userCode ?? "", avatarURL: dto.avatarUrl, blockedDate: nil) } } diff --git a/iOS/moti/moti/Data/Sources/Repository/BlockingRepository.swift b/iOS/moti/moti/Data/Sources/Repository/BlockingRepository.swift index 21390faf..6edab840 100644 --- a/iOS/moti/moti/Data/Sources/Repository/BlockingRepository.swift +++ b/iOS/moti/moti/Data/Sources/Repository/BlockingRepository.swift @@ -29,11 +29,11 @@ public struct BlockingRepository: BlockingRepositoryProtocol { return responseDTO.success ?? false } - public func fetchBlockedUserList() async throws -> [BlockedUser] { + public func fetchBlockedUserList() async throws -> [User] { let endpoint = MotiAPI.fetchBlockedUserList let responseDTO = try await provider.request(with: endpoint, type: FetchBlockedUserListResponseDTO.self) guard let blockedUserListDTO = responseDTO.data?.data else { throw NetworkError.decode } - return blockedUserListDTO.map { BlockedUser(dto: $0) } + return blockedUserListDTO.map { User(dto: $0) } } } diff --git a/iOS/moti/moti/Domain/Sources/Domain/Entity/Achievement.swift b/iOS/moti/moti/Domain/Sources/Domain/Entity/Achievement.swift index 936d3e9e..960dc0f3 100644 --- a/iOS/moti/moti/Domain/Sources/Domain/Entity/Achievement.swift +++ b/iOS/moti/moti/Domain/Sources/Domain/Entity/Achievement.swift @@ -51,7 +51,7 @@ public struct Achievement: Hashable { } else { let myUserId = UserDefaults.standard.readString(key: .myUserCode) ?? "" let myAvatarUrlString = UserDefaults.standard.readString(key: .myAvatarUrlString) ?? "" - self.user = User(code: myUserId, avatarURL: URL(string: myAvatarUrlString)) + self.user = User(code: myUserId, avatarURL: URL(string: myAvatarUrlString), blockedDate: nil) self.userCode = myUserId } } @@ -76,7 +76,7 @@ public struct Achievement: Hashable { } else { let myUserId = UserDefaults.standard.readString(key: .myUserCode) ?? "" let myAvatarUrlString = UserDefaults.standard.readString(key: .myAvatarUrlString) ?? "" - self.user = User(code: myUserId, avatarURL: URL(string: myAvatarUrlString)) + self.user = User(code: myUserId, avatarURL: URL(string: myAvatarUrlString), blockedDate: nil) self.userCode = myUserId } } diff --git a/iOS/moti/moti/Domain/Sources/Domain/Entity/BlockedUser.swift b/iOS/moti/moti/Domain/Sources/Domain/Entity/BlockedUser.swift deleted file mode 100644 index bb22483d..00000000 --- a/iOS/moti/moti/Domain/Sources/Domain/Entity/BlockedUser.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// BlockedUser.swift -// -// -// Created by Kihyun Lee on 12/29/23. -// - -import Foundation - -public struct BlockedUser: Hashable { - public let user: User - public var createdAt: Date? - - public init( - user: User, - createdAt: Date? = nil - ) { - self.user = user - self.createdAt = createdAt - } -} diff --git a/iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift b/iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift index ebb9ea41..0eabcd03 100644 --- a/iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift +++ b/iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift @@ -22,14 +22,17 @@ public struct UserToken: Equatable { public struct User: Hashable { public let code: String public let avatarURL: URL? + public let blockedDate: Date? - public init(code: String, avatarURL: URL?) { + public init(code: String, avatarURL: URL?, blockedDate: Date?) { self.code = code self.avatarURL = avatarURL + self.blockedDate = blockedDate } public init() { self.code = "" self.avatarURL = nil + self.blockedDate = nil } } diff --git a/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/BlockingRepositoryProtocol.swift b/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/BlockingRepositoryProtocol.swift index ab7c2d37..310ee26b 100644 --- a/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/BlockingRepositoryProtocol.swift +++ b/iOS/moti/moti/Domain/Sources/Domain/RepositoryProtocol/BlockingRepositoryProtocol.swift @@ -10,5 +10,5 @@ import Foundation public protocol BlockingRepositoryProtocol { func blockingUser(userCode: String) async throws -> Bool func blockingAchievement(achievementId: Int) async throws -> Bool - func fetchBlockedUserList() async throws -> [BlockedUser] + func fetchBlockedUserList() async throws -> [User] } diff --git a/iOS/moti/moti/Domain/Sources/Domain/UseCase/FetchBlockedUserListUseCase.swift b/iOS/moti/moti/Domain/Sources/Domain/UseCase/FetchBlockedUserListUseCase.swift index 87cccd3f..71122059 100644 --- a/iOS/moti/moti/Domain/Sources/Domain/UseCase/FetchBlockedUserListUseCase.swift +++ b/iOS/moti/moti/Domain/Sources/Domain/UseCase/FetchBlockedUserListUseCase.swift @@ -14,7 +14,7 @@ public struct FetchBlockedUserListUseCase { self.blockingRepository = blockingRepository } - public func execute() async throws -> [BlockedUser] { + public func execute() async throws -> [User] { return try await blockingRepository.fetchBlockedUserList() } } diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift index e2880b82..845142e8 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift @@ -21,11 +21,11 @@ final class BlockedUserListViewModel { case failed(message: String) } - typealias BlockedUserListDataSource = ListDiffableDataSource + typealias BlockedUserListDataSource = ListDiffableDataSource // MARK: - Properties private var blockUserDataSource: BlockedUserListDataSource? - private var blockedUsers: [BlockedUser] = [] { + private var blockedUsers: [User] = [] { didSet { blockUserDataSource?.update(data: blockedUsers) } diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift index f44b5fc7..516f43d5 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift @@ -73,13 +73,13 @@ final class BlockedUserListCollectionViewCell: UICollectionViewCell { } // MARK: - Methods - func configure(with blockedUser: BlockedUser) { - if let url = blockedUser.user.avatarURL { + func configure(with user: User) { + if let url = user.avatarURL { iconImageView.jk.setImage(with: url, downsamplingScale: 1.5) } - userCodeLabel.text = "@" + blockedUser.user.code - guard let blockDate = blockedUser.createdAt else { return } - blockedDateLabel.text = blockDate.convertStringYYYY년_MM월_dd일() + " 차단" + userCodeLabel.text = "@" + user.code + guard let blockedDate = user.blockedDate else { return } + blockedDateLabel.text = blockedDate.convertStringYYYY년_MM월_dd일() + " 차단" } func cancelDownloadImage() { From a263e6557a5e4b3da14c7e47ca2353ee29062166 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 20:12:37 +0900 Subject: [PATCH 40/47] =?UTF-8?q?[iOS]=20refactor:=20print=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockedUserList/BlockedUserListViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift index 6cd88310..f91ae238 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift @@ -59,7 +59,7 @@ extension BlockedUserListViewController: UICollectionViewDelegate { extension BlockedUserListViewController: BlockedUserListCollectionViewCellDelegate { func unblockButtonDidClicked() { - print("차단 해제 버튼 눌림!") + Logger.debug("차단 해제 버튼 눌림!") // viewModel.action(.unblock) } } From 1cdd9f1cbdfd0a990041ed5e84e9fd6545b90c77 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 20:46:28 +0900 Subject: [PATCH 41/47] =?UTF-8?q?[iOS]=20feat:=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20state=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9=20(=EB=A1=9C=EB=94=A9=20=EC=9D=B8?= =?UTF-8?q?=EB=94=94=EC=BC=80=EC=9D=B4=ED=84=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockedUserListViewController.swift | 21 ++++++++++++++++++- .../BlockedUserListViewModel.swift | 2 ++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift index f91ae238..5446eeb8 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift @@ -10,7 +10,7 @@ import Combine import Core import Domain -final class BlockedUserListViewController: BaseViewController, HiddenTabBarViewController { +final class BlockedUserListViewController: BaseViewController, LoadingIndicator, HiddenTabBarViewController { // MARK: - Properties weak var coordinator: BlockedUserListCoordinator? @@ -31,10 +31,29 @@ final class BlockedUserListViewController: BaseViewController Date: Sat, 30 Dec 2023 20:47:32 +0900 Subject: [PATCH 42/47] =?UTF-8?q?[iOS]=20fix:=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift index 516f43d5..58673fa8 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/Cell/BlockedUserListCollectionViewCell.swift @@ -23,7 +23,6 @@ final class BlockedUserListCollectionViewCell: UICollectionViewCell { // MARK: - Views private lazy var iconImageView = { let imageView = UIImageView() - imageView.isAccessibilityElement = true imageView.image = SymbolImage.personProfile imageView.contentMode = .scaleAspectFit imageView.clipsToBounds = true From 73f6ad7ea8aad87cc11e08a8383647f11de1ff09 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 20:50:34 +0900 Subject: [PATCH 43/47] =?UTF-8?q?[iOS]=20feat:=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EB=A1=9C=EB=94=A9=20=EC=B7=A8=EC=86=8C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockedUserList/BlockedUserListViewController.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift index 5446eeb8..840d3779 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift @@ -73,7 +73,10 @@ final class BlockedUserListViewController: BaseViewController Date: Sat, 30 Dec 2023 21:15:10 +0900 Subject: [PATCH 44/47] =?UTF-8?q?[iOS]=20refactor:=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockedUserList/BlockedUserListViewModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift index 95300a53..57046a58 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewModel.swift @@ -11,7 +11,7 @@ import Core import Combine final class BlockedUserListViewModel { - enum BlockUserViewModelAction { + enum BlockedUserListViewModelAction { case fetchBlockedUserList case unblockUser } @@ -47,7 +47,7 @@ final class BlockedUserListViewModel { blockUserDataSource?.update(data: []) } - func action(_ action: BlockUserViewModelAction) { + func action(_ action: BlockedUserListViewModelAction) { switch action { case .fetchBlockedUserList: fetchBlockedUserList() From 67bd588d99586f817a0c813b11bd2f122ff77699 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 21:15:56 +0900 Subject: [PATCH 45/47] =?UTF-8?q?[iOS]=20refactor:=20User=20blockedDate=20?= =?UTF-8?q?init=20=EC=83=88=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift | 2 +- iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift | 2 +- .../moti/Domain/Sources/Domain/Entity/Achievement.swift | 4 ++-- iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift | 6 ++++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift b/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift index 4186aa23..e7af6089 100644 --- a/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift +++ b/iOS/moti/moti/Data/Sources/Data/Network/DTO/FetchGroupMemberListDTO.swift @@ -28,7 +28,7 @@ struct GroupMemberDTO: Codable { extension GroupMember { init(dto: GroupMemberDTO) { self.init( - user: .init(code: dto.userCode, avatarURL: dto.avatarUrl, blockedDate: nil), + user: .init(code: dto.userCode, avatarURL: dto.avatarUrl), lastChallenged: dto.lastChallenged, grade: GroupGrade(rawValue: dto.grade ?? "") ?? .participant ) diff --git a/iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift b/iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift index 2ae7647d..1e2eeace 100644 --- a/iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift +++ b/iOS/moti/moti/Data/Sources/Data/Network/DTO/UserDTO.swift @@ -30,6 +30,6 @@ extension UserToken { extension User { init(dto: UserDTO) { - self.init(code: dto.userCode ?? "", avatarURL: dto.avatarUrl, blockedDate: nil) + self.init(code: dto.userCode ?? "", avatarURL: dto.avatarUrl) } } diff --git a/iOS/moti/moti/Domain/Sources/Domain/Entity/Achievement.swift b/iOS/moti/moti/Domain/Sources/Domain/Entity/Achievement.swift index 960dc0f3..936d3e9e 100644 --- a/iOS/moti/moti/Domain/Sources/Domain/Entity/Achievement.swift +++ b/iOS/moti/moti/Domain/Sources/Domain/Entity/Achievement.swift @@ -51,7 +51,7 @@ public struct Achievement: Hashable { } else { let myUserId = UserDefaults.standard.readString(key: .myUserCode) ?? "" let myAvatarUrlString = UserDefaults.standard.readString(key: .myAvatarUrlString) ?? "" - self.user = User(code: myUserId, avatarURL: URL(string: myAvatarUrlString), blockedDate: nil) + self.user = User(code: myUserId, avatarURL: URL(string: myAvatarUrlString)) self.userCode = myUserId } } @@ -76,7 +76,7 @@ public struct Achievement: Hashable { } else { let myUserId = UserDefaults.standard.readString(key: .myUserCode) ?? "" let myAvatarUrlString = UserDefaults.standard.readString(key: .myAvatarUrlString) ?? "" - self.user = User(code: myUserId, avatarURL: URL(string: myAvatarUrlString), blockedDate: nil) + self.user = User(code: myUserId, avatarURL: URL(string: myAvatarUrlString)) self.userCode = myUserId } } diff --git a/iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift b/iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift index 0eabcd03..1c4b6d06 100644 --- a/iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift +++ b/iOS/moti/moti/Domain/Sources/Domain/Entity/User.swift @@ -24,6 +24,12 @@ public struct User: Hashable { public let avatarURL: URL? public let blockedDate: Date? + public init(code: String, avatarURL: URL?) { + self.code = code + self.avatarURL = avatarURL + self.blockedDate = nil + } + public init(code: String, avatarURL: URL?, blockedDate: Date?) { self.code = code self.avatarURL = avatarURL From 42e1d36df47aae9a21c40de16d9333c2c5073c45 Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 21:17:24 +0900 Subject: [PATCH 46/47] =?UTF-8?q?[iOS]=20refactor:=20BlockedUserView?= =?UTF-8?q?=EB=A1=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockedUserList/BlockedUserListViewController.swift | 2 +- .../{BlockedUserListView.swift => BlockedUserView.swift} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/{BlockedUserListView.swift => BlockedUserView.swift} (95%) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift index 840d3779..732bb5f6 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift @@ -10,7 +10,7 @@ import Combine import Core import Domain -final class BlockedUserListViewController: BaseViewController, LoadingIndicator, HiddenTabBarViewController { +final class BlockedUserListViewController: BaseViewController, LoadingIndicator, HiddenTabBarViewController { // MARK: - Properties weak var coordinator: BlockedUserListCoordinator? diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserView.swift similarity index 95% rename from iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift rename to iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserView.swift index 1534c9b6..4c704a16 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserView.swift @@ -7,7 +7,7 @@ import UIKit -final class BlockedUserListView: UIView { +final class BlockedUserView: UIView { // MARK: - Views private(set) lazy var blockedUserListCollectionView: UICollectionView = { @@ -37,7 +37,7 @@ final class BlockedUserListView: UIView { } } -private extension BlockedUserListView { +private extension BlockedUserView { func makeCollectionViewLayout() -> UICollectionViewLayout { let itemPadding: CGFloat = 20 let itemSize = NSCollectionLayoutSize( From 6d79cd18ffa8d52f1e596125513a911425b3bc6e Mon Sep 17 00:00:00 2001 From: looloolalaa Date: Sat, 30 Dec 2023 21:33:48 +0900 Subject: [PATCH 47/47] =?UTF-8?q?[iOS]=20refactor:=20BlockedUserList?= =?UTF-8?q?=EB=A1=9C=20=EC=9C=A0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BlockedUserList/BlockedUserListCoordinator.swift | 8 ++++---- .../{BlockedUserView.swift => BlockedUserListView.swift} | 4 ++-- .../BlockedUserList/BlockedUserListViewController.swift | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/{BlockedUserView.swift => BlockedUserListView.swift} (95%) diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListCoordinator.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListCoordinator.swift index aa83169e..baba8537 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListCoordinator.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListCoordinator.swift @@ -27,11 +27,11 @@ final class BlockedUserListCoordinator: Coordinator { func start(group: Group) { let blockingRepository = BlockingRepository(groupId: group.id) - let blockUserVM = BlockedUserListViewModel( + let blockedUserListVM = BlockedUserListViewModel( fetchBlockedUserListUseCase: .init(blockingRepository: blockingRepository) ) - let blockUserVC = BlockedUserListViewController(viewModel: blockUserVM) - blockUserVC.coordinator = self - navigationController.pushViewController(blockUserVC, animated: true) + let blockedUserListVC = BlockedUserListViewController(viewModel: blockedUserListVM) + blockedUserListVC.coordinator = self + navigationController.pushViewController(blockedUserListVC, animated: true) } } diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserView.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift similarity index 95% rename from iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserView.swift rename to iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift index 4c704a16..1534c9b6 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserView.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListView.swift @@ -7,7 +7,7 @@ import UIKit -final class BlockedUserView: UIView { +final class BlockedUserListView: UIView { // MARK: - Views private(set) lazy var blockedUserListCollectionView: UICollectionView = { @@ -37,7 +37,7 @@ final class BlockedUserView: UIView { } } -private extension BlockedUserView { +private extension BlockedUserListView { func makeCollectionViewLayout() -> UICollectionViewLayout { let itemPadding: CGFloat = 20 let itemSize = NSCollectionLayoutSize( diff --git a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift index 732bb5f6..840d3779 100644 --- a/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift +++ b/iOS/moti/moti/Presentation/Sources/Presentation/BlockedUserList/BlockedUserListViewController.swift @@ -10,7 +10,7 @@ import Combine import Core import Domain -final class BlockedUserListViewController: BaseViewController, LoadingIndicator, HiddenTabBarViewController { +final class BlockedUserListViewController: BaseViewController, LoadingIndicator, HiddenTabBarViewController { // MARK: - Properties weak var coordinator: BlockedUserListCoordinator?