From 5696549b9ea7fdea21a1cbd9b80050982be1fb60 Mon Sep 17 00:00:00 2001 From: p2glet Date: Fri, 31 Oct 2025 01:29:02 +0900 Subject: [PATCH 01/14] =?UTF-8?q?fix/#264:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=ED=83=AD=20=EB=B6=84=EA=B8=B0=EC=B2=98=EB=A6=AC=20(loginWithDa?= =?UTF-8?q?ta=20/=20loginWithoutData=20/=20logout)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DictionaryList/DictionnaryItemType.swift | 24 +++++---- .../BookmarkEmpty/BookmarkEmptyView.swift | 15 +++--- .../BookmarkListFactoryImpl.swift | 6 ++- .../BookmarkList/BookmarkListReactor.swift | 51 ++++++++++++++----- .../BookmarkList/BookmarkListView.swift | 51 +++++++++++++------ .../BookmarkListViewController.swift | 47 +++++++++++------ .../BookmarkFeatureDemo/AppDelegate.swift | 2 +- .../Tabbar/BottomTabBarController.swift | 7 +++ 8 files changed, 141 insertions(+), 62 deletions(-) diff --git a/MLS/Domain/DomainInterface/Entity/DictionaryList/DictionnaryItemType.swift b/MLS/Domain/DomainInterface/Entity/DictionaryList/DictionnaryItemType.swift index c7fa6a13..6591e63d 100644 --- a/MLS/Domain/DomainInterface/Entity/DictionaryList/DictionnaryItemType.swift +++ b/MLS/Domain/DomainInterface/Entity/DictionaryList/DictionnaryItemType.swift @@ -36,16 +36,20 @@ public enum DictionaryItemType: String { "퀘스트 상세 정보" } } - - static func from(_ string: String) -> DictionaryItemType? { - let mapping: [String: DictionaryItemType] = [ - "item": .item, - "monster": .monster, - "map": .map, - "npc": .npc, - "quest": .quest - ] - return mapping[string.lowercased()] + + public var toDictionaryType: DictionaryType? { + switch self { + case .item: + return .item + case .monster: + return .monster + case .map: + return .map + case .npc: + return .npc + case .quest: + return .quest + } } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift index 5dc2330f..553cd23f 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift @@ -18,13 +18,14 @@ final class BookmarkEmptyView: UIView { private let mainLabel = UILabel() private let subLabel = UILabel() - private let button = CommonButton(style: .normal, title: "북마크하러 가기", disabledTitle: nil) + public let button = CommonButton(style: .normal, title: "ㅌ 가기", disabledTitle: nil) // MARK: - Init init() { super.init(frame: .zero) addViews() setupConstraints() + configureUI() } @available(*, unavailable) @@ -51,7 +52,7 @@ private extension BookmarkEmptyView { mainLabel.snp.makeConstraints { make in make.top.equalTo(imageView.snp.bottom) - make.centerX.equalToSuperview() + make.horizontalEdges.equalToSuperview() } subLabel.snp.makeConstraints { make in @@ -65,10 +66,14 @@ private extension BookmarkEmptyView { make.width.equalTo(Constant.buttonWidth) } } + + func configureUI() { + backgroundColor = .neutral100 + } } extension BookmarkEmptyView { - func setLabel(isLogin: Bool, buttonAction: @escaping () -> Void) { + func setLabel(isLogin: Bool) { imageView.image = DesignSystemAsset.image(named: "noShowList") mainLabel.attributedText = .makeStyledString( font: .h_xl_b, @@ -81,9 +86,5 @@ extension BookmarkEmptyView { ) button.updateTitle(title: isLogin ? "북마크하러 가기" : "로그인하러 가기") - button.removeTarget(nil, action: nil, for: .allEvents) - button.addAction(UIAction { _ in - buttonAction() - }, for: .touchUpInside) } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift index d6edd92f..07ee5628 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift @@ -10,6 +10,7 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { private let sortedFactory: SortedBottomSheetFactory private let bookmarkModalFactory: BookmarkModalFactory private let loginFactory: LoginFactory + private let dictionaryDetailFactory: DictionaryDetailFactory private let setBookmarkUseCase: SetBookmarkUseCase private let checkLoginUseCase: CheckLoginUseCase @@ -26,6 +27,7 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { sortedFactory: SortedBottomSheetFactory, bookmarkModalFactory: BookmarkModalFactory, loginFactory: LoginFactory, + dictionaryDetailFactory: DictionaryDetailFactory, setBookmarkUseCase: SetBookmarkUseCase, checkLoginUseCase: CheckLoginUseCase, fetchBookmarkUseCase: FetchBookmarkUseCase, @@ -40,6 +42,7 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { self.sortedFactory = sortedFactory self.bookmarkModalFactory = bookmarkModalFactory self.loginFactory = loginFactory + self.dictionaryDetailFactory = dictionaryDetailFactory self.setBookmarkUseCase = setBookmarkUseCase self.checkLoginUseCase = checkLoginUseCase self.fetchBookmarkUseCase = fetchBookmarkUseCase @@ -58,7 +61,8 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { monsterFilterFactory: monsterFilterFactory, sortedFactory: sortedFactory, bookmarkModalFactory: bookmarkModalFactory, - loginFactory: loginFactory + loginFactory: loginFactory, + dictionaryDetailFactory: dictionaryDetailFactory ) if listType == .search { viewController.isBottomTabbarHidden = true diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListReactor.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListReactor.swift index 66e08ee4..089cb5ea 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListReactor.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListReactor.swift @@ -4,11 +4,20 @@ import ReactorKit import RxSwift public final class BookmarkListReactor: Reactor { - // MARK: - Route + // MARK: - Type public enum Route { case none case sort(DictionaryType) case filter(DictionaryType) + case detail(DictionaryType, Int) + case dictionary + case login + } + + enum ViewState: Equatable { + case loginWithData + case loginWithoutData + case logout } // MARK: - Action @@ -21,17 +30,18 @@ public final class BookmarkListReactor: Reactor { case sortOptionSelected(SortType) case filterOptionSelected(startLevel: Int, endLevel: Int) case undoLastDeletedBookmark + case dataTapped(Int) + case emptyButtonTapped } // MARK: - Mutation public enum Mutation { case setItems([BookmarkResponse]) - case showSortFilter - case showFilter case setLoginState(Bool) case setSort(SortType) case setFilter(start: Int, end: Int) case setLastDeletedBookmark(BookmarkResponse?) + case toNavagate(Route) } // MARK: - State @@ -44,6 +54,15 @@ public final class BookmarkListReactor: Reactor { var startLevel: Int? var endLevel: Int? var lastDeletedBookmark: BookmarkResponse? + var viewState: ViewState { + if !isLogin { + return .logout + } else if items.isEmpty { + return .loginWithoutData + } else { + return .loginWithData + } + } } public var initialState: State @@ -107,7 +126,7 @@ public final class BookmarkListReactor: Reactor { let saveDeletedMutation: Observable = isSelected ? .just(.setLastDeletedBookmark(bookmarkItem)) - : .just(.setLastDeletedBookmark(nil)) + : .just(.setLastDeletedBookmark(nil)) return saveDeletedMutation .concat( @@ -119,10 +138,10 @@ public final class BookmarkListReactor: Reactor { ) case .sortButtonTapped: - return .just(.showSortFilter) + return .just(.toNavagate(.sort(currentState.type))) case .filterButtonTapped: - return .just(.showFilter) + return .just(.toNavagate(.filter(currentState.type))) case .fetchList: guard currentState.isLogin else { return .empty() } @@ -152,6 +171,16 @@ public final class BookmarkListReactor: Reactor { .just(.setLastDeletedBookmark(nil)) ]) ) + case .dataTapped(let index): + let item = currentState.items[index] + guard let type = item.type.toDictionaryType else { return .empty() } + return .just(.toNavagate(.detail(type, item.originalId))) + case .emptyButtonTapped: + if currentState.viewState == .logout { + return .just(.toNavagate(.login)) + } else { + return .just(.toNavagate(.dictionary)) + } } } @@ -204,12 +233,6 @@ public final class BookmarkListReactor: Reactor { case let .setItems(response): newState.items = response - case .showSortFilter: - newState.route = .sort(newState.type) - - case .showFilter: - newState.route = .filter(newState.type) - case let .setLoginState(isLogin): newState.isLogin = isLogin @@ -220,8 +243,10 @@ public final class BookmarkListReactor: Reactor { newState.startLevel = start newState.endLevel = end - case .setLastDeletedBookmark(let item): + case let .setLastDeletedBookmark(item): newState.lastDeletedBookmark = item + case .toNavagate(let route): + newState.route = route } return newState diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListView.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListView.swift index 30a0fbe3..57c07196 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListView.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListView.swift @@ -4,22 +4,43 @@ import BaseFeature import DesignSystem final class BookmarkListView: BaseListView { + let bookmarkEmptyView: BookmarkEmptyView + // MARK: - Init - init(isFilterHidden: Bool) { - let editButton = TextButton() - let sortButton = BaseListView.makeSortButton(title: "가나다 순", tintColor: .textColor) - let filterButton = BaseListView.makeFilterButton(title: "필터", tintColor: .textColor) - let emptyView = BookmarkEmptyView() + init(isFilterHidden: Bool, bookmarkEmptyView: BookmarkEmptyView) { + let editButton = TextButton() + let sortButton = BaseListView.makeSortButton(title: "가나다 순", tintColor: .textColor) + let filterButton = BaseListView.makeFilterButton(title: "필터", tintColor: .textColor) + self.bookmarkEmptyView = bookmarkEmptyView + super.init( + editButton: editButton, + sortButton: sortButton, + filterButton: filterButton, + emptyView: bookmarkEmptyView, + isFilterHidden: isFilterHidden + ) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError() } +} + +extension BookmarkListView { + func updateView(state: BookmarkListReactor.ViewState) { + switch state { + case .loginWithData: + checkEmptyData(isEmpty: false) - super.init( - editButton: editButton, - sortButton: sortButton, - filterButton: filterButton, - emptyView: emptyView, - isFilterHidden: isFilterHidden - ) - } + case .loginWithoutData: + checkEmptyData(isEmpty: true) + if let emptyView = emptyView as? BookmarkEmptyView { + emptyView.setLabel(isLogin: true) + } - @available(*, unavailable) - required init?(coder: NSCoder) { fatalError() } + case .logout: + if let emptyView = emptyView as? BookmarkEmptyView { + emptyView.setLabel(isLogin: false) + } + } + } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift index 696e9caa..5dfabca6 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift @@ -3,6 +3,7 @@ import UIKit import AuthFeatureInterface import BaseFeature import BookmarkFeatureInterface +import DesignSystem import DictionaryFeatureInterface import ReactorKit @@ -19,11 +20,13 @@ public final class BookmarkListViewController: BaseViewController, View { private let bookmarkModalFactory: BookmarkModalFactory private let sortedFactory: SortedBottomSheetFactory private let loginFactory: LoginFactory + private let dictionaryDetailFactory: DictionaryDetailFactory private var selectedSortIndex = 0 // MARK: - Components private var mainView: BookmarkListView + private var emptyView = BookmarkEmptyView() public init( reactor: BookmarkListReactor, @@ -31,14 +34,16 @@ public final class BookmarkListViewController: BaseViewController, View { monsterFilterFactory: MonsterFilterBottomSheetFactory, sortedFactory: SortedBottomSheetFactory, bookmarkModalFactory: BookmarkModalFactory, - loginFactory: LoginFactory + loginFactory: LoginFactory, + dictionaryDetailFactory: DictionaryDetailFactory ) { self.itemFilterFactory = itemFilterFactory self.monsterFilterFactory = monsterFilterFactory self.sortedFactory = sortedFactory self.bookmarkModalFactory = bookmarkModalFactory self.loginFactory = loginFactory - self.mainView = BookmarkListView(isFilterHidden: reactor.currentState.type.isBookmarkSortHidden) + self.dictionaryDetailFactory = dictionaryDetailFactory + self.mainView = BookmarkListView(isFilterHidden: reactor.currentState.type.isBookmarkSortHidden, bookmarkEmptyView: emptyView) super.init() self.reactor = reactor } @@ -109,6 +114,11 @@ extension BookmarkListViewController { .map { Reactor.Action.filterButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) + + emptyView.button.rx.tap + .map { .emptyButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) } func bindViewState(reactor: Reactor) { @@ -119,8 +129,7 @@ extension BookmarkListViewController { .observe(on: MainScheduler.instance) .bind(onNext: { owner, items in owner.mainView.listCollectionView.reloadData() - owner.mainView.isUserInteractionEnabled = !items.isEmpty - owner.mainView.checkEmptyData(isEmpty: items.isEmpty) +// owner.mainView.isUserInteractionEnabled = !items.isEmpty }) .disposed(by: disposeBag) @@ -152,6 +161,17 @@ extension BookmarkListViewController { default: break } + case .detail(let type, let id): + let vc = owner.dictionaryDetailFactory.make(type: type, id: id) + owner.navigationController?.pushViewController(vc, animated: true) + case .login: + let vc = owner.loginFactory.make(isReLogin: false) + owner.navigationController?.pushViewController(vc, animated: true) + + case .dictionary: + if let tabBarController = owner.tabBarController as? BottomTabBarController { + tabBarController.selectTab(index: 0) + } default: break } @@ -168,19 +188,12 @@ extension BookmarkListViewController { .disposed(by: disposeBag) reactor.state - .map(\.isLogin) + .map(\.viewState) .distinctUntilChanged() .withUnretained(self) - .bind(onNext: { owner, isLogin in - guard let emptyView = owner.mainView.emptyView as? BookmarkEmptyView else { return } - emptyView.setLabel(isLogin: isLogin, buttonAction: { - if isLogin { - owner.tabBarController?.selectedIndex = 0 - } else { - let viewController = owner.loginFactory.make(isReLogin: false) - owner.navigationController?.pushViewController(viewController, animated: true) - } - }) + .observe(on: MainScheduler.instance) + .bind(onNext: { owner, state in + owner.mainView.updateView(state: state) }) .disposed(by: disposeBag) } @@ -251,4 +264,8 @@ extension BookmarkListViewController: UICollectionViewDelegate, UICollectionView return cell } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + reactor?.action.onNext(.dataTapped(indexPath.item)) + } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift index 8042437f..995de534 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift @@ -354,7 +354,7 @@ private extension AppDelegate { ) } DIContainer.register(type: BookmarkListFactory.self) { - BookmarkListFactoryImpl(itemFilterFactory: DIContainer.resolve(type: ItemFilterBottomSheetFactory.self), monsterFilterFactory: DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self), sortedFactory: DIContainer.resolve(type: SortedBottomSheetFactory.self), bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), loginFactory: DIContainer.resolve(type: LoginFactory.self), setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), fetchBookmarkUseCase: DIContainer.resolve(type: FetchBookmarkUseCase.self), fetchMonsterBookmarkUseCase: DIContainer.resolve(type: FetchMonsterBookmarkUseCase.self), fetchItemBookmarkUseCase: DIContainer.resolve(type: FetchItemBookmarkUseCase.self), fetchNPCBookmarkUseCase: DIContainer.resolve(type: FetchNPCBookmarkUseCase.self), fetchQuestBookmarkUseCase: DIContainer.resolve(type: FetchQuestBookmarkUseCase.self), fetchMapBookmarkUseCase: DIContainer.resolve(type: FetchMapBookmarkUseCase.self)) + BookmarkListFactoryImpl(itemFilterFactory: DIContainer.resolve(type: ItemFilterBottomSheetFactory.self), monsterFilterFactory: DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self), sortedFactory: DIContainer.resolve(type: SortedBottomSheetFactory.self), bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), loginFactory: DIContainer.resolve(type: LoginFactory.self), dictionaryDetailFactory: DIContainer.resolve(type: DictionaryDetailFactory.self), setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), fetchBookmarkUseCase: DIContainer.resolve(type: FetchBookmarkUseCase.self), fetchMonsterBookmarkUseCase: DIContainer.resolve(type: FetchMonsterBookmarkUseCase.self), fetchItemBookmarkUseCase: DIContainer.resolve(type: FetchItemBookmarkUseCase.self), fetchNPCBookmarkUseCase: DIContainer.resolve(type: FetchNPCBookmarkUseCase.self), fetchQuestBookmarkUseCase: DIContainer.resolve(type: FetchQuestBookmarkUseCase.self), fetchMapBookmarkUseCase: DIContainer.resolve(type: FetchMapBookmarkUseCase.self)) } DIContainer.register(type: CollectionSettingFactory.self) { CollectionSettingFactoryImpl() diff --git a/MLS/Presentation/DesignSystem/DesignSystem/Components/Tabbar/BottomTabBarController.swift b/MLS/Presentation/DesignSystem/DesignSystem/Components/Tabbar/BottomTabBarController.swift index eb8ce922..2482b338 100644 --- a/MLS/Presentation/DesignSystem/DesignSystem/Components/Tabbar/BottomTabBarController.swift +++ b/MLS/Presentation/DesignSystem/DesignSystem/Components/Tabbar/BottomTabBarController.swift @@ -95,4 +95,11 @@ public extension BottomTabBarController { divider.alpha = hidden ? 0 : 1 } } + + func selectTab(index: Int, animated: Bool = false) { + UIView.performWithoutAnimation { + selectedIndex = index + customTabBar.selectTab(index: index) + } + } } From 03a357e4b5e24745509b1bd25d3ed6e7d8cdad41 Mon Sep 17 00:00:00 2001 From: p2glet Date: Tue, 4 Nov 2025 22:05:46 +0900 Subject: [PATCH 02/14] =?UTF-8?q?fix/#264:=20=EB=8F=84=EA=B0=90=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B6=84=EA=B8=B0=20/=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EB=8F=84=EA=B0=90=20=EB=B2=84=ED=8A=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BookmarkFeatureDemo/AppDelegate.swift | 3 +-- .../DictionaryDetailBaseViewController.swift | 25 ++++++++----------- .../DictionaryMainReactor.swift | 15 +++++++++-- .../DictionaryMainViewFactoryImpl.swift | 6 +++-- .../DictionaryFeatureDemo/AppDelegate.swift | 3 +-- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift index e958937e..fc84edb8 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift @@ -298,8 +298,7 @@ private extension AppDelegate { .resolve(type: DictionaryMainListFactory.self), searchFactory: DIContainer.resolve(type: DictionarySearchFactory.self), notificationFactory: DIContainer - .resolve(type: DictionaryNotificationFactory.self) - ) + .resolve(type: DictionaryNotificationFactory.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self)) } DIContainer.register(type: BookmarkOnBoardingFactory.self) { BookmarkOnBoardingFactoryImpl() diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift index 48f4db13..d88cd12e 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift @@ -53,8 +53,8 @@ class DictionaryDetailBaseViewController: BaseViewController { addViews() setupConstraints() - bindActions() // 액션 바인딩 - mainView.scrollView.delegate = self + configureUI() + bind() // 액션 바인딩 setupMenu(type.detailTypes) } @@ -81,6 +81,10 @@ private extension DictionaryDetailBaseViewController { make.horizontalEdges.bottom.equalToSuperview() } } + + func configureUI() { + mainView.scrollView.delegate = self + } } /// 스티키 헤더 만들기 위한 델리게이트 @@ -298,25 +302,16 @@ extension DictionaryDetailBaseViewController { } private extension DictionaryDetailBaseViewController { - func bindActions() { - // 뒤로가기 버튼 액션 바인드 - bindBackButton() - bindBookmarkButton() - } - - func bindBackButton() { + func bind() { mainView.backButton.rx.tap .bind { [weak self] in self?.navigationController?.popViewController(animated: true) } .disposed(by: disposeBag) - } - - // 이부분은 왜 inject로 넣어야 하나?? - func bindBookmarkButton() { - mainView.bookmarkButton.rx.tap + + mainView.dictButton.rx.tap .bind { [weak self] in - print("bookmark tapped") + self?.navigationController?.popToRootViewController(animated: true) } .disposed(by: disposeBag) } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainReactor.swift index ca0d1269..54286068 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainReactor.swift @@ -8,6 +8,7 @@ public final class DictionaryMainReactor: Reactor { case none case search case notification + case login } public enum Action { @@ -31,9 +32,12 @@ public final class DictionaryMainReactor: Reactor { public var initialState: State var disposeBag = DisposeBag() + private let checkLoginUseCase: CheckLoginUseCase + // MARK: - init - public init() { + public init(checkLoginUseCase: CheckLoginUseCase) { self.initialState = State() + self.checkLoginUseCase = checkLoginUseCase } // MARK: - Reactor Methods @@ -42,7 +46,14 @@ public final class DictionaryMainReactor: Reactor { case .searchButtonTapped: return Observable.just(.navigateTo(.search)) case .notificationButtonTapped: - return Observable.just(.navigateTo(.notification)) + return checkLoginUseCase.execute() + .map { isLogin in + if isLogin { + return .navigateTo(.notification) + } else { + return .navigateTo(.login) + } + } } } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewFactoryImpl.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewFactoryImpl.swift index a941c64b..579cc17b 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewFactoryImpl.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewFactoryImpl.swift @@ -6,15 +6,17 @@ public final class DictionaryMainViewFactoryImpl: DictionaryMainViewFactory { private let dictionaryMainListFactory: DictionaryMainListFactory private let searchFactory: DictionarySearchFactory private let notificationFactory: DictionaryNotificationFactory + private let checkLoginUseCase: CheckLoginUseCase - public init(dictionaryMainListFactory: DictionaryMainListFactory, searchFactory: DictionarySearchFactory, notificationFactory: DictionaryNotificationFactory) { + public init(dictionaryMainListFactory: DictionaryMainListFactory, searchFactory: DictionarySearchFactory, notificationFactory: DictionaryNotificationFactory, checkLoginUseCase: CheckLoginUseCase) { self.dictionaryMainListFactory = dictionaryMainListFactory self.searchFactory = searchFactory self.notificationFactory = notificationFactory + self.checkLoginUseCase = checkLoginUseCase } public func make() -> BaseViewController { - let reactor = DictionaryMainReactor() + let reactor = DictionaryMainReactor(checkLoginUseCase: checkLoginUseCase) let viewController = DictionaryMainViewController(dictionaryMainListFactory: dictionaryMainListFactory, searchFactory: searchFactory, notificationFactory: notificationFactory, reactor: reactor) viewController.reactor = reactor return viewController diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift index be0cb697..f93808a5 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift @@ -298,8 +298,7 @@ private extension AppDelegate { .resolve(type: DictionaryMainListFactory.self), searchFactory: DIContainer.resolve(type: DictionarySearchFactory.self), notificationFactory: DIContainer - .resolve(type: DictionaryNotificationFactory.self) - ) + .resolve(type: DictionaryNotificationFactory.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self)) } DIContainer.register(type: BookmarkOnBoardingFactory.self) { BookmarkOnBoardingFactoryImpl() From a88bb8bf6bfa820aeecbeb1cd1bb582c373eed0c Mon Sep 17 00:00:00 2001 From: p2glet Date: Wed, 5 Nov 2025 23:09:15 +0900 Subject: [PATCH 03/14] =?UTF-8?q?fix/#264:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=9D=B4=ED=9B=84=20=ED=99=94=EB=A9=B4=20=EC=A0=84=ED=99=98?= =?UTF-8?q?(AppcCoordinator)=20/=20=EA=B8=B0=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DictionaryDetailItemResponseDTO.swift | 16 +- .../DictionaryDetailMonsterResponseDTO.swift | 20 +- .../DictionaryDetailQuestResponseDTO.swift | 21 +- .../Repository/AlarmAPIRepositoryImpl.swift | 4 +- .../UserDefaultsRepositoryImpl.swift | 57 ++- .../DataMock/Factory/LoginFactoryMock.swift | 2 +- .../AuthAPI/FetchPlatformUseCaseImpl.swift | 17 + .../AuthAPI/LoginWithAppleUseCaseImpl.swift | 24 +- .../AuthAPI/LoginWithKakaoUseCaseImpl.swift | 24 +- .../DictionaryDetailItemResponse.swift | 16 +- .../DictionaryDetailMonsterResponse.swift | 20 +- .../DictionaryDetailQuestResponse.swift | 21 +- .../Entity/Shared/LoginPlatform.swift | 2 +- .../Repository/UserDefaultsRepository.swift | 3 + .../AuthAPI/FetchPlatformUseCase.swift | 5 + MLS/MLS/Application/AppCoordinator.swift | 66 ++++ MLS/MLS/Application/AppDelegate.swift | 355 +++++++++++++++--- MLS/MLS/Application/SceneDelegate.swift | 105 +++--- .../AuthFeature/Login/LoginFactoryImpl.swift | 31 +- .../AuthFeature/Login/LoginReactor.swift | 21 +- .../AuthFeature/Login/LoginView.swift | 46 ++- .../Login/LoginViewController.swift | 27 +- ...OnBoardingNotificationViewController.swift | 5 +- .../OnBoardingInputViewController.swift | 3 +- ...BoardingNotificationSheetFactoryImpl.swift | 17 +- .../OnBoardingNotificationSheetReactor.swift | 9 +- ...rdingNotificationSheetViewController.swift | 12 +- .../OnBoardingQuestionViewController.swift | 4 +- .../TermsAgreementViewController.swift | 2 +- .../AuthFeatureDemo/AppDelegate.swift | 110 +++--- .../AuthFeatureDemo/ViewController.swift | 2 +- .../AuthFeatureInterface/LoginExitRoute.swift | 6 + .../AuthFeatureInterface/LoginFactory.swift | 8 +- .../Base/BaseErrorViewController.swift | 2 +- .../BaseFeature/SharedView/BaseListView.swift | 1 - .../BaseFeature/Utills/AppRouter.swift | 20 + .../AddCollectionViewController.swift | 1 + .../BookmarkListFactoryImpl.swift | 12 +- .../BookmarkList/BookmarkListView.swift | 2 + .../BookmarkListViewController.swift | 4 +- .../BookmarkMainViewController.swift | 1 + .../BookmarkModalViewController.swift | 6 +- .../CollectionDetailViewController.swift | 4 +- .../CollectionEditViewController.swift | 2 +- .../CollectionListViewController.swift | 7 +- .../CollectionSettingViewController.swift | 1 + .../BookmarkFeatureDemo/AppDelegate.swift | 1 + .../DictionaryDetailBaseViewController.swift | 10 +- .../DictionaryDetailFactoryImpl.swift | 46 ++- .../Map/MapDictionaryDetailReactor.swift | 9 +- .../Quest/QuestDictionaryDetailReactor.swift | 21 +- .../DictionaryMainViewController.swift | 1 + .../DictionarySearchViewController.swift | 3 +- ...DictionarySearchResultViewController.swift | 1 + .../ItemFilterBottomSheetViewController.swift | 3 +- .../SortedBottomSheetViewController.swift | 1 + .../DictionaryFeatureDemo/AppDelegate.swift | 1 + .../AnnouncementViewController.swift | 2 +- .../CustomerSupportBaseViewController.swift | 11 +- .../Main/MyPageMainViewController.swift | 4 +- .../NotificationSettingViewController.swift | 3 +- .../SelectImageViewContoller.swift | 4 +- .../SetCharacterViewController.swift | 3 +- .../SetProfile/SetProfileViewController.swift | 1 + .../MyPageFeatureDemo/AppDelegate.swift | 3 + 65 files changed, 975 insertions(+), 297 deletions(-) create mode 100644 MLS/Domain/Domain/UseCaseImpl/AuthAPI/FetchPlatformUseCaseImpl.swift create mode 100644 MLS/Domain/DomainInterface/UseCase/AuthAPI/FetchPlatformUseCase.swift create mode 100644 MLS/MLS/Application/AppCoordinator.swift create mode 100644 MLS/Presentation/AuthFeature/AuthFeatureInterface/LoginExitRoute.swift create mode 100644 MLS/Presentation/BaseFeature/BaseFeature/Utills/AppRouter.swift diff --git a/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Item/DictionaryDetailItemResponseDTO.swift b/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Item/DictionaryDetailItemResponseDTO.swift index 8819e047..75e93b06 100644 --- a/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Item/DictionaryDetailItemResponseDTO.swift +++ b/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Item/DictionaryDetailItemResponseDTO.swift @@ -16,6 +16,20 @@ public struct DictionaryDetailItemResponseDTO: Decodable { public let bookmarkId: Int? public func toDomain() -> DictionaryDetailItemResponse { - return DictionaryDetailItemResponse(itemId: itemId, nameKr: nameKr, nameEn: nameEn, descriptionText: descriptionText, imgUrl: imgUrl, npcPrice: npcPrice, itemType: itemType, categoryHierachy: categoryHierachy, availableJobs: availableJobs, requiredStats: requiredStats, equipmentStats: equipmentStats, scrollDetail: scrollDetail, bookmarkId: bookmarkId) + return DictionaryDetailItemResponse( + itemId: itemId, + nameKr: nameKr, + nameEn: nameEn, + descriptionText: descriptionText, + imgUrl: imgUrl, + npcPrice: npcPrice, + itemType: itemType, + categoryHierachy: categoryHierachy, + availableJobs: availableJobs, + requiredStats: requiredStats, + equipmentStats: equipmentStats, + scrollDetail: scrollDetail, + bookmarkId: bookmarkId + ) } } diff --git a/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Monster/DictionaryDetailMonsterResponseDTO.swift b/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Monster/DictionaryDetailMonsterResponseDTO.swift index 125e5f23..b094b332 100644 --- a/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Monster/DictionaryDetailMonsterResponseDTO.swift +++ b/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Monster/DictionaryDetailMonsterResponseDTO.swift @@ -20,6 +20,24 @@ public struct DictionaryDetailMonsterResponseDTO: Decodable { public let bookmarkId: Int? public func toDomain() -> DictionaryDetailMonsterResponse { - return DictionaryDetailMonsterResponse(monsterId: monsterId, nameKr: nameKr, nameEn: nameEn, imageUrl: imageUrl, level: level, exp: exp, hp: hp, mp: mp, physicalDefense: physicalDefense, magicDefense: magicDefense, requiredAccuracy: requiredAccuracy, bonusAccuracyPerLevelLower: bonusAccuracyPerLevelLower, evasionRate: evasionRate, mesoDropAmount: mesoDropAmount, mesoDropRate: mesoDropRate, typeEffectiveness: typeEffectiveness, bookmarkId: bookmarkId) + return DictionaryDetailMonsterResponse( + monsterId: monsterId, + nameKr: nameKr, + nameEn: nameEn, + imageUrl: imageUrl, + level: level, + exp: exp, + hp: hp, + mp: mp, + physicalDefense: physicalDefense, + magicDefense: magicDefense, + requiredAccuracy: requiredAccuracy, + bonusAccuracyPerLevelLower: bonusAccuracyPerLevelLower, + evasionRate: evasionRate, + mesoDropAmount: mesoDropAmount, + mesoDropRate: mesoDropRate, + typeEffectiveness: typeEffectiveness, + bookmarkId: bookmarkId + ) } } diff --git a/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Quest/DictionaryDetailQuestResponseDTO.swift b/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Quest/DictionaryDetailQuestResponseDTO.swift index 52bec217..2376ce48 100644 --- a/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Quest/DictionaryDetailQuestResponseDTO.swift +++ b/MLS/Data/Data/Network/DTO/DictionaryDetailDTO/Quest/DictionaryDetailQuestResponseDTO.swift @@ -21,6 +21,25 @@ public struct DictionaryDetailQuestResponseDTO: Decodable { public let bookmarkId: Int? public func toDomain() -> DictionaryDetailQuestResponse { - return DictionaryDetailQuestResponse(questId: questId, titlePrefix: titlePrefix, nameKr: nameKr, nameEn: nameEn, iconUrl: iconUrl, questType: questType, minLevel: minLevel, maxLevel: maxLevel, requiredMesoStart: requiredMesoStart, startNpcId: startNpcId, startNpcName: startNpcName, endNpcId: endNpcId, endNpcName: endNpcName, reward: reward, rewardItems: rewardItems, requirements: requirements, allowedJobs: allowedJobs, bookmarkId: bookmarkId) + return DictionaryDetailQuestResponse( + questId: questId, + titlePrefix: titlePrefix, + nameKr: nameKr, + nameEn: nameEn, + iconUrl: iconUrl, + questType: questType, + minLevel: minLevel, + maxLevel: maxLevel, + requiredMesoStart: requiredMesoStart, + startNpcId: startNpcId, + startNpcName: startNpcName, + endNpcId: endNpcId, + endNpcName: endNpcName, + reward: reward, + rewardItems: rewardItems, + requirements: requirements, + allowedJobs: allowedJobs, + bookmarkId: bookmarkId + ) } } diff --git a/MLS/Data/Data/Repository/AlarmAPIRepositoryImpl.swift b/MLS/Data/Data/Repository/AlarmAPIRepositoryImpl.swift index c79b48f3..0a482051 100644 --- a/MLS/Data/Data/Repository/AlarmAPIRepositoryImpl.swift +++ b/MLS/Data/Data/Repository/AlarmAPIRepositoryImpl.swift @@ -44,7 +44,7 @@ public class AlarmAPIRepositoryImpl: AlarmAPIRepository { } public func setRead(alarmLink: String) -> Completable { - let endpoint = AlarmEndPoint.setRead(query: setReadQuery(alrimLink: alarmLink)) + let endpoint = AlarmEndPoint.setRead(query: SetReadQuery(alrimLink: alarmLink)) return provider.requestData(endPoint: endpoint, interceptor: tokenInterceptor) } @@ -56,7 +56,7 @@ private extension AlarmAPIRepositoryImpl { let pageSize: Int } - struct setReadQuery: Encodable { + struct SetReadQuery: Encodable { let alrimLink: String } } diff --git a/MLS/Data/Data/Repository/UserDefaultsRepositoryImpl.swift b/MLS/Data/Data/Repository/UserDefaultsRepositoryImpl.swift index ee92e589..1fb04a83 100644 --- a/MLS/Data/Data/Repository/UserDefaultsRepositoryImpl.swift +++ b/MLS/Data/Data/Repository/UserDefaultsRepositoryImpl.swift @@ -4,45 +4,68 @@ import Foundation import RxSwift public final class UserDefaultsRepositoryImpl: UserDefaultsRepository { - private let key = "recentSearch" + private let recentSearchkey = "recentSearch" + private let platformKey = "platformKey" - public init() { } + public init() {} public func fetchRecentSearch() -> Observable<[String]> { return Observable.create { observer in - let current = UserDefaults.standard.stringArray(forKey: self.key) ?? [] + let current = UserDefaults.standard.stringArray(forKey: self.recentSearchkey) ?? [] observer.onNext(current) observer.onCompleted() return Disposables.create() } } - + public func addRecentSearch(keyword: String) -> Completable { return Completable.create { completable in - var current = UserDefaults.standard.stringArray(forKey: self.key) ?? [] - + var current = UserDefaults.standard.stringArray(forKey: self.recentSearchkey) ?? [] + // 중복 제거 - current.removeAll(where: { $0 == keyword}) + current.removeAll(where: { $0 == keyword }) current.insert(keyword, at: 0) - - UserDefaults.standard.set(current, forKey: self.key) + + UserDefaults.standard.set(current, forKey: self.recentSearchkey) completable(.completed) return Disposables.create() } } - + public func removeRecentSearch(keyword: String) -> Completable { return Completable.create { completable in - var current = UserDefaults.standard.stringArray(forKey: self.key) ?? [] + var current = UserDefaults.standard.stringArray(forKey: self.recentSearchkey) ?? [] + + // 해당 키워드 제거 + current.removeAll { $0 == keyword } - // 해당 키워드 제거 - current.removeAll { $0 == keyword } + // 다시 저장 + UserDefaults.standard.set(current, forKey: self.recentSearchkey) - // 다시 저장 - UserDefaults.standard.set(current, forKey: self.key) + completable(.completed) + return Disposables.create() + } + } - completable(.completed) - return Disposables.create() + public func fetchPlatform() -> Observable { + return Observable.create { observer in + if let rawValue = UserDefaults.standard.string(forKey: self.platformKey), + let platform = LoginPlatform(rawValue: rawValue) + { + observer.onNext(platform) + } else { + observer.onNext(nil) } + observer.onCompleted() + return Disposables.create() + } + } + + public func savePlatform(platform: LoginPlatform) -> Completable { + return Completable.create { completable in + UserDefaults.standard.set(platform.rawValue, forKey: self.platformKey) + completable(.completed) + return Disposables.create() + } } } diff --git a/MLS/Data/DataMock/Factory/LoginFactoryMock.swift b/MLS/Data/DataMock/Factory/LoginFactoryMock.swift index 69618f51..acc23bb1 100644 --- a/MLS/Data/DataMock/Factory/LoginFactoryMock.swift +++ b/MLS/Data/DataMock/Factory/LoginFactoryMock.swift @@ -6,7 +6,7 @@ import DomainInterface public final class LoginFactoryMock: LoginFactory { public init() {} - public func make(isReLogin isRelogin: Bool) -> BaseViewController { + public func make(exitRoute: LoginExitRoute) -> BaseViewController { let viewController = BaseViewController() viewController.view.backgroundColor = .blue return viewController diff --git a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/FetchPlatformUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/FetchPlatformUseCaseImpl.swift new file mode 100644 index 00000000..40e7100d --- /dev/null +++ b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/FetchPlatformUseCaseImpl.swift @@ -0,0 +1,17 @@ +import Foundation + +import DomainInterface + +import RxSwift + +public class FetchPlatformUseCaseImpl: FetchPlatformUseCase { + private var repository: UserDefaultsRepository + + public init(repository: UserDefaultsRepository) { + self.repository = repository + } + + public func execute() -> Observable { + return repository.fetchPlatform() + } +} diff --git a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithAppleUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithAppleUseCaseImpl.swift index 39d5ff9c..1c625aee 100644 --- a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithAppleUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithAppleUseCaseImpl.swift @@ -5,15 +5,31 @@ import DomainInterface import RxSwift public class LoginWithAppleUseCaseImpl: LoginWithAppleUseCase { - private var repository: AuthAPIRepository + private var authRepository: AuthAPIRepository + private let tokenRepository: TokenRepository + private var userDefaultsRepository: UserDefaultsRepository - public init(repository: AuthAPIRepository) { - self.repository = repository + public init(authRepository: AuthAPIRepository, tokenRepository: TokenRepository, userDefaultsRepository: UserDefaultsRepository) { + self.authRepository = authRepository + self.tokenRepository = tokenRepository + self.userDefaultsRepository = userDefaultsRepository } // 로그인할때 토큰 저장 필요 public func execute(credential: Credential) -> Observable { - return repository.loginWithApple(credential: credential) + return authRepository.loginWithApple(credential: credential) + .flatMap { response -> Observable in + let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + + let savePlatform = self.userDefaultsRepository.savePlatform(platform: .apple) + + switch saveAccess { + case .success: + return savePlatform.andThen(Observable.just(response)) + default: + return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) + } + } .catch { error in Observable.error(error) } diff --git a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithKakaoUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithKakaoUseCaseImpl.swift index 776921e0..ee8028e3 100644 --- a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithKakaoUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithKakaoUseCaseImpl.swift @@ -5,15 +5,31 @@ import DomainInterface import RxSwift public class LoginWithKakaoUseCaseImpl: LoginWithKakaoUseCase { - private var repository: AuthAPIRepository + private var authRepository: AuthAPIRepository + private let tokenRepository: TokenRepository + private var userDefaultsRepository: UserDefaultsRepository - public init(repository: AuthAPIRepository) { - self.repository = repository + public init(authRepository: AuthAPIRepository, tokenRepository: TokenRepository, userDefaultsRepository: UserDefaultsRepository) { + self.authRepository = authRepository + self.tokenRepository = tokenRepository + self.userDefaultsRepository = userDefaultsRepository } // 로그인할때 토큰 저장 필요 public func execute(credential: Credential) -> Observable { - return repository.loginWithKakao(credential: credential) + return authRepository.loginWithKakao(credential: credential) + .flatMap { response -> Observable in + let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + + let savePlatform = self.userDefaultsRepository.savePlatform(platform: .kakao) + + switch saveAccess { + case .success: + return savePlatform.andThen(Observable.just(response)) + default: + return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) + } + } .catch { error in Observable.error(error) } diff --git a/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailItemResponse.swift b/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailItemResponse.swift index acb54a0c..5ef9c4ff 100644 --- a/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailItemResponse.swift +++ b/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailItemResponse.swift @@ -13,7 +13,21 @@ public struct DictionaryDetailItemResponse: Equatable { public let scrollDetail: ScrollDetail? // 주문서 상세정보 public let bookmarkId: Int? - public init(itemId: Int?, nameKr: String?, nameEn: String?, descriptionText: String?, imgUrl: String?, npcPrice: Int?, itemType: String?, categoryHierachy: CategoryHierachy?, availableJobs: [Jobs]?, requiredStats: RequiredStats?, equipmentStats: EquipmentStats?, scrollDetail: ScrollDetail?, bookmarkId: Int?) { + public init( + itemId: Int?, + nameKr: String?, + nameEn: String?, + descriptionText: String?, + imgUrl: String?, + npcPrice: Int?, + itemType: String?, + categoryHierachy: CategoryHierachy?, + availableJobs: [Jobs]?, + requiredStats: RequiredStats?, + equipmentStats: EquipmentStats?, + scrollDetail: ScrollDetail?, + bookmarkId: Int? + ) { self.itemId = itemId self.nameKr = nameKr self.nameEn = nameEn diff --git a/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailMonsterResponse.swift b/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailMonsterResponse.swift index e4a4d5cb..751f1b17 100644 --- a/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailMonsterResponse.swift +++ b/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailMonsterResponse.swift @@ -21,7 +21,25 @@ public struct DictionaryDetailMonsterResponse: Equatable { public let typeEffectiveness: Effectiveness? public let bookmarkId: Int? - public init(monsterId: Int, nameKr: String, nameEn: String, imageUrl: String, level: Int, exp: Int, hp: Int, mp: Int, physicalDefense: Int, magicDefense: Int, requiredAccuracy: Int, bonusAccuracyPerLevelLower: Double, evasionRate: Int, mesoDropAmount: Int?, mesoDropRate: Int?, typeEffectiveness: Effectiveness?, bookmarkId: Int?) { + public init( + monsterId: Int, + nameKr: String, + nameEn: String, + imageUrl: String, + level: Int, + exp: Int, + hp: Int, + mp: Int, + physicalDefense: Int, + magicDefense: Int, + requiredAccuracy: Int, + bonusAccuracyPerLevelLower: Double, + evasionRate: Int, + mesoDropAmount: Int?, + mesoDropRate: Int?, + typeEffectiveness: Effectiveness?, + bookmarkId: Int? + ) { self.monsterId = monsterId self.nameKr = nameKr self.nameEn = nameEn diff --git a/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailQuestResponse.swift b/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailQuestResponse.swift index 4a58538e..753fbc21 100644 --- a/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailQuestResponse.swift +++ b/MLS/Domain/DomainInterface/Entity/DictionaryDetail/DictionaryDetailQuestResponse.swift @@ -18,7 +18,26 @@ public struct DictionaryDetailQuestResponse: Equatable { public let allowedJobs: [AllowedJob]? public let bookmarkId: Int? - public init(questId: Int?, titlePrefix: String?, nameKr: String?, nameEn: String?, iconUrl: String?, questType: String?, minLevel: Int?, maxLevel: Int?, requiredMesoStart: Int?, startNpcId: Int?, startNpcName: String?, endNpcId: Int?, endNpcName: String?, reward: Reward?, rewardItems: [RewardItem]?, requirements: [Requirements]?, allowedJobs: [AllowedJob]?, bookmarkId: Int?) { + public init( + questId: Int?, + titlePrefix: String?, + nameKr: String?, + nameEn: String?, + iconUrl: String?, + questType: String?, + minLevel: Int?, + maxLevel: Int?, + requiredMesoStart: Int?, + startNpcId: Int?, + startNpcName: String?, + endNpcId: Int?, + endNpcName: String?, + reward: Reward?, + rewardItems: [RewardItem]?, + requirements: [Requirements]?, + allowedJobs: [AllowedJob]?, + bookmarkId: Int? + ) { self.questId = questId self.titlePrefix = titlePrefix self.nameKr = nameKr diff --git a/MLS/Domain/DomainInterface/Entity/Shared/LoginPlatform.swift b/MLS/Domain/DomainInterface/Entity/Shared/LoginPlatform.swift index 835cd2df..35c4f7ff 100644 --- a/MLS/Domain/DomainInterface/Entity/Shared/LoginPlatform.swift +++ b/MLS/Domain/DomainInterface/Entity/Shared/LoginPlatform.swift @@ -1,6 +1,6 @@ import Foundation -public enum LoginPlatform { +public enum LoginPlatform: String { case kakao case apple } diff --git a/MLS/Domain/DomainInterface/Repository/UserDefaultsRepository.swift b/MLS/Domain/DomainInterface/Repository/UserDefaultsRepository.swift index 2f4a3a32..d9badd8e 100644 --- a/MLS/Domain/DomainInterface/Repository/UserDefaultsRepository.swift +++ b/MLS/Domain/DomainInterface/Repository/UserDefaultsRepository.swift @@ -4,4 +4,7 @@ public protocol UserDefaultsRepository { func fetchRecentSearch() -> Observable<[String]> func addRecentSearch(keyword: String) -> Completable func removeRecentSearch(keyword: String) -> Completable + + func fetchPlatform() -> Observable + func savePlatform(platform: LoginPlatform) -> Completable } diff --git a/MLS/Domain/DomainInterface/UseCase/AuthAPI/FetchPlatformUseCase.swift b/MLS/Domain/DomainInterface/UseCase/AuthAPI/FetchPlatformUseCase.swift new file mode 100644 index 00000000..ce1113f9 --- /dev/null +++ b/MLS/Domain/DomainInterface/UseCase/AuthAPI/FetchPlatformUseCase.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol FetchPlatformUseCase { + func execute() -> Observable +} diff --git a/MLS/MLS/Application/AppCoordinator.swift b/MLS/MLS/Application/AppCoordinator.swift new file mode 100644 index 00000000..df326387 --- /dev/null +++ b/MLS/MLS/Application/AppCoordinator.swift @@ -0,0 +1,66 @@ +import UIKit +import AuthFeature +import AuthFeatureInterface +import BaseFeature +import BookmarkFeatureInterface +import DesignSystem +import DictionaryFeatureInterface +import MyPageFeatureInterface + +import RxSwift + +public final class AppCoordinator { + // MARK: - Properties + private let window: UIWindow + private let dictionaryMainViewFactory: DictionaryMainViewFactory + private let bookmarkMainFactory: BookmarkMainFactory + private let myPageMainFactory: MyPageMainFactory + private let loginFactory: LoginFactory + + private let disposeBag = DisposeBag() + + // MARK: - Init + public init( + window: UIWindow, + dictionaryMainViewFactory: DictionaryMainViewFactory, + bookmarkMainFactory: BookmarkMainFactory, + myPageMainFactory: MyPageMainFactory, + loginFactory: LoginFactory + ) { + self.window = window + self.dictionaryMainViewFactory = dictionaryMainViewFactory + self.bookmarkMainFactory = bookmarkMainFactory + self.myPageMainFactory = myPageMainFactory + self.loginFactory = loginFactory + } + + // MARK: - Public Methods + public func showMainTab() { + let tabBar = BottomTabBarController(viewControllers: [ + dictionaryMainViewFactory.make(), + bookmarkMainFactory.make(), + myPageMainFactory.make() + ]) + setRoot(tabBar) + } + + public func showLogin(exitRoute: LoginExitRoute) { + let loginVC = loginFactory.make(exitRoute: exitRoute) { [weak self] in + switch exitRoute { + case .home: + self?.showMainTab() + default: + break + } + } + + let navigationController = UINavigationController(rootViewController: loginVC) + setRoot(navigationController) + } + + // MARK: - Private Helper + private func setRoot(_ viewController: UIViewController) { + window.rootViewController = viewController + window.makeKeyAndVisible() + } +} diff --git a/MLS/MLS/Application/AppDelegate.swift b/MLS/MLS/Application/AppDelegate.swift index 0f35d077..620fc1c3 100644 --- a/MLS/MLS/Application/AppDelegate.swift +++ b/MLS/MLS/Application/AppDelegate.swift @@ -1,3 +1,6 @@ +// swiftlint:disable function_body_length +// swiftlint:disable line_length + import os import UIKit import UserNotifications @@ -96,31 +99,43 @@ private extension AppDelegate { func registerProvider() { DIContainer.register(type: NetworkProvider.self) { - return NetworkProviderImpl() + NetworkProviderImpl() } DIContainer.register(type: SocialAuthenticatableProvider.self, name: "kakao") { - return KakaoLoginProviderImpl() + KakaoLoginProviderImpl() } DIContainer.register(type: SocialAuthenticatableProvider.self, name: "apple") { - return AppleLoginProviderImpl() + AppleLoginProviderImpl() + } + DIContainer.register(type: Interceptor.self) { + TokenInterceptor(fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self)) } } func registerRepository() { DIContainer.register(type: AuthAPIRepository.self) { - return AuthAPIRepositoryImpl( + AuthAPIRepositoryImpl( provider: DIContainer.resolve(type: NetworkProvider.self), - interceptor: TokenInterceptor(fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self)) + interceptor: DIContainer.resolve(type: Interceptor.self) ) } DIContainer.register(type: TokenRepository.self) { - return KeyChainRepositoryImpl() - } - DIContainer.register(type: DictionaryListRepository.self) { - return DictionaryListRepositoryImpl(allItems: []) + KeyChainRepositoryImpl() } DIContainer.register(type: DictionaryDetailAPIRepository.self) { - return DictionaryDetailAPIRepositoryImpl(provider: DIContainer.resolve(type: NetworkProvider.self), tokenInterceptor: TokenInterceptor(fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self))) + DictionaryDetailAPIRepositoryImpl(provider: DIContainer.resolve(type: NetworkProvider.self), tokenInterceptor: DIContainer.resolve(type: Interceptor.self)) + } + DIContainer.register(type: DictionaryListAPIRepository.self) { + DictionaryListAPIRepositoryImpl(provider: DIContainer.resolve(type: NetworkProvider.self), tokenInterceptor: DIContainer.resolve(type: Interceptor.self)) + } + DIContainer.register(type: BookmarkRepository.self) { + BookmarkRepositoryImpl(provider: DIContainer.resolve(type: NetworkProvider.self), interceptor: DIContainer.resolve(type: Interceptor.self)) + } + DIContainer.register(type: UserDefaultsRepository.self) { + UserDefaultsRepositoryImpl() + } + DIContainer.register(type: AlarmAPIRepository.self) { + AlarmAPIRepositoryImpl(provider: DIContainer.resolve(type: NetworkProvider.self), interceptor: DIContainer.resolve(type: Interceptor.self)) } } @@ -134,127 +149,263 @@ private extension AppDelegate { return SocialLoginUseCaseImpl(provider: provider) } DIContainer.register(type: CheckEmptyLevelAndRoleUseCase.self) { - return CheckEmptyLevelAndRoleUseCaseImpl() + CheckEmptyLevelAndRoleUseCaseImpl() } DIContainer.register(type: CheckValidLevelUseCase.self) { - return CheckValidLevelUseCaseImpl() + CheckValidLevelUseCaseImpl() } DIContainer.register(type: FetchJobListUseCase.self) { - return FetchJobListUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + FetchJobListUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: LoginWithAppleUseCase.self) { - return LoginWithAppleUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + LoginWithAppleUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self), userDefaultsRepository: DIContainer.resolve(type: UserDefaultsRepository.self)) } DIContainer.register(type: LoginWithKakaoUseCase.self) { - return LoginWithKakaoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + LoginWithKakaoUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self), userDefaultsRepository: DIContainer.resolve(type: UserDefaultsRepository.self)) } DIContainer.register(type: SignUpWithAppleUseCase.self) { - return SignUpWithAppleUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + SignUpWithAppleUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: SignUpWithKakaoUseCase.self) { - return SignUpWithKakaoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + SignUpWithKakaoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: UpdateUserInfoUseCase.self) { - return UpdateUserInfoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + UpdateUserInfoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: ReissueUseCase.self) { - return ReissueUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + ReissueUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: PutFCMTokenUseCase.self) { - return PutFCMTokenUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + PutFCMTokenUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: FetchTokenFromLocalUseCase.self) { - return FetchTokenFromLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) + FetchTokenFromLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) } DIContainer.register(type: SaveTokenToLocalUseCase.self) { - return SaveTokenToLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) + SaveTokenToLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) } DIContainer.register(type: DeleteTokenFromLocalUseCase.self) { - return DeleteTokenFromLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) + DeleteTokenFromLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) } DIContainer.register(type: UpdateMarketingAgreementUseCase.self) { - return UpdateMarketingAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self)) + UpdateMarketingAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self)) } DIContainer.register(type: CheckNotificationPermissionUseCase.self) { - return CheckNotificationPermissionUseCaseImpl() + CheckNotificationPermissionUseCaseImpl() } DIContainer.register(type: OpenNotificationSettingUseCase.self) { - return OpenNotificationSettingUseCaseImpl() + OpenNotificationSettingUseCaseImpl() } DIContainer.register(type: UpdateNotificationAgreementUseCase.self) { - return UpdateNotificationAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self)) + UpdateNotificationAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self)) } - DIContainer.register(type: FetchDictionaryItemsUseCase.self) { - return FetchDictionaryItemsUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListRepository.self)) + DIContainer.register(type: FetchNotificationUseCase.self) { + FetchNotificationUseCaseImpl() } - DIContainer.register(type: ToggleBookmarkUseCase.self) { - return ToggleBookmarkUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListRepository.self)) + DIContainer.register(type: CheckLoginUseCase.self) { + CheckLoginUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self)) } - DIContainer.register(type: FetchNotificationUseCase.self) { - return FetchNotificationUseCaseImpl() + DIContainer.register(type: FetchDictionaryAllListUseCase.self) { + FetchDictionaryAllListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } - DIContainer.register(type: FetchDictionaryDetailMonsterItemsUseCase.self) { - return FetchDictionaryDetailMonsterDropItemUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + DIContainer.register(type: SetBookmarkUseCase.self) { + SetBookmarkUseCaseImpl(repository: DIContainer.resolve(type: BookmarkRepository.self)) } - DIContainer.register(type: FetchDictionaryDetailMonsterMapUseCase.self) { - return FetchDictionaryDetailMonsterMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + DIContainer.register(type: FetchPlatformUseCase.self) { + FetchPlatformUseCaseImpl(repository: DIContainer.resolve(type: UserDefaultsRepository.self)) } DIContainer.register(type: FetchDictionaryMapListUseCase.self) { - return FetchDictionaryMapListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryMapListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryItemListUseCase.self) { - return FetchDictionaryItemListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryItemListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryQuestListUseCase.self) { - return FetchDictionaryQuestListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryQuestListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryNpcListUseCase.self) { - return FetchDictionaryNpcListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryNpcListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryMonsterListUseCase.self) { - return FetchDictionaryMonsterListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryMonsterListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailMonsterUseCase.self) { + FetchDictionaryDetailMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailMonsterItemsUseCase.self) { + FetchDictionaryDetailMonsterDropItemUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailMonsterMapUseCase.self) { + FetchDictionaryDetailMonsterMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailNpcUseCase.self) { + FetchDictionaryDetailNpcUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailNpcQuestUseCase.self) { + FetchDictionaryDetailNpcQuestUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailNpcMapUseCase.self) { + FetchDictionaryDetailNpcMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailItemUseCase.self) { + FetchDictionaryDetailItemUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailItemDropMonsterUseCase.self) { + FetchDictionaryDetailItemDropMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailQuestUseCase.self) { + FetchDictionaryDetailQuestUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailQuestLinkedQuestsUseCase.self) { + FetchDictionaryDetailQuestLinkedQuestsUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailMapUseCase.self) { + FetchDictionaryDetailMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailMapSpawnMonsterUseCase.self) { + FetchDictionaryDetailMapSpawnMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: FetchDictionaryDetailMapNpcUseCase.self) { + FetchDictionaryDetailMapNpcUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + } + DIContainer.register(type: RecentSearchRemoveUseCase.self) { + RecentSearchRemoveUseCaseImpl(repository: DIContainer.resolve(type: UserDefaultsRepository.self)) + } + DIContainer.register(type: RecentSearchAddUseCase.self) { + RecentSearchAddUseCaseImpl(repository: DIContainer.resolve(type: UserDefaultsRepository.self)) + } + DIContainer.register(type: FetchDictionaryListCountUseCase.self) { + FetchDictionaryListCountUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + } + DIContainer.register(type: FetchDictionarySearchListUseCase.self) { + FetchDictionarySearchListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + } + DIContainer.register(type: RecentSearchFetchUseCase.self) { + RecentSearchFetchUseCaseImpl(repository: DIContainer.resolve(type: UserDefaultsRepository.self)) + } + DIContainer.register(type: FetchBookmarkUseCase.self) { + FetchBookmarkUseCaseImpl(repository: DIContainer.resolve(type: BookmarkRepository.self)) + } + DIContainer.register(type: FetchMonsterBookmarkUseCase.self) { + FetchMonsterBookmarkUseCaseImpl(repository: DIContainer.resolve(type: BookmarkRepository.self)) + } + DIContainer.register(type: FetchItemBookmarkUseCase.self) { + FetchItemBookmarkUseCaseImpl(repository: DIContainer.resolve(type: BookmarkRepository.self)) + } + DIContainer.register(type: FetchNPCBookmarkUseCase.self) { + FetchNPCBookmarkUseCaseImpl(repository: DIContainer.resolve(type: BookmarkRepository.self)) + } + DIContainer.register(type: FetchQuestBookmarkUseCase.self) { + FetchQuestBookmarkUseCaseImpl(repository: DIContainer.resolve(type: BookmarkRepository.self)) + } + DIContainer.register(type: FetchMapBookmarkUseCase.self) { + FetchMapBookmarkUseCaseImpl(repository: DIContainer.resolve(type: BookmarkRepository.self)) + } + DIContainer.register(type: UpdateProfileImageUseCase.self) { + UpdateProfileImageUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + } + DIContainer.register(type: FetchJobUseCase.self) { + FetchJobUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + } + DIContainer.register(type: FetchProfileUseCase.self) { + FetchProfileUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self), fetchJobUseCase: DIContainer.resolve(type: FetchJobUseCase.self)) + } + DIContainer.register(type: CheckNickNameUseCase.self) { + CheckNickNameUseCaseImpl() + } + DIContainer.register(type: UpdateNickNameUseCase.self) { + UpdateNickNameUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + } + DIContainer.register(type: LogoutUseCase.self) { + LogoutUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) + } + DIContainer.register(type: WithdrawUseCase.self) { + WithdrawUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self)) + } + DIContainer.register(type: FetchNoticesUseCase.self) { + FetchNoticesUseCaseImpl(repository: DIContainer.resolve(type: AlarmAPIRepository.self)) + } + DIContainer.register(type: FetchOngoingEventsUseCase.self) { + FetchOngoingEventsUseCaseImpl(repository: DIContainer.resolve(type: AlarmAPIRepository.self)) + } + DIContainer.register(type: FetchOutdatedEventsUseCase.self) { + FetchOutdatedEventsUseCaseImpl(repository: DIContainer.resolve(type: AlarmAPIRepository.self)) + } + DIContainer.register(type: FetchPatchNotesUseCase.self) { + FetchPatchNotesUseCaseImpl(repository: DIContainer.resolve(type: AlarmAPIRepository.self)) + } + DIContainer.register(type: SetReadUseCase.self) { + SetReadUseCaseImpl(repository: DIContainer.resolve(type: AlarmAPIRepository.self)) } } func registerFactory() { DIContainer.register(type: ItemFilterBottomSheetFactory.self) { - return ItemFilterBottomSheetFactoryImpl() + ItemFilterBottomSheetFactoryImpl() } DIContainer.register(type: MonsterFilterBottomSheetFactory.self) { - return MonsterFilterBottomSheetFactoryImpl() + MonsterFilterBottomSheetFactoryImpl() } DIContainer.register(type: SortedBottomSheetFactory.self) { - return SortedBottomSheetFactoryImpl() + SortedBottomSheetFactoryImpl() } DIContainer.register(type: AddCollectionFactory.self) { - return AddCollectionFactoryImpl() + AddCollectionFactoryImpl() } DIContainer.register(type: BookmarkModalFactory.self) { - return BookmarkModalFactoryImpl(addCollectionFactory: DIContainer.resolve(type: AddCollectionFactory.self)) + BookmarkModalFactoryImpl(addCollectionFactory: DIContainer.resolve(type: AddCollectionFactory.self)) } DIContainer.register(type: DictionaryDetailFactory.self) { - return DictionaryDetailFactoryImpl(dictionaryDetailMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterUseCase.self), dictionaryDetailMonsterDropItemUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterItemsUseCase.self), dictionaryDetailMonsterMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterMapUseCase.self)) + DictionaryDetailFactoryImpl(bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), dictionaryDetailMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapUseCase.self), dictionaryDetailMapSpawnMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapSpawnMonsterUseCase.self), dictionaryDetailMapNpcUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapNpcUseCase.self), dictionaryDetailQuestLinkedQuestsUseCase: DIContainer.resolve(type: FetchDictionaryDetailQuestLinkedQuestsUseCase.self), dictionaryDetailQuestUseCase: DIContainer.resolve(type: FetchDictionaryDetailQuestUseCase.self), dictionaryDetailItemDropMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailItemDropMonsterUseCase.self), dictionaryDetailItemUseCase: DIContainer.resolve(type: FetchDictionaryDetailItemUseCase.self), dictionaryDetailNpcUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcUseCase.self), dictionaryDetailNpcQuestUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcQuestUseCase.self), dictionaryDetailNpcMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcMapUseCase.self), dictionaryDetailMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterUseCase.self), dictionaryDetailMonsterDropItemUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterItemsUseCase.self), dictionaryDetailMonsterMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterMapUseCase.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self)) } DIContainer.register(type: DictionaryMainListFactory.self) { - return DictionaryListFactoryImpl(dictionaryMapListItemUseCase: DIContainer.resolve(type: FetchDictionaryMapListUseCase.self), dictionaryItemListItemUseCase: DIContainer.resolve(type: FetchDictionaryItemListUseCase.self), dictionaryQuestListItemUseCase: DIContainer.resolve(type: FetchDictionaryQuestListUseCase.self), dictionaryNpcListItemUseCase: DIContainer.resolve(type: FetchDictionaryNpcListUseCase.self), dictionaryListItemUseCase: DIContainer.resolve(type: FetchDictionaryMonsterListUseCase.self), fetchDictionaryItemsUseCase: DIContainer.resolve(type: FetchDictionaryItemsUseCase.self), toggleBookmarkUseCase: DIContainer.resolve(type: ToggleBookmarkUseCase.self), itemFilterFactory: DIContainer.resolve(type: ItemFilterBottomSheetFactory.self), monsterFilterFactory: DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self), sortedFactory: DIContainer.resolve(type: SortedBottomSheetFactory.self), bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), detailFactory: DIContainer.resolve(type: DictionaryDetailFactory.self)) + DictionaryListFactoryImpl( + checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), + dictionaryAllListItemUseCase: DIContainer.resolve(type: FetchDictionaryAllListUseCase.self), + dictionaryMapListItemUseCase: DIContainer.resolve(type: FetchDictionaryMapListUseCase.self), + dictionaryItemListItemUseCase: DIContainer.resolve(type: FetchDictionaryItemListUseCase.self), + dictionaryQuestListItemUseCase: DIContainer.resolve(type: FetchDictionaryQuestListUseCase.self), + dictionaryNpcListItemUseCase: DIContainer.resolve(type: FetchDictionaryNpcListUseCase.self), + dictionaryListItemUseCase: DIContainer.resolve(type: FetchDictionaryMonsterListUseCase.self), + setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self), + itemFilterFactory: DIContainer.resolve(type: ItemFilterBottomSheetFactory.self), + monsterFilterFactory: DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self), + sortedFactory: DIContainer.resolve(type: SortedBottomSheetFactory.self), + bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), + detailFactory: DIContainer.resolve(type: DictionaryDetailFactory.self) + ) } DIContainer.register(type: DictionarySearchResultFactory.self) { - return DictionarySearchResultFactoryImpl(dictionaryMainListFactory: DIContainer.resolve(type: DictionaryMainListFactory.self)) + DictionarySearchResultFactoryImpl( + dictionaryListCountUseCase: DIContainer.resolve(type: FetchDictionaryListCountUseCase.self), + dictionaryMainListFactory: DIContainer + .resolve(type: DictionaryMainListFactory.self), + dictionarySearchListUseCase: DIContainer.resolve(type: FetchDictionarySearchListUseCase.self) + ) } DIContainer.register(type: DictionarySearchFactory.self) { - return DictionarySearchFactoryImpl(searchResultFactory: DIContainer.resolve(type: DictionarySearchResultFactory.self)) + DictionarySearchFactoryImpl(recentSearchRemoveUseCase: DIContainer.resolve(type: RecentSearchRemoveUseCase.self), + recentSearchAddUseCase: DIContainer.resolve(type: RecentSearchAddUseCase.self), + searchResultFactory: DIContainer + .resolve(type: DictionarySearchResultFactory.self), recentSearchFetchUseCase: DIContainer.resolve(type: RecentSearchFetchUseCase.self)) } DIContainer.register(type: NotificationSettingFactory.self) { - return NotificationSettingFactoryImpl(checkNotificationPermissionUseCase: DIContainer.resolve(type: CheckNotificationPermissionUseCase.self), updateNotificationAgreementUseCase: DIContainer.resolve(type: UpdateNotificationAgreementUseCase.self)) + NotificationSettingFactoryImpl(checkNotificationPermissionUseCase: DIContainer.resolve(type: CheckNotificationPermissionUseCase.self), updateNotificationAgreementUseCase: DIContainer.resolve(type: UpdateNotificationAgreementUseCase.self)) } DIContainer.register(type: DictionaryNotificationFactory.self) { - return DictionaryNotificationFactoryImpl(fetchNotificationUseCase: DIContainer.resolve(type: FetchNotificationUseCase.self), notificationSettingFactory: DIContainer.resolve(type: NotificationSettingFactory.self)) + DictionaryNotificationFactoryImpl(fetchNotificationUseCase: DIContainer.resolve(type: FetchNotificationUseCase.self), notificationSettingFactory: DIContainer.resolve(type: NotificationSettingFactory.self)) } DIContainer.register(type: DictionaryMainViewFactory.self) { - return DictionaryMainViewFactoryImpl(dictionaryMainListFactory: DIContainer.resolve(type: DictionaryMainListFactory.self), searchFactory: DIContainer.resolve(type: DictionarySearchFactory.self), notificationFactory: DIContainer.resolve(type: DictionaryNotificationFactory.self)) + DictionaryMainViewFactoryImpl( + dictionaryMainListFactory: DIContainer + .resolve(type: DictionaryMainListFactory.self), + searchFactory: DIContainer.resolve(type: DictionarySearchFactory.self), + notificationFactory: DIContainer + .resolve(type: DictionaryNotificationFactory.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self) + ) } DIContainer.register(type: OnBoardingNotificationSheetFactory.self) { - return OnBoardingNotificationSheetFactoryImpl( + OnBoardingNotificationSheetFactoryImpl( checkNotificationPermissionUseCase: DIContainer .resolve(type: CheckNotificationPermissionUseCase.self), openNotificationSettingUseCase: DIContainer @@ -264,19 +415,20 @@ private extension AppDelegate { ) } DIContainer.register(type: OnBoardingInputFactory.self) { - return OnBoardingInputFactoryImpl( + OnBoardingInputFactoryImpl( checkEmptyUseCase: DIContainer.resolve(type: CheckEmptyLevelAndRoleUseCase.self), checkValidLevelUseCase: DIContainer.resolve(type: CheckValidLevelUseCase.self), fetchJobListUseCase: DIContainer.resolve(type: FetchJobListUseCase.self), - updateUserInfoUseCase: DIContainer.resolve(type: UpdateUserInfoUseCase.self), onBoardingNotificationFactory: DIContainer.resolve(type: OnBoardingNotificationFactory.self)) + updateUserInfoUseCase: DIContainer.resolve(type: UpdateUserInfoUseCase.self), onBoardingNotificationFactory: DIContainer.resolve(type: OnBoardingNotificationFactory.self) + ) } DIContainer.register(type: OnBoardingQuestionFactory.self) { - return OnBoardingQuestionFactoryImpl( + OnBoardingQuestionFactoryImpl( onBoardingInputFactory: DIContainer.resolve(type: OnBoardingInputFactory.self) ) } DIContainer.register(type: TermsAgreementFactory.self) { - return TermsAgreementFactoryImpl( + TermsAgreementFactoryImpl( onBoardingQuestionFactory: DIContainer.resolve(type: OnBoardingQuestionFactory.self), signUpWithKakaoUseCase: DIContainer.resolve(type: SignUpWithKakaoUseCase.self), signUpWithAppleUseCase: DIContainer.resolve(type: SignUpWithAppleUseCase.self), @@ -285,18 +437,97 @@ private extension AppDelegate { ) } DIContainer.register(type: LoginFactory.self) { - return LoginFactoryImpl( + LoginFactoryImpl( termsAgreementsFactory: DIContainer.resolve(type: TermsAgreementFactory.self), appleLoginUseCase: DIContainer.resolve(type: FetchSocialCredentialUseCase.self, name: "apple"), kakaoLoginUseCase: DIContainer.resolve(type: FetchSocialCredentialUseCase.self, name: "kakao"), loginWithAppleUseCase: DIContainer.resolve(type: LoginWithAppleUseCase.self), loginWithKakaoUseCase: DIContainer.resolve(type: LoginWithKakaoUseCase.self), fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self), - putFCMTokenUseCase: DIContainer.resolve(type: PutFCMTokenUseCase.self) + putFCMTokenUseCase: DIContainer.resolve(type: PutFCMTokenUseCase.self), fetchPlatformUseCase: DIContainer.resolve(type: FetchPlatformUseCase.self) ) } DIContainer.register(type: OnBoardingNotificationFactory.self) { - return OnBoardingNotificationFactoryImpl(onBoardingNotificationSheetFactory: DIContainer.resolve(type: OnBoardingNotificationSheetFactory.self)) + OnBoardingNotificationFactoryImpl(onBoardingNotificationSheetFactory: DIContainer.resolve(type: OnBoardingNotificationSheetFactory.self)) + } + DIContainer.register(type: BookmarkMainFactory.self) { + BookmarkMainFactoryImpl( + setBookmarkUseCase: DIContainer + .resolve(type: SetBookmarkUseCase.self), + onBoardingFactory: DIContainer + .resolve(type: BookmarkOnBoardingFactory.self), + bookmarkListFactory: DIContainer + .resolve(type: BookmarkListFactory.self), + collectionListFactory: DIContainer + .resolve(type: CollectionListFactory.self), + searchFactory: DIContainer + .resolve(type: DictionarySearchFactory.self), + notificationFactory: DIContainer.resolve( + type: DictionaryNotificationFactory.self + ) + ) + } + DIContainer.register(type: BookmarkOnBoardingFactory.self) { + BookmarkOnBoardingFactoryImpl() + } + DIContainer.register(type: BookmarkListFactory.self) { + BookmarkListFactoryImpl(itemFilterFactory: DIContainer.resolve(type: ItemFilterBottomSheetFactory.self), monsterFilterFactory: DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self), sortedFactory: DIContainer.resolve(type: SortedBottomSheetFactory.self), bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), loginFactory: DIContainer.resolve(type: LoginFactory.self), dictionaryDetailFactory: DIContainer.resolve(type: DictionaryDetailFactory.self), setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), fetchBookmarkUseCase: DIContainer.resolve(type: FetchBookmarkUseCase.self), fetchMonsterBookmarkUseCase: DIContainer.resolve(type: FetchMonsterBookmarkUseCase.self), fetchItemBookmarkUseCase: DIContainer.resolve(type: FetchItemBookmarkUseCase.self), fetchNPCBookmarkUseCase: DIContainer.resolve(type: FetchNPCBookmarkUseCase.self), fetchQuestBookmarkUseCase: DIContainer.resolve(type: FetchQuestBookmarkUseCase.self), fetchMapBookmarkUseCase: DIContainer.resolve(type: FetchMapBookmarkUseCase.self)) + } + DIContainer.register(type: CollectionListFactory.self) { + CollectionListFactoryImpl(addCollectionFactory: DIContainer.resolve(type: AddCollectionFactory.self), bookmarkDetailFactory: DIContainer.resolve(type: CollectionDetailFactory.self)) + } + DIContainer.register(type: CollectionDetailFactory.self) { + CollectionDetailFactoryImpl( + setBookmarkUseCase: DIContainer + .resolve(type: SetBookmarkUseCase.self), + bookmarkModalFactory: DIContainer + .resolve(type: BookmarkModalFactory.self), + collectionSettingFactory: DIContainer + .resolve(type: CollectionSettingFactory.self), + addCollectionFactory: DIContainer + .resolve(type: AddCollectionFactory.self), + collectionEditFactory: DIContainer + .resolve(type: CollectionEditFactory.self) + ) + } + DIContainer.register(type: CollectionSettingFactory.self) { + CollectionSettingFactoryImpl() + } + DIContainer.register(type: CollectionEditFactory.self) { + CollectionEditFactoryImpl(setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self), bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self)) + } + DIContainer.register(type: MyPageMainFactory.self) { + MyPageMainFactoryImpl( + loginFactory: DIContainer.resolve(type: LoginFactory.self), setProfileFactory: DIContainer + .resolve(type: SetProfileFactory.self), + customerSupportFactory: DIContainer + .resolve(type: CustomerSupportFactory.self), + notificationSettingFactory: DIContainer + .resolve(type: NotificationSettingFactory.self), + setCharacterFactory: DIContainer + .resolve(type: SetCharacterFactory.self), fetchProfileUseCase: DIContainer.resolve(type: FetchProfileUseCase.self) + ) + } + DIContainer.register(type: CustomerSupportFactory.self) { + CustomerSupportBaseViewFactoryImpl(fetchNoticesUseCase: DIContainer.resolve(type: FetchNoticesUseCase.self), fetchOngoingEventsUseCase: DIContainer.resolve(type: FetchOngoingEventsUseCase.self), fetchOutdatedEventsUseCase: DIContainer.resolve(type: FetchOutdatedEventsUseCase.self), fetchPatchNotesUseCase: DIContainer.resolve(type: FetchPatchNotesUseCase.self), setReadUseCase: DIContainer.resolve(type: SetReadUseCase.self)) + } + DIContainer.register(type: SetProfileFactory.self) { + SetProfileFactoryImpl(selectImageFactory: DIContainer.resolve(type: SelectImageFactory.self), checkNickNameUseCase: DIContainer.resolve(type: CheckNickNameUseCase.self), updateNickNameUseCase: DIContainer.resolve(type: UpdateNickNameUseCase.self), logoutUseCase: DIContainer.resolve(type: LogoutUseCase.self), withdrawUseCase: DIContainer.resolve(type: WithdrawUseCase.self)) + } + DIContainer.register(type: SetCharacterFactory.self) { + SetCharacterFactoryImpl( + checkEmptyUseCase: DIContainer + .resolve(type: CheckEmptyLevelAndRoleUseCase.self), + checkValidLevelUseCase: DIContainer + .resolve(type: CheckValidLevelUseCase.self), + fetchJobListUseCase: DIContainer + .resolve(type: FetchJobListUseCase.self), + updateUserInfoUseCase: DIContainer + .resolve(type: UpdateUserInfoUseCase.self) + ) + } + DIContainer.register(type: SelectImageFactory.self) { + SelectImageFactoryImpl(updateProfileImageUseCase: DIContainer.resolve(type: UpdateProfileImageUseCase.self)) } } } diff --git a/MLS/MLS/Application/SceneDelegate.swift b/MLS/MLS/Application/SceneDelegate.swift index 1b0e1044..a35df616 100644 --- a/MLS/MLS/Application/SceneDelegate.swift +++ b/MLS/MLS/Application/SceneDelegate.swift @@ -1,82 +1,75 @@ -import NotificationCenter import UIKit -import AuthFeature import AuthFeatureInterface -import BaseFeature +import BookmarkFeatureInterface import Core -import Data -import DictionaryFeature import DictionaryFeatureInterface -import Domain import DomainInterface +import MyPageFeatureInterface -import KakaoSDKAuth import RxSwift -class SceneDelegate: UIResponder, UIWindowSceneDelegate { +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + var appCoordinator: AppCoordinator? var disposeBag = DisposeBag() func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } - window = UIWindow(windowScene: windowScene) - setStartViewController(window: window) - } - func sceneDidDisconnect(_ scene: UIScene) {} + let window = UIWindow(windowScene: windowScene) + window.makeKeyAndVisible() + self.window = window - func sceneDidBecomeActive(_ scene: UIScene) {} + let dictionaryMainViewFactory: DictionaryMainViewFactory = DIContainer.resolve(type: DictionaryMainViewFactory.self) + let bookmarkMainFactory: BookmarkMainFactory = DIContainer.resolve(type: BookmarkMainFactory.self) + let myPageMainFactory: MyPageMainFactory = DIContainer.resolve(type: MyPageMainFactory.self) + let loginFactory: LoginFactory = DIContainer.resolve(type: LoginFactory.self) - func sceneWillResignActive(_ scene: UIScene) {} + let coordinator = AppCoordinator( + window: window, + dictionaryMainViewFactory: dictionaryMainViewFactory, + bookmarkMainFactory: bookmarkMainFactory, + myPageMainFactory: myPageMainFactory, + loginFactory: loginFactory + ) + self.appCoordinator = coordinator - func sceneWillEnterForeground(_ scene: UIScene) {} + startScene(coordinator: coordinator) + } - func sceneDidEnterBackground(_ scene: UIScene) {} + private func startScene(coordinator: AppCoordinator) { + let fetchTokenUseCase = DIContainer.resolve(type: FetchTokenFromLocalUseCase.self) + let reissueUseCase = DIContainer.resolve(type: ReissueUseCase.self) + let saveTokenUseCase = DIContainer.resolve(type: SaveTokenToLocalUseCase.self) - func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { - if let url = URLContexts.first?.url { - if AuthApi.isKakaoTalkLoginUrl(url) { - _ = AuthController.handleOpenUrl(url: url) - } - } - } + let fetchResult = fetchTokenUseCase.execute(type: .refreshToken) - func setStartViewController(window: UIWindow?) { - UNUserNotificationCenter.current().getNotificationSettings { [weak self] _ in - guard let self = self else { return } - DispatchQueue.main.async { - let loginFactory: LoginFactory = DIContainer.resolve(type: LoginFactory.self) - let notificationFactory: OnBoardingNotificationFactory = DIContainer.resolve(type: OnBoardingNotificationFactory.self) - window?.makeKeyAndVisible() - let reissueUseCase = DIContainer.resolve(type: ReissueUseCase.self) - let fetchTokenUseCase = DIContainer.resolve(type: FetchTokenFromLocalUseCase.self) - let saveTokenUseCase = DIContainer.resolve(type: SaveTokenToLocalUseCase.self) - let fetchResult = fetchTokenUseCase.execute(type: .refreshToken) + switch fetchResult { + case .success(let refreshToken): + // ✅ refreshToken 존재 → accessToken 재발급 시도 + reissueUseCase.execute(refreshToken: refreshToken) + .observe(on: MainScheduler.instance) + .subscribe( + onNext: { response in + let accessSave = saveTokenUseCase.execute(type: .accessToken, value: response.accessToken) + let refreshSave = saveTokenUseCase.execute(type: .refreshToken, value: response.refreshToken) - switch fetchResult { - case .success(let token): - reissueUseCase.execute(refreshToken: token) - .observe(on: MainScheduler.instance) - .subscribe { response in - let accessSaveResult = saveTokenUseCase.execute(type: .accessToken, value: response.accessToken) - let refreshSaveResult = saveTokenUseCase.execute(type: .refreshToken, value: response.refreshToken) - window?.rootViewController = UINavigationController(rootViewController: ViewController()) - if case .success = accessSaveResult, case .success = refreshSaveResult { - // 저장 결과 모두 성공일 때만 진입 - window?.rootViewController = UINavigationController(rootViewController: ViewController()) - } else { - // 저장 실패 시 로그인 화면으로 이동 - window?.rootViewController = UINavigationController(rootViewController: loginFactory.make(isReLogin: false)) - } - } onError: { _ in - window?.rootViewController = UINavigationController(rootViewController: loginFactory.make(isReLogin: false)) + if case .success = accessSave, case .success = refreshSave { + coordinator.showMainTab() + } else { + coordinator.showLogin(exitRoute: .home) } - .disposed(by: self.disposeBag) - case .failure: - window?.rootViewController = UINavigationController(rootViewController: loginFactory.make(isReLogin: false)) - } - } + }, + onError: { _ in + coordinator.showLogin(exitRoute: .home) + } + ) + .disposed(by: disposeBag) + + case .failure: + // ✅ refreshToken 없으면 바로 로그인으로 + coordinator.showLogin(exitRoute: .home) } } } diff --git a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginFactoryImpl.swift b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginFactoryImpl.swift index a4f8f9ee..a75289b0 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginFactoryImpl.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginFactoryImpl.swift @@ -2,6 +2,8 @@ import AuthFeatureInterface import BaseFeature import DomainInterface +import RxSwift + public struct LoginFactoryImpl: LoginFactory { private let termsAgreementsFactory: TermsAgreementFactory private let appleLoginUseCase: FetchSocialCredentialUseCase @@ -10,6 +12,7 @@ public struct LoginFactoryImpl: LoginFactory { private let loginWithKakaoUseCase: LoginWithKakaoUseCase private let fetchTokenUseCase: FetchTokenFromLocalUseCase private let putFCMTokenUseCase: PutFCMTokenUseCase + private let fetchPlatformUseCase: FetchPlatformUseCase public init( termsAgreementsFactory: TermsAgreementFactory, @@ -18,7 +21,8 @@ public struct LoginFactoryImpl: LoginFactory { loginWithAppleUseCase: LoginWithAppleUseCase, loginWithKakaoUseCase: LoginWithKakaoUseCase, fetchTokenUseCase: FetchTokenFromLocalUseCase, - putFCMTokenUseCase: PutFCMTokenUseCase + putFCMTokenUseCase: PutFCMTokenUseCase, + fetchPlatformUseCase: FetchPlatformUseCase ) { self.termsAgreementsFactory = termsAgreementsFactory self.appleLoginUseCase = appleLoginUseCase @@ -27,20 +31,35 @@ public struct LoginFactoryImpl: LoginFactory { self.loginWithKakaoUseCase = loginWithKakaoUseCase self.fetchTokenUseCase = fetchTokenUseCase self.putFCMTokenUseCase = putFCMTokenUseCase + self.fetchPlatformUseCase = fetchPlatformUseCase } - public func make( - isReLogin: Bool - ) -> BaseViewController { - let viewController = LoginViewController(isRelogin: isReLogin, termsAgreementsFactory: termsAgreementsFactory) + public func make(exitRoute: LoginExitRoute, onLoginCompleted: (() -> Void)?) -> BaseViewController { + let viewController = LoginViewController(termsAgreementsFactory: termsAgreementsFactory) + viewController.isBottomTabbarHidden = true + viewController.reactor = LoginReactor( fetchAppleCredentialUseCase: appleLoginUseCase, fetchKakaoCredentialUseCase: kakaoLoginUseCase, loginWithAppleUseCase: loginWithAppleUseCase, loginWithKakaoUseCase: loginWithKakaoUseCase, fetchTokenUseCase: fetchTokenUseCase, - putFCMTokenUseCase: putFCMTokenUseCase + putFCMTokenUseCase: putFCMTokenUseCase, + fetchPlatformUseCase: fetchPlatformUseCase ) + + viewController.routeToHome + .observe(on: MainScheduler.instance) + .subscribe(onNext: { [weak viewController] in + switch exitRoute { + case .home: + onLoginCompleted?() + case .pop: + viewController?.navigationController?.popViewController(animated: true) + } + }) + .disposed(by: viewController.disposeBag) + return viewController } } diff --git a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginReactor.swift b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginReactor.swift index 6989a7ee..d710266e 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginReactor.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginReactor.swift @@ -8,12 +8,13 @@ public final class LoginReactor: Reactor { public enum Route { case none case termsAgreements(credential: Credential, platform: LoginPlatform) - case home case error + case dismiss } // MARK: - Reactor public enum Action { + case viewWillAppear case kakaoLoginButtonTapped case appleLoginButtonTapped case guestLoginButtonTapped @@ -21,10 +22,12 @@ public final class LoginReactor: Reactor { public enum Mutation { case navigateTo(route: Route) + case setRelogin(LoginPlatform?) } public struct State { @Pulse var route: Route = .none + var platform: LoginPlatform? = nil } // MARK: - properties @@ -36,6 +39,7 @@ public final class LoginReactor: Reactor { private let loginWithKakaoUseCase: LoginWithKakaoUseCase private let fetchTokenUseCase: FetchTokenFromLocalUseCase private let putFCMTokenUseCase: PutFCMTokenUseCase + private let fetchPlatformUseCase: FetchPlatformUseCase // MARK: - init public init( @@ -44,7 +48,8 @@ public final class LoginReactor: Reactor { loginWithAppleUseCase: LoginWithAppleUseCase, loginWithKakaoUseCase: LoginWithKakaoUseCase, fetchTokenUseCase: FetchTokenFromLocalUseCase, - putFCMTokenUseCase: PutFCMTokenUseCase + putFCMTokenUseCase: PutFCMTokenUseCase, + fetchPlatformUseCase: FetchPlatformUseCase ) { self.fetchAppleCredentialUseCase = fetchAppleCredentialUseCase self.fetchKakaoCredentialUseCase = fetchKakaoCredentialUseCase @@ -52,18 +57,22 @@ public final class LoginReactor: Reactor { self.loginWithKakaoUseCase = loginWithKakaoUseCase self.fetchTokenUseCase = fetchTokenUseCase self.putFCMTokenUseCase = putFCMTokenUseCase + self.fetchPlatformUseCase = fetchPlatformUseCase self.initialState = State() } // MARK: - Reactor Methods public func mutate(action: Action) -> Observable { switch action { + case .viewWillAppear: + return fetchPlatformUseCase.execute() + .map{ Mutation.setRelogin($0) } case .kakaoLoginButtonTapped: return handleKakaoLogin() case .appleLoginButtonTapped: return handleAppleLogin() case .guestLoginButtonTapped: - return .just(.navigateTo(route: .home)) + return .just(.navigateTo(route: .dismiss)) } } @@ -72,6 +81,8 @@ public final class LoginReactor: Reactor { switch mutation { case .navigateTo(let route): newState.route = route + case .setRelogin(let platform): + newState.platform = platform } return newState } @@ -92,7 +103,7 @@ private extension LoginReactor { if response.isRegister { // 3. 회원가입된 유저면 FCM 토큰 등록 후 홈으로 이동 return owner.putFCMTokenUseCase.execute(credential: response.accessToken, fcmToken: fcmToken) - .andThen(.just(.navigateTo(route: .home))) + .andThen(.just(.navigateTo(route: .dismiss))) } else { // 4. 미가입 유저면 약관 동의 화면으로 이동 return .just(.navigateTo(route: .termsAgreements( @@ -129,7 +140,7 @@ private extension LoginReactor { if response.isRegister { // 3. 회원가입된 유저면 FCM 토큰 등록 후 홈으로 이동 return owner.putFCMTokenUseCase.execute(credential: response.accessToken, fcmToken: fcmToken) - .andThen(.just(.navigateTo(route: .home))) + .andThen(.just(.navigateTo(route: .dismiss))) } else { // 4. 미가입 유저면 약관 동의 화면으로 이동 return .just(.navigateTo(route: .termsAgreements( diff --git a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginView.swift b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginView.swift index 60b6ee9f..d2302a0e 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginView.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginView.swift @@ -1,6 +1,7 @@ import UIKit import DesignSystem +import DomainInterface import SnapKit @@ -78,8 +79,6 @@ final class LoginView: UIView { return button }() - private let isRelogin: Bool - private let mainTitleLabel: UILabel = { let label = UILabel() label.attributedText = .makeStyledString(font: .h_xl_b, text: "모험가님,") @@ -93,8 +92,7 @@ final class LoginView: UIView { }() // MARK: - init - init(isRelogin: Bool) { - self.isRelogin = isRelogin + init() { super.init(frame: .zero) addViews() @@ -113,16 +111,10 @@ private extension LoginView { func addViews() { addSubview(loginImageView) addSubview(buttonStackView) + buttonStackView.addArrangedSubview(kakaoLoginButton) buttonStackView.addArrangedSubview(appleLoginButton) - if isRelogin { - addSubview(mainTitleLabel) - addSubview(subTitleLabel) - } else { - buttonStackView.addArrangedSubview(guestLoginButton) - } - kakaoLoginButton.addSubview(kakaoLogoImageView) kakaoLoginButton.addSubview(kakaoLoginLabel) appleLoginButton.addSubview(appleLogoImageView) @@ -170,24 +162,42 @@ private extension LoginView { make.centerY.equalToSuperview() make.centerX.equalToSuperview().inset(Constant.buttonCenterXInset) } + } + + func configureUI() {} +} + +extension LoginView { + func update(loginPlatform: LoginPlatform?) { + mainTitleLabel.removeFromSuperview() + subTitleLabel.removeFromSuperview() + guestLoginButton.removeFromSuperview() + + switch loginPlatform { + case .kakao, .apple: + // 최근로그인 라벨 추가 + addSubview(mainTitleLabel) + addSubview(subTitleLabel) - if isRelogin { - subTitleLabel.snp.makeConstraints { make in + subTitleLabel.snp.remakeConstraints { make in make.bottom.equalTo(buttonStackView.snp.top).offset(Constant.subTitleBottomSpacing) make.centerX.equalToSuperview() make.height.equalTo(Constant.labelHeight) } - mainTitleLabel.snp.makeConstraints { make in + mainTitleLabel.snp.remakeConstraints { make in make.bottom.equalTo(subTitleLabel.snp.top) make.centerX.equalToSuperview() make.height.equalTo(Constant.labelHeight) } - } else { - guestLoginButton.snp.makeConstraints { make in + case nil: + buttonStackView.addArrangedSubview(guestLoginButton) + guestLoginButton.snp.remakeConstraints { make in make.height.equalTo(Constant.buttonHeight) } + } - } - func configureUI() {} + setNeedsLayout() + layoutIfNeeded() + } } diff --git a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginViewController.swift index 4cbbe924..2852d204 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginViewController.swift @@ -13,13 +13,15 @@ public final class LoginViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() + + public let routeToHome = PublishRelay() private let mainView: LoginView private let termsAgreementsFactory: TermsAgreementFactory - public init(isRelogin: Bool, termsAgreementsFactory: TermsAgreementFactory) { - self.mainView = LoginView(isRelogin: isRelogin) + public init(termsAgreementsFactory: TermsAgreementFactory) { + self.mainView = LoginView() self.termsAgreementsFactory = termsAgreementsFactory super.init() } @@ -66,6 +68,11 @@ public extension LoginViewController { } func bindUserActions(reactor: Reactor) { + rx.viewWillAppear + .map { _ in Reactor.Action.viewWillAppear } + .bind(to: reactor.action) + .disposed(by: disposeBag) + mainView.kakaoLoginButton.rx.tap .map { Reactor.Action.kakaoLoginButtonTapped } .bind(to: reactor.action) @@ -111,6 +118,16 @@ public extension LoginViewController { } func bindViewState(reactor: Reactor) { + reactor.state + .observe(on: MainScheduler.instance) + .map { $0.platform } + .distinctUntilChanged() + .withUnretained(self) + .subscribe { owner, platform in + owner.mainView.update(loginPlatform: platform) + } + .disposed(by: disposeBag) + rx.viewDidAppear .take(1) .flatMapLatest { _ in reactor.pulse(\.$route) } @@ -121,10 +138,8 @@ public extension LoginViewController { case .termsAgreements(let credential, let platform): let controller = owner.termsAgreementsFactory.make(credential: credential, platform: platform) owner.navigationController?.pushViewController(controller, animated: true) - case .home: - let controller = UIViewController() - controller.view.backgroundColor = .green - owner.navigationController?.pushViewController(controller, animated: true) + case .dismiss: + owner.routeToHome.accept(()) case .error: DispatchQueue.main.async { let controller = BaseErrorViewController() diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoadingNotification/OnBoardingNotificationViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoadingNotification/OnBoardingNotificationViewController.swift index ba6d82dd..6005c9ed 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoadingNotification/OnBoardingNotificationViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoadingNotification/OnBoardingNotificationViewController.swift @@ -12,11 +12,12 @@ import SnapKit public class OnBoardingNotificationViewController: BaseViewController, View { // MARK: - Properties public typealias Reactor = OnBoardingNotificationReactor - + + public var disposeBag = DisposeBag() + private let onBoardingNotificationSheetFactory: OnBoardingNotificationSheetFactory // MARK: - Components - public var disposeBag = DisposeBag() private var mainView = OnBoardingNotificationView() diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingInput/OnBoardingInputViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingInput/OnBoardingInputViewController.swift index 55a13222..8002c956 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingInput/OnBoardingInputViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingInput/OnBoardingInputViewController.swift @@ -14,10 +14,11 @@ public class OnBoardingInputViewController: BaseViewController, View { // MARK: - Properties public typealias Reactor = OnBoardingInputReactor + public var disposeBag = DisposeBag() + private let onBoardingNotificationFactory: OnBoardingNotificationFactory // MARK: - Components - public var disposeBag = DisposeBag() private var mainView = OnBoardingInputView() diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift index 1f19b9a3..85de382f 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetFactoryImpl.swift @@ -10,7 +10,13 @@ public struct OnBoardingNotificationSheetFactoryImpl: OnBoardingNotificationShee private let updateUserInfoUseCase: UpdateUserInfoUseCase private let dictionaryMainViewFactory: DictionaryMainViewFactory - public init(checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, openNotificationSettingUseCase: OpenNotificationSettingUseCase, updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, updateUserInfoUseCase: UpdateUserInfoUseCase, dictionaryMainViewFactory: DictionaryMainViewFactory) { + public init( + checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, + openNotificationSettingUseCase: OpenNotificationSettingUseCase, + updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, + updateUserInfoUseCase: UpdateUserInfoUseCase, + dictionaryMainViewFactory: DictionaryMainViewFactory + ) { self.checkNotificationPermissionUseCase = checkNotificationPermissionUseCase self.openNotificationSettingUseCase = openNotificationSettingUseCase self.updateNotificationAgreementUseCase = updateNotificationAgreementUseCase @@ -20,7 +26,14 @@ public struct OnBoardingNotificationSheetFactoryImpl: OnBoardingNotificationShee public func make(selectedLevel: Int, selectedJobID: Int) -> BaseViewController & ModalPresentable { let viewController = OnBoardingNotificationSheetViewController(dictionaryMainViewFactory: dictionaryMainViewFactory) - viewController.reactor = OnBoardingNotificationSheetReactor(selectedLevel: selectedLevel, selectedJobID: selectedJobID, checkNotificationPermissionUseCase: checkNotificationPermissionUseCase, openNotificationSettingUseCase: openNotificationSettingUseCase, updateNotificationAgreementUseCase: updateNotificationAgreementUseCase, updateUserInfoUseCase: updateUserInfoUseCase) + viewController.reactor = OnBoardingNotificationSheetReactor( + selectedLevel: selectedLevel, + selectedJobID: selectedJobID, + checkNotificationPermissionUseCase: checkNotificationPermissionUseCase, + openNotificationSettingUseCase: openNotificationSettingUseCase, + updateNotificationAgreementUseCase: updateNotificationAgreementUseCase, + updateUserInfoUseCase: updateUserInfoUseCase + ) return viewController } } diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift index 0dd02188..3c72a9a3 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetReactor.swift @@ -46,7 +46,14 @@ public final class OnBoardingNotificationSheetReactor: Reactor { private let updateUserInfoUseCase: UpdateUserInfoUseCase // MARK: - init - public init(selectedLevel: Int, selectedJobID: Int, checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, openNotificationSettingUseCase: OpenNotificationSettingUseCase, updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, updateUserInfoUseCase: UpdateUserInfoUseCase) { + public init( + selectedLevel: Int, + selectedJobID: Int, + checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, + openNotificationSettingUseCase: OpenNotificationSettingUseCase, + updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, + updateUserInfoUseCase: UpdateUserInfoUseCase + ) { self.initialState = State(selectedLevel: selectedLevel, selectedJobID: selectedJobID) self.checkNotificationPermissionUseCase = checkNotificationPermissionUseCase self.openNotificationSettingUseCase = openNotificationSettingUseCase diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift index cce762ac..024a866a 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift @@ -13,9 +13,10 @@ public final class OnBoardingNotificationSheetViewController: BaseViewController public var modalHeight: CGFloat? public typealias Reactor = OnBoardingNotificationSheetReactor + + public var disposeBag = DisposeBag() // MARK: - Properties - public var disposeBag = DisposeBag() private let dictionaryMainViewFactory: DictionaryMainViewFactory @@ -113,14 +114,7 @@ extension OnBoardingNotificationSheetViewController { case .home: let viewController = owner.dictionaryMainViewFactory.make() let navigationController = UINavigationController(rootViewController: viewController) - - if let window = UIApplication.shared.connectedScenes - .compactMap({ $0 as? UIWindowScene }) - .flatMap({ $0.windows }) - .first(where: { $0.isKeyWindow }) { - window.rootViewController = navigationController - window.makeKeyAndVisible() - } + AppRouter.setRoot(navigationController) case .setting: guard let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) else { return } diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingQuestion/OnBoardingQuestionViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingQuestion/OnBoardingQuestionViewController.swift index 6439b81a..90ca08b4 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingQuestion/OnBoardingQuestionViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingQuestion/OnBoardingQuestionViewController.swift @@ -11,10 +11,12 @@ import SnapKit public class OnBoardingQuestionViewController: BaseViewController, View { // MARK: - Properties public typealias Reactor = OnBoardingQuestionReactor + + public var disposeBag = DisposeBag() + private let onBoardingInputFactory: OnBoardingInputFactory // MARK: - Components - public var disposeBag = DisposeBag() private var mainView = OnBoardingQuestionView() diff --git a/MLS/Presentation/AuthFeature/AuthFeature/TermsAgreement/TermsAgreementViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/TermsAgreement/TermsAgreementViewController.swift index bb7d2325..a520bf41 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/TermsAgreement/TermsAgreementViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/TermsAgreement/TermsAgreementViewController.swift @@ -13,7 +13,7 @@ public class TermsAgreementViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let onBoardingQuestionFactory: OnBoardingQuestionFactory private var mainView = TermsAgreementView() diff --git a/MLS/Presentation/AuthFeature/AuthFeatureDemo/AppDelegate.swift b/MLS/Presentation/AuthFeature/AuthFeatureDemo/AppDelegate.swift index 01e04687..36895d3e 100644 --- a/MLS/Presentation/AuthFeature/AuthFeatureDemo/AppDelegate.swift +++ b/MLS/Presentation/AuthFeature/AuthFeatureDemo/AppDelegate.swift @@ -39,25 +39,28 @@ private extension AppDelegate { func registerProvider() { DIContainer.register(type: NetworkProvider.self) { - return NetworkProviderImpl() + NetworkProviderImpl() } DIContainer.register(type: SocialAuthenticatableProvider.self, name: "kakao") { - return KakaoLoginProviderImpl() + KakaoLoginProviderImpl() } DIContainer.register(type: SocialAuthenticatableProvider.self, name: "apple") { - return AppleLoginProviderImpl() + AppleLoginProviderImpl() } } func registerRepository() { DIContainer.register(type: AuthAPIRepository.self) { - return AuthAPIRepositoryImpl( + AuthAPIRepositoryImpl( provider: DIContainer.resolve(type: NetworkProvider.self), interceptor: TokenInterceptor(fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self)) ) } DIContainer.register(type: TokenRepository.self) { - return KeyChainRepositoryImpl() + KeyChainRepositoryImpl() + } + DIContainer.register(type: UserDefaultsRepository.self) { + UserDefaultsRepositoryImpl() } } @@ -71,121 +74,118 @@ private extension AppDelegate { return SocialLoginUseCaseImpl(provider: provider) } DIContainer.register(type: CheckEmptyLevelAndRoleUseCase.self) { - return CheckEmptyLevelAndRoleUseCaseImpl() + CheckEmptyLevelAndRoleUseCaseImpl() } DIContainer.register(type: CheckValidLevelUseCase.self) { - return CheckValidLevelUseCaseImpl() + CheckValidLevelUseCaseImpl() } DIContainer.register(type: FetchJobListUseCase.self) { - return FetchJobListUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + FetchJobListUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: LoginWithAppleUseCase.self) { - return LoginWithAppleUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + LoginWithAppleUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self), userDefaultsRepository: DIContainer.resolve(type: UserDefaultsRepository.self)) } DIContainer.register(type: LoginWithKakaoUseCase.self) { - return LoginWithKakaoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + LoginWithKakaoUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self), userDefaultsRepository: DIContainer.resolve(type: UserDefaultsRepository.self)) } DIContainer.register(type: SignUpWithAppleUseCase.self) { - return SignUpWithAppleUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + SignUpWithAppleUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: SignUpWithKakaoUseCase.self) { - return SignUpWithKakaoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + SignUpWithKakaoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: UpdateUserInfoUseCase.self) { - return UpdateUserInfoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + UpdateUserInfoUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: FetchTokenFromLocalUseCase.self) { - return FetchTokenFromLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) + FetchTokenFromLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) } DIContainer.register(type: SaveTokenToLocalUseCase.self) { - return SaveTokenToLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) + SaveTokenToLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) } DIContainer.register(type: DeleteTokenFromLocalUseCase.self) { - return DeleteTokenFromLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) + DeleteTokenFromLocalUseCaseImpl(repository: DIContainer.resolve(type: TokenRepository.self)) } DIContainer.register(type: UpdateMarketingAgreementUseCase.self) { - return UpdateMarketingAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self)) + UpdateMarketingAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self)) } DIContainer.register(type: PutFCMTokenUseCase.self) { - return PutFCMTokenUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) + PutFCMTokenUseCaseImpl(repository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: CheckNotificationPermissionUseCase.self) { - return CheckNotificationPermissionUseCaseImpl() + CheckNotificationPermissionUseCaseImpl() } DIContainer.register(type: OpenNotificationSettingUseCase.self) { - return OpenNotificationSettingUseCaseImpl() + OpenNotificationSettingUseCaseImpl() } DIContainer.register(type: UpdateNotificationAgreementUseCase.self) { - return UpdateNotificationAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self)) + UpdateNotificationAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self)) } DIContainer.register(type: FetchDictionaryMapListUseCase.self) { - return FetchDictionaryMapListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryMapListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryItemListUseCase.self) { - return FetchDictionaryItemListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryItemListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryQuestListUseCase.self) { - return FetchDictionaryQuestListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryQuestListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryNpcListUseCase.self) { - return FetchDictionaryNpcListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) + FetchDictionaryNpcListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryMonsterListUseCase.self) { - return FetchDictionaryMonsterListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) - } - DIContainer.register(type: FetchDictionaryItemsUseCase.self) { - return FetchDictionaryItemsUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListRepository.self)) + FetchDictionaryMonsterListUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailMonsterUseCase.self) { - return FetchDictionaryDetailMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailMonsterItemsUseCase.self) { - return FetchDictionaryDetailMonsterDropItemUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailMonsterDropItemUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailMonsterMapUseCase.self) { - return FetchDictionaryDetailMonsterMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailMonsterMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailNpcUseCase.self) { - return FetchDictionaryDetailNpcUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailNpcUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailNpcQuestUseCase.self) { - return FetchDictionaryDetailNpcQuestUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailNpcQuestUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailNpcMapUseCase.self) { - return FetchDictionaryDetailNpcMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailNpcMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailItemUseCase.self) { - return FetchDictionaryDetailItemUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailItemUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailItemDropMonsterUseCase.self) { - return FetchDictionaryDetailItemDropMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailItemDropMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailQuestUseCase.self) { - return FetchDictionaryDetailQuestUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailQuestUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailQuestLinkedQuestsUseCase.self) { - return FetchDictionaryDetailQuestLinkedQuestsUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailQuestLinkedQuestsUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailMapUseCase.self) { - return FetchDictionaryDetailMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailMapUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailMapSpawnMonsterUseCase.self) { - return FetchDictionaryDetailMapSpawnMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) + FetchDictionaryDetailMapSpawnMonsterUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchDictionaryDetailMapNpcUseCase.self) { - return FetchDictionaryDetailMapNpcUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) - } - DIContainer.register(type: ToggleBookmarkUseCase.self) { - return ToggleBookmarkUseCaseImpl(repository: DIContainer.resolve(type: DictionaryListRepository.self)) + FetchDictionaryDetailMapNpcUseCaseImpl(repository: DIContainer.resolve(type: DictionaryDetailAPIRepository.self)) } DIContainer.register(type: FetchNotificationUseCase.self) { - return FetchNotificationUseCaseImpl() + FetchNotificationUseCaseImpl() + } + DIContainer.register(type: FetchPlatformUseCase.self) { + FetchPlatformUseCaseImpl(repository: DIContainer.resolve(type: UserDefaultsRepository.self)) } } func registerFactory() { DIContainer.register(type: OnBoardingNotificationSheetFactory.self) { - return OnBoardingNotificationSheetFactoryImpl( + OnBoardingNotificationSheetFactoryImpl( checkNotificationPermissionUseCase: DIContainer .resolve(type: CheckNotificationPermissionUseCase.self), openNotificationSettingUseCase: DIContainer @@ -195,23 +195,24 @@ private extension AppDelegate { ) } DIContainer.register(type: OnBoardingNotificationFactory.self) { - return OnBoardingNotificationFactoryImpl(onBoardingNotificationSheetFactory: DIContainer.resolve(type: OnBoardingNotificationSheetFactory.self)) + OnBoardingNotificationFactoryImpl(onBoardingNotificationSheetFactory: DIContainer.resolve(type: OnBoardingNotificationSheetFactory.self)) } DIContainer.register(type: OnBoardingInputFactory.self) { - return OnBoardingInputFactoryImpl( + OnBoardingInputFactoryImpl( checkEmptyUseCase: DIContainer.resolve(type: CheckEmptyLevelAndRoleUseCase.self), checkValidLevelUseCase: DIContainer.resolve(type: CheckValidLevelUseCase.self), fetchJobListUseCase: DIContainer.resolve(type: FetchJobListUseCase.self), updateUserInfoUseCase: DIContainer.resolve(type: UpdateUserInfoUseCase.self), - onBoardingNotificationFactory: DIContainer.resolve(type: OnBoardingNotificationFactory.self)) + onBoardingNotificationFactory: DIContainer.resolve(type: OnBoardingNotificationFactory.self) + ) } DIContainer.register(type: OnBoardingQuestionFactory.self) { - return OnBoardingQuestionFactoryImpl( + OnBoardingQuestionFactoryImpl( onBoardingInputFactory: DIContainer.resolve(type: OnBoardingInputFactory.self) ) } DIContainer.register(type: TermsAgreementFactory.self) { - return TermsAgreementFactoryImpl( + TermsAgreementFactoryImpl( onBoardingQuestionFactory: DIContainer.resolve(type: OnBoardingQuestionFactory.self), signUpWithKakaoUseCase: DIContainer.resolve(type: SignUpWithKakaoUseCase.self), signUpWithAppleUseCase: DIContainer.resolve(type: SignUpWithAppleUseCase.self), @@ -220,14 +221,15 @@ private extension AppDelegate { ) } DIContainer.register(type: LoginFactory.self) { - return LoginFactoryImpl( + LoginFactoryImpl( termsAgreementsFactory: DIContainer.resolve(type: TermsAgreementFactory.self), appleLoginUseCase: DIContainer.resolve(type: FetchSocialCredentialUseCase.self, name: "apple"), kakaoLoginUseCase: DIContainer.resolve(type: FetchSocialCredentialUseCase.self, name: "kakao"), loginWithAppleUseCase: DIContainer.resolve(type: LoginWithAppleUseCase.self), loginWithKakaoUseCase: DIContainer.resolve(type: LoginWithKakaoUseCase.self), fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self), - putFCMTokenUseCase: DIContainer.resolve(type: PutFCMTokenUseCase.self) + putFCMTokenUseCase: DIContainer.resolve(type: PutFCMTokenUseCase.self), + fetchPlatformUseCase: DIContainer.resolve(type: FetchPlatformUseCase.self) ) } } diff --git a/MLS/Presentation/AuthFeature/AuthFeatureDemo/ViewController.swift b/MLS/Presentation/AuthFeature/AuthFeatureDemo/ViewController.swift index d9d8a7de..75594d1d 100644 --- a/MLS/Presentation/AuthFeature/AuthFeatureDemo/ViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeatureDemo/ViewController.swift @@ -18,7 +18,7 @@ class ViewController: UIViewController { }() lazy var views: [UIViewController] = { - let loginVC = DIContainer.resolve(type: LoginFactory.self).make(isReLogin: false) + let loginVC = DIContainer.resolve(type: LoginFactory.self).make(exitRoute: .pop) loginVC.title = "로그인" let termVC = DIContainer.resolve(type: TermsAgreementFactory.self).make(credential: KakaoCredential(token: "", providerID: ""), platform: .apple) diff --git a/MLS/Presentation/AuthFeature/AuthFeatureInterface/LoginExitRoute.swift b/MLS/Presentation/AuthFeature/AuthFeatureInterface/LoginExitRoute.swift new file mode 100644 index 00000000..6807d771 --- /dev/null +++ b/MLS/Presentation/AuthFeature/AuthFeatureInterface/LoginExitRoute.swift @@ -0,0 +1,6 @@ +import UIKit + +public enum LoginExitRoute { + case pop + case home +} diff --git a/MLS/Presentation/AuthFeature/AuthFeatureInterface/LoginFactory.swift b/MLS/Presentation/AuthFeature/AuthFeatureInterface/LoginFactory.swift index 5d26770e..4231837f 100644 --- a/MLS/Presentation/AuthFeature/AuthFeatureInterface/LoginFactory.swift +++ b/MLS/Presentation/AuthFeature/AuthFeatureInterface/LoginFactory.swift @@ -1,5 +1,11 @@ import BaseFeature public protocol LoginFactory { - func make(isReLogin: Bool) -> BaseViewController + func make(exitRoute: LoginExitRoute, onLoginCompleted: (() -> Void)?) -> BaseViewController +} + +public extension LoginFactory { + func make(exitRoute: LoginExitRoute) -> BaseViewController { + make(exitRoute: exitRoute, onLoginCompleted: nil) + } } diff --git a/MLS/Presentation/BaseFeature/BaseFeature/Base/BaseErrorViewController.swift b/MLS/Presentation/BaseFeature/BaseFeature/Base/BaseErrorViewController.swift index b4c701b9..6646537e 100644 --- a/MLS/Presentation/BaseFeature/BaseFeature/Base/BaseErrorViewController.swift +++ b/MLS/Presentation/BaseFeature/BaseFeature/Base/BaseErrorViewController.swift @@ -18,7 +18,7 @@ public final class BaseErrorViewController: BaseViewController { } // MARK: - Properties - var disposeBag = DisposeBag() + private var disposeBag = DisposeBag() private let containerView: UIView = UIView() diff --git a/MLS/Presentation/BaseFeature/BaseFeature/SharedView/BaseListView.swift b/MLS/Presentation/BaseFeature/BaseFeature/SharedView/BaseListView.swift index f94c6466..4709b86f 100644 --- a/MLS/Presentation/BaseFeature/BaseFeature/SharedView/BaseListView.swift +++ b/MLS/Presentation/BaseFeature/BaseFeature/SharedView/BaseListView.swift @@ -157,6 +157,5 @@ public extension BaseListView { emptyView.isHidden = !isEmpty filterStackView.isHidden = isEmpty listCollectionView.isHidden = isEmpty - isUserInteractionEnabled = !isEmpty } } diff --git a/MLS/Presentation/BaseFeature/BaseFeature/Utills/AppRouter.swift b/MLS/Presentation/BaseFeature/BaseFeature/Utills/AppRouter.swift new file mode 100644 index 00000000..1a74585b --- /dev/null +++ b/MLS/Presentation/BaseFeature/BaseFeature/Utills/AppRouter.swift @@ -0,0 +1,20 @@ +import UIKit + +public enum AppRouter { + public static func setRoot(_ viewController: UIViewController, animated: Bool = true) { + guard let window = UIApplication.shared.connectedScenes + .compactMap({ $0 as? UIWindowScene }) + .flatMap({ $0.windows }) + .first(where: { $0.isKeyWindow }) else { return } + + if animated { + UIView.transition(with: window, duration: 0.3, options: .transitionCrossDissolve, animations: { + window.rootViewController = viewController + }) + } else { + window.rootViewController = viewController + } + + window.makeKeyAndVisible() + } +} diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/AddCollection/AddCollectionViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/AddCollection/AddCollectionViewController.swift index 5bb00700..cd42f150 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/AddCollection/AddCollectionViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/AddCollection/AddCollectionViewController.swift @@ -14,6 +14,7 @@ public final class AddCollectionViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() + public var onDismissWithMessage: ((BookmarkCollection?) -> Void)? // MARK: - Components diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift index 07ee5628..53528256 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift @@ -54,7 +54,17 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { } public func make(type: DictionaryType, listType: DictionaryMainViewType) -> BaseViewController { - let reactor = BookmarkListReactor(type: type, checkLoginUseCase: checkLoginUseCase, setBookmarkUseCase: setBookmarkUseCase, fetchBookmarkUseCase: fetchBookmarkUseCase, fetchMonsterBookmarkUseCase: fetchMonsterBookmarkUseCase, fetchItemBookmarkUseCase: fetchItemBookmarkUseCase, fetchNPCBookmarkUseCase: fetchNPCBookmarkUseCase, fetchQuestBookmarkUseCase: fetchQuestBookmarkUseCase, fetchMapBookmarkUseCase: fetchMapBookmarkUseCase) + let reactor = BookmarkListReactor( + type: type, + checkLoginUseCase: checkLoginUseCase, + setBookmarkUseCase: setBookmarkUseCase, + fetchBookmarkUseCase: fetchBookmarkUseCase, + fetchMonsterBookmarkUseCase: fetchMonsterBookmarkUseCase, + fetchItemBookmarkUseCase: fetchItemBookmarkUseCase, + fetchNPCBookmarkUseCase: fetchNPCBookmarkUseCase, + fetchQuestBookmarkUseCase: fetchQuestBookmarkUseCase, + fetchMapBookmarkUseCase: fetchMapBookmarkUseCase + ) let viewController = BookmarkListViewController( reactor: reactor, itemFilterFactory: itemFilterFactory, diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListView.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListView.swift index 57c07196..e70894a3 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListView.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListView.swift @@ -34,11 +34,13 @@ extension BookmarkListView { case .loginWithoutData: checkEmptyData(isEmpty: true) if let emptyView = emptyView as? BookmarkEmptyView { + checkEmptyData(isEmpty: true) emptyView.setLabel(isLogin: true) } case .logout: if let emptyView = emptyView as? BookmarkEmptyView { + checkEmptyData(isEmpty: true) emptyView.setLabel(isLogin: false) } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift index b2c5ae40..50b70dfb 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift @@ -165,7 +165,7 @@ extension BookmarkListViewController { let vc = owner.dictionaryDetailFactory.make(type: type, id: id) owner.navigationController?.pushViewController(vc, animated: true) case .login: - let vc = owner.loginFactory.make(isReLogin: false) + let vc = owner.loginFactory.make(exitRoute: .pop) owner.navigationController?.pushViewController(vc, animated: true) case .dictionary: @@ -239,7 +239,7 @@ extension BookmarkListViewController: UICollectionViewDelegate, UICollectionView ctaText: "로그인 하기", cancelText: "취소", ctaAction: { - let viewController = self.loginFactory.make(isReLogin: false) + let viewController = self.loginFactory.make(exitRoute: .pop) self.navigationController?.pushViewController(viewController, animated: true) }, cancelAction: nil diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainViewController.swift index 20d9c6bd..43f580c8 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainViewController.swift @@ -14,6 +14,7 @@ public final class BookmarkMainViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() + private let initialIndex: Int private lazy var currentPageIndex = BehaviorRelay(value: initialIndex) diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkModal/BookmarkModalViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkModal/BookmarkModalViewController.swift index 5b5bcb3d..20cb1f97 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkModal/BookmarkModalViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkModal/BookmarkModalViewController.swift @@ -12,12 +12,12 @@ public final class BookmarkModalViewController: BaseViewController, View { public typealias Reactor = BookmarkModalReactor // MARK: - Properties - private let addCollectionFactory: AddCollectionFactory + public var disposeBag = DisposeBag() public var onDismissWithMessage: ((BookmarkCollection?) -> Void)? public var onDismissWithCollections: (([BookmarkCollection?]) -> Void)? - - public var disposeBag = DisposeBag() + + private let addCollectionFactory: AddCollectionFactory // MARK: - Components private let mainView = BookmarkModalView() diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift index 8d529a02..d5417a6e 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift @@ -13,13 +13,13 @@ public final class CollectionDetailViewController: BaseViewController, View { public typealias Reactor = CollectionDetailReactor // MARK: - Properties + public var disposeBag = DisposeBag() + private let bookmarkModalFactory: BookmarkModalFactory private let collectionSettingFactory: CollectionSettingFactory private let addCollectionFactory: AddCollectionFactory private let collectionEditFactory: CollectionEditFactory - public var disposeBag = DisposeBag() - private var selectedSortIndex = 0 // MARK: - Components diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditViewController.swift index c97eee38..06fb8bd4 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditViewController.swift @@ -12,7 +12,7 @@ public final class CollectionEditViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let bookmarkModalFactory: BookmarkModalFactory // MARK: - Components diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionList/CollectionListViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionList/CollectionListViewController.swift index 0947e0a2..d63c2f28 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionList/CollectionListViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionList/CollectionListViewController.swift @@ -10,11 +10,12 @@ public final class CollectionListViewController: BaseViewController, View { public typealias Reactor = CollectionListReactor // MARK: - Properties - private let addCollectionFactory: AddCollectionFactory - private let detailFactory: CollectionDetailFactory - public var disposeBag = DisposeBag() + public var onDismissWithMessage: ((BookmarkCollection?) -> Void)? + + private let addCollectionFactory: AddCollectionFactory + private let detailFactory: CollectionDetailFactory // MARK: - Components private var mainView = CollectionListView() diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionSettingSheet/CollectionSettingViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionSettingSheet/CollectionSettingViewController.swift index 4e2ab091..082b11c4 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionSettingSheet/CollectionSettingViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionSettingSheet/CollectionSettingViewController.swift @@ -15,6 +15,7 @@ public final class CollectionSettingViewController: BaseViewController, ModalPre // MARK: - Properties public var disposeBag = DisposeBag() + public var setMenu: ((CollectionSettingMenu) -> Void)? private var mainView = CollectionSettingView() diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift index fc84edb8..3dfa35d1 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeatureDemo/AppDelegate.swift @@ -1,4 +1,5 @@ // swiftlint:disable function_body_length +// swiftlint:disable line_length import UIKit diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift index d88cd12e..4cd7a15a 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift @@ -239,7 +239,15 @@ extension DictionaryDetailBaseViewController { currentTabIndex = index } - func bindBookmarkButton(buttonTap: ControlEvent, currentItem: Observable, isLogin: @escaping () -> Bool, imageUrl: @escaping (T) -> String?, backgroundColor: UIColor, isBookmarked: @escaping (T) -> Bool, toggleBookmark: @escaping (Bool) -> Void, undoLastDeleted: @escaping () -> Void) -> Disposable { + func bindBookmarkButton( + buttonTap: ControlEvent, + currentItem: Observable, + isLogin: @escaping () -> Bool, + imageUrl: @escaping (T) -> String?, + backgroundColor: UIColor, + isBookmarked: @escaping (T) -> Bool, + toggleBookmark: @escaping (Bool) -> Void, + undoLastDeleted: @escaping () -> Void) -> Disposable { buttonTap .withLatestFrom(currentItem) .observe(on: MainScheduler.instance) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailFactoryImpl.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailFactoryImpl.swift index 58e11bd1..ffc05c73 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailFactoryImpl.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailFactoryImpl.swift @@ -22,7 +22,24 @@ public final class DictionaryDetailFactoryImpl: DictionaryDetailFactory { private let checkLoginUseCase: CheckLoginUseCase private let setBookmarkUseCase: SetBookmarkUseCase - public init(bookmarkModalFactory: BookmarkModalFactory, dictionaryDetailMapUseCase: FetchDictionaryDetailMapUseCase, dictionaryDetailMapSpawnMonsterUseCase: FetchDictionaryDetailMapSpawnMonsterUseCase, dictionaryDetailMapNpcUseCase: FetchDictionaryDetailMapNpcUseCase, dictionaryDetailQuestLinkedQuestsUseCase: FetchDictionaryDetailQuestLinkedQuestsUseCase, dictionaryDetailQuestUseCase: FetchDictionaryDetailQuestUseCase, dictionaryDetailItemDropMonsterUseCase: FetchDictionaryDetailItemDropMonsterUseCase, dictionaryDetailItemUseCase: FetchDictionaryDetailItemUseCase, dictionaryDetailNpcUseCase: FetchDictionaryDetailNpcUseCase, dictionaryDetailNpcQuestUseCase: FetchDictionaryDetailNpcQuestUseCase, dictionaryDetailNpcMapUseCase: FetchDictionaryDetailNpcMapUseCase, dictionaryDetailMonsterUseCase: FetchDictionaryDetailMonsterUseCase, dictionaryDetailMonsterDropItemUseCase: FetchDictionaryDetailMonsterItemsUseCase, dictionaryDetailMonsterMapUseCase: FetchDictionaryDetailMonsterMapUseCase, checkLoginUseCase: CheckLoginUseCase, setBookmarkUseCase: SetBookmarkUseCase) { + public init( + bookmarkModalFactory: BookmarkModalFactory, + dictionaryDetailMapUseCase: FetchDictionaryDetailMapUseCase, + dictionaryDetailMapSpawnMonsterUseCase: FetchDictionaryDetailMapSpawnMonsterUseCase, + dictionaryDetailMapNpcUseCase: FetchDictionaryDetailMapNpcUseCase, + dictionaryDetailQuestLinkedQuestsUseCase: FetchDictionaryDetailQuestLinkedQuestsUseCase, + dictionaryDetailQuestUseCase: FetchDictionaryDetailQuestUseCase, + dictionaryDetailItemDropMonsterUseCase: FetchDictionaryDetailItemDropMonsterUseCase, + dictionaryDetailItemUseCase: FetchDictionaryDetailItemUseCase, + dictionaryDetailNpcUseCase: FetchDictionaryDetailNpcUseCase, + dictionaryDetailNpcQuestUseCase: FetchDictionaryDetailNpcQuestUseCase, + dictionaryDetailNpcMapUseCase: FetchDictionaryDetailNpcMapUseCase, + dictionaryDetailMonsterUseCase: FetchDictionaryDetailMonsterUseCase, + dictionaryDetailMonsterDropItemUseCase: FetchDictionaryDetailMonsterItemsUseCase, + dictionaryDetailMonsterMapUseCase: FetchDictionaryDetailMonsterMapUseCase, + checkLoginUseCase: CheckLoginUseCase, + setBookmarkUseCase: SetBookmarkUseCase + ) { self.bookmarkModalFactory = bookmarkModalFactory self.dictionaryDetailMapUseCase = dictionaryDetailMapUseCase self.dictionaryDetailMapSpawnMonsterUseCase = dictionaryDetailMapSpawnMonsterUseCase @@ -56,18 +73,39 @@ public final class DictionaryDetailFactoryImpl: DictionaryDetailFactory { } case .monster: viewController = MonsterDictionaryDetailViewController(type: .monster, bookmarkModalFactory: bookmarkModalFactory) - let reactor = MonsterDictionaryDetailReactor(dictionaryDetailMonsterUseCase: dictionaryDetailMonsterUseCase, dictionaryDetailMonsterDropItemUseCase: dictionaryDetailMonsterDropItemUseCase, dictionaryDetailMonsterMapUseCase: dictionaryDetailMonsterMapUseCase, checkLoginUseCase: checkLoginUseCase, setBookmarkUseCase: setBookmarkUseCase, id: id) + let reactor = MonsterDictionaryDetailReactor( + dictionaryDetailMonsterUseCase: dictionaryDetailMonsterUseCase, + dictionaryDetailMonsterDropItemUseCase: dictionaryDetailMonsterDropItemUseCase, + dictionaryDetailMonsterMapUseCase: dictionaryDetailMonsterMapUseCase, + checkLoginUseCase: checkLoginUseCase, + setBookmarkUseCase: setBookmarkUseCase, + id: id + ) if let viewController = viewController as? MonsterDictionaryDetailViewController { viewController.reactor = reactor } case .map: - let reactor = MapDictionaryDetailReactor(dictionaryDetailMapUseCase: dictionaryDetailMapUseCase, dictionaryDetailMapSpawnMonsterUseCase: dictionaryDetailMapSpawnMonsterUseCase, dictionaryDetailMapNpcUseCase: dictionaryDetailMapNpcUseCase, checkLoginUseCase: checkLoginUseCase, setBookmarkUseCase: setBookmarkUseCase, id: id) + let reactor = MapDictionaryDetailReactor( + dictionaryDetailMapUseCase: dictionaryDetailMapUseCase, + dictionaryDetailMapSpawnMonsterUseCase: dictionaryDetailMapSpawnMonsterUseCase, + dictionaryDetailMapNpcUseCase: dictionaryDetailMapNpcUseCase, + checkLoginUseCase: checkLoginUseCase, + setBookmarkUseCase: setBookmarkUseCase, + id: id + ) viewController = MapDictionaryDetailViewController(type: .map, bookmarkModalFactory: bookmarkModalFactory) if let viewController = viewController as? MapDictionaryDetailViewController { viewController.reactor = reactor } case .npc: - let reactor = NpcDictionaryDetailReactor(dictionaryDetailNpcUseCase: dictionaryDetailNpcUseCase, dictionaryDetailNpcQuestUseCase: dictionaryDetailNpcQuestUseCase, dictionaryDetailNpcMapUseCase: dictionaryDetailNpcMapUseCase, checkLoginUseCase: checkLoginUseCase, setBookmarkUseCase: setBookmarkUseCase, id: id) + let reactor = NpcDictionaryDetailReactor( + dictionaryDetailNpcUseCase: dictionaryDetailNpcUseCase, + dictionaryDetailNpcQuestUseCase: dictionaryDetailNpcQuestUseCase, + dictionaryDetailNpcMapUseCase: dictionaryDetailNpcMapUseCase, + checkLoginUseCase: checkLoginUseCase, + setBookmarkUseCase: setBookmarkUseCase, + id: id + ) viewController = NpcDictionaryDetailViewController(type: .npc, bookmarkModalFactory: bookmarkModalFactory) if let viewController = viewController as? NpcDictionaryDetailViewController { viewController.reactor = reactor diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Map/MapDictionaryDetailReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Map/MapDictionaryDetailReactor.swift index 7dbb1e33..eb8388c2 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Map/MapDictionaryDetailReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Map/MapDictionaryDetailReactor.swift @@ -46,7 +46,14 @@ public final class MapDictionaryDetailReactor: Reactor { public var initialState: State private let disposBag = DisposeBag() - public init(dictionaryDetailMapUseCase: FetchDictionaryDetailMapUseCase, dictionaryDetailMapSpawnMonsterUseCase: FetchDictionaryDetailMapSpawnMonsterUseCase, dictionaryDetailMapNpcUseCase: FetchDictionaryDetailMapNpcUseCase, checkLoginUseCase: CheckLoginUseCase, setBookmarkUseCase: SetBookmarkUseCase, id: Int) { + public init( + dictionaryDetailMapUseCase: FetchDictionaryDetailMapUseCase, + dictionaryDetailMapSpawnMonsterUseCase: FetchDictionaryDetailMapSpawnMonsterUseCase, + dictionaryDetailMapNpcUseCase: FetchDictionaryDetailMapNpcUseCase, + checkLoginUseCase: CheckLoginUseCase, + setBookmarkUseCase: SetBookmarkUseCase, + id: Int + ) { initialState = State(mapDetailInfo: DictionaryDetailMapResponse(mapId: nil, nameKr: nil, nameEn: nil, regionName: nil, detailName: nil, topRegionName: nil, mapUrl: nil, iconUrl: nil, bookmarkId: nil), spawnMonsters: [], npcs: [], type: .map, id: id) self.dictionaryDetailMapUseCase = dictionaryDetailMapUseCase diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Quest/QuestDictionaryDetailReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Quest/QuestDictionaryDetailReactor.swift index f0f5a4b9..ae407c66 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Quest/QuestDictionaryDetailReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Quest/QuestDictionaryDetailReactor.swift @@ -47,7 +47,26 @@ public final class QuestDictionaryDetailReactor: Reactor { self.setBookmarkUseCase = setBookmarkUseCase self.initialState = .init( id: id, - detailInfo: .init(questId: nil, titlePrefix: nil, nameKr: nil, nameEn: nil, iconUrl: nil, questType: nil, minLevel: nil, maxLevel: nil, requiredMesoStart: nil, startNpcId: nil, startNpcName: nil, endNpcId: nil, endNpcName: nil, reward: nil, rewardItems: nil, requirements: nil, allowedJobs: nil, bookmarkId: nil), + detailInfo: .init( + questId: nil, + titlePrefix: nil, + nameKr: nil, + nameEn: nil, + iconUrl: nil, + questType: nil, + minLevel: nil, + maxLevel: nil, + requiredMesoStart: nil, + startNpcId: nil, + startNpcName: nil, + endNpcId: nil, + endNpcName: nil, + reward: nil, + rewardItems: nil, + requirements: nil, + allowedJobs: nil, + bookmarkId: nil + ), linkedQuestInfo: .init(previousQuests: nil, nextQuests: nil) ) } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift index df13a133..51ab261c 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift @@ -14,6 +14,7 @@ public final class DictionaryMainViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() + private let initialIndex: Int private lazy var currentPageIndex = BehaviorRelay(value: initialIndex) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift index 82d5fd18..dbb9ab41 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift @@ -13,11 +13,12 @@ public final class DictionarySearchViewController: BaseViewController, View { public typealias Reactor = DictionarySearchReactor // MARK: - Properties + public var disposeBag = DisposeBag() + private var searchResultFactory: DictionarySearchResultFactory private let chipTapRelay = PublishRelay() private let chipCancelRelay = PublishRelay() - public var disposeBag = DisposeBag() // MARK: - Components private let mainView = DictionarySearchView() diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultViewController.swift index 7a510c21..11732a41 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultViewController.swift @@ -14,6 +14,7 @@ public final class DictionarySearchResultViewController: BaseViewController, Vie // MARK: - Properties public var disposeBag = DisposeBag() + private let initialIndex: Int private lazy var currentPageIndex = BehaviorRelay(value: initialIndex) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift index c588ff7b..cb89f508 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/ItemFilterBottomSheet/ItemFilterBottomSheetViewController.swift @@ -1,3 +1,5 @@ +// swiftlint:disable all + import UIKit import BaseFeature @@ -424,7 +426,6 @@ extension ItemFilterBottomSheetViewController { } .bind(to: reactor.action) .disposed(by: disposeBag) - } func bindViewState(reactor: Reactor) { diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/SortedBottomSheet/SortedBottomSheetViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/SortedBottomSheet/SortedBottomSheetViewController.swift index 8d92b536..5b6fcd92 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/SortedBottomSheet/SortedBottomSheetViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/SortedBottomSheet/SortedBottomSheetViewController.swift @@ -16,6 +16,7 @@ public final class SortedBottomSheetViewController: BaseViewController, ModalPre // MARK: - Properties public var disposeBag = DisposeBag() + public var onSelectedIndex: ((Int) -> Void)? // MARK: - Components diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift index f93808a5..b74269b4 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift @@ -1,4 +1,5 @@ // swiftlint:disable function_body_length +// swiftlint:disable line_length import UIKit diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Announcement/AnnouncementViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Announcement/AnnouncementViewController.swift index a759a41c..66b658cc 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Announcement/AnnouncementViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Announcement/AnnouncementViewController.swift @@ -6,7 +6,7 @@ import ReactorKit final class AnnouncementViewController: CustomerSupportBaseViewController, View { typealias Reactor = AnnouncementReactor - + // MARK: - Init override init(type: CustomerSupportType) { super.init(type: type) diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift index 888f7c6c..e02b49fb 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift @@ -12,17 +12,16 @@ import RxSwift */ class CustomerSupportBaseViewController: BaseViewController { // MARK: - Properties - public var disposeBag = DisposeBag() - - // MARK: - Components - public var mainView = CustomerSupportBaseView() - public var type: CustomerSupportType - + /// 현재 보여지고 있는 뷰의 인덱스 public var currentTabIndex: Int? public var urlStrings: [String] = [] var onItemTapped: ((Int) -> Void)? + + // MARK: - Components + public var mainView = CustomerSupportBaseView() + public var type: CustomerSupportType public init(type: CustomerSupportType) { self.type = type diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift index 314ee9da..72c40bf7 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift @@ -18,7 +18,7 @@ public final class MyPageMainViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let setProfileFactory: SetProfileFactory private let customerSupportFactory: CustomerSupportFactory private let notificationSettingFactory: NotificationSettingFactory @@ -155,7 +155,7 @@ extension MyPageMainViewController { let viewController = owner.notificationSettingFactory.make() owner.navigationController?.pushViewController(viewController, animated: true) case .login: - let viewController = owner.loginFactory.make(isReLogin: false) + let viewController = owner.loginFactory.make(exitRoute: .pop) owner.navigationController?.pushViewController(viewController, animated: true) case .none: break diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingViewController.swift index d6114dbf..e78a33f5 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingViewController.swift @@ -8,9 +8,10 @@ import RxSwift final class NotificationSettingViewController: BaseViewController, View, UNUserNotificationCenterDelegate { typealias Reactor = NotificationSettingReactor + + public var disposeBag = DisposeBag() // MARK: - Properties - var disposeBag = DisposeBag() // MARK: - UI Components private let mainView = NotificationSettingView() diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift index 0571acc7..51339c81 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift @@ -11,11 +11,11 @@ import RxSwift import SnapKit public final class SelectImageViewContoller: BaseViewController, ModalPresentable, View { + // 수정필요 public var modalHeight: CGFloat? = 16 + 32 + UIScreen.main.bounds.size.width + 4 + 24 + 54 + 4 public typealias Reactor = SelectImageReactor - - // MARK: - Properties + public var disposeBag = DisposeBag() // MARK: - Components diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SetCharacter/SetCharacterViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SetCharacter/SetCharacterViewController.swift index 2945fc77..a50490ea 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SetCharacter/SetCharacterViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SetCharacter/SetCharacterViewController.swift @@ -12,9 +12,10 @@ import SnapKit public class SetCharacterViewController: BaseViewController, View { // MARK: - Properties public typealias Reactor = SetCharacterReactor + + public var disposeBag = DisposeBag() // MARK: - Components - public var disposeBag = DisposeBag() private var mainView = SetCharacterView() } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileViewController.swift index 6f96cd8d..a4070d53 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileViewController.swift @@ -18,6 +18,7 @@ public final class SetProfileViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() + var didReturn = PublishRelay() private var selectImageFactory: SelectImageFactory diff --git a/MLS/Presentation/MyPageFeature/MyPageFeatureDemo/AppDelegate.swift b/MLS/Presentation/MyPageFeature/MyPageFeatureDemo/AppDelegate.swift index d2ce7a01..2147726d 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeatureDemo/AppDelegate.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeatureDemo/AppDelegate.swift @@ -1,3 +1,6 @@ +// swiftlint:disable function_body_length +// swiftlint:disable line_length + import UIKit import AuthFeatureInterface From 7dd6bd3e27b3f72c3302e59735a86c19242a5eed Mon Sep 17 00:00:00 2001 From: p2glet Date: Wed, 5 Nov 2025 23:28:04 +0900 Subject: [PATCH 04/14] =?UTF-8?q?fix/#264:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Repository/AuthAPIRepositoryImpl.swift | 2 +- .../AuthAPI/LoginWithAppleUseCaseImpl.swift | 10 ++++++---- .../AuthAPI/LoginWithKakaoUseCaseImpl.swift | 13 ++++++++----- .../MyPage/FetchProfileUseCaseImpl.swift | 8 ++++---- .../Repository/AuthAPIRepository.swift | 2 +- .../UseCase/MyPage/FetchProfileUseCase.swift | 2 +- MLS/MLS/Application/SceneDelegate.swift | 3 ++- .../MyPageFeature/Main/MyPageMainReactor.swift | 6 +++++- 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/MLS/Data/Data/Repository/AuthAPIRepositoryImpl.swift b/MLS/Data/Data/Repository/AuthAPIRepositoryImpl.swift index 9f668321..7874b605 100644 --- a/MLS/Data/Data/Repository/AuthAPIRepositoryImpl.swift +++ b/MLS/Data/Data/Repository/AuthAPIRepositoryImpl.swift @@ -13,7 +13,7 @@ public class AuthAPIRepositoryImpl: AuthAPIRepository { self.tokenInterceptor = interceptor } - public func fetchProfile() -> Observable { + public func fetchProfile() -> Observable { let endpoint = AuthEndPoint.fetchProfile() return provider.requestData(endPoint: endpoint, interceptor: tokenInterceptor) .map { $0.toMyPageDomain() } diff --git a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithAppleUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithAppleUseCaseImpl.swift index 1c625aee..fbff7c74 100644 --- a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithAppleUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithAppleUseCaseImpl.swift @@ -20,14 +20,16 @@ public class LoginWithAppleUseCaseImpl: LoginWithAppleUseCase { return authRepository.loginWithApple(credential: credential) .flatMap { response -> Observable in let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) - + let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) let savePlatform = self.userDefaultsRepository.savePlatform(platform: .apple) - switch saveAccess { - case .success: + switch (saveAccess, saveRefresh) { + case (.success, .success): return savePlatform.andThen(Observable.just(response)) default: - return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) + return Observable.error( + TokenRepositoryError.dataConversionError(message: "Failed to save tokens") + ) } } .catch { error in diff --git a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithKakaoUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithKakaoUseCaseImpl.swift index ee8028e3..6912e4b6 100644 --- a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithKakaoUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/LoginWithKakaoUseCaseImpl.swift @@ -20,14 +20,17 @@ public class LoginWithKakaoUseCaseImpl: LoginWithKakaoUseCase { return authRepository.loginWithKakao(credential: credential) .flatMap { response -> Observable in let saveAccess = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + let saveRefresh = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) + let savePlatform = self.userDefaultsRepository.savePlatform(platform: .apple) - let savePlatform = self.userDefaultsRepository.savePlatform(platform: .kakao) - - switch saveAccess { - case .success: + // ✅ 모든 저장 결과 확인 + switch (saveAccess, saveRefresh) { + case (.success, .success): return savePlatform.andThen(Observable.just(response)) default: - return Observable.error(TokenRepositoryError.dataConversionError(message: "Failed to save tokens")) + return Observable.error( + TokenRepositoryError.dataConversionError(message: "Failed to save tokens") + ) } } .catch { error in diff --git a/MLS/Domain/Domain/UseCaseImpl/MyPage/FetchProfileUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/MyPage/FetchProfileUseCaseImpl.swift index ae7936d8..6353a869 100644 --- a/MLS/Domain/Domain/UseCaseImpl/MyPage/FetchProfileUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/MyPage/FetchProfileUseCaseImpl.swift @@ -11,17 +11,17 @@ public class FetchProfileUseCaseImpl: FetchProfileUseCase { self.fetchJobUseCase = fetchJobUseCase } - public func execute() -> Observable { + public func execute() -> Observable { return repository.fetchProfile() - .flatMap { [weak self] profile -> Observable in - guard let self = self, let jobId = profile.jobId else { + .flatMap { [weak self] profile -> Observable in + guard let self = self, let jobId = profile?.jobId else { return .just(profile) } return self.fetchJobUseCase.execute(jobId: String(jobId)) .map { job in var new = profile - new.jobName = job.name + new?.jobName = job.name return new } } diff --git a/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift b/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift index adc7d437..dff7bb8d 100644 --- a/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift +++ b/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift @@ -64,5 +64,5 @@ public protocol AuthAPIRepository { func updateProfileImage(url: String) -> Completable - func fetchProfile() -> Observable + func fetchProfile() -> Observable } diff --git a/MLS/Domain/DomainInterface/UseCase/MyPage/FetchProfileUseCase.swift b/MLS/Domain/DomainInterface/UseCase/MyPage/FetchProfileUseCase.swift index e97b5124..f8176cb4 100644 --- a/MLS/Domain/DomainInterface/UseCase/MyPage/FetchProfileUseCase.swift +++ b/MLS/Domain/DomainInterface/UseCase/MyPage/FetchProfileUseCase.swift @@ -1,5 +1,5 @@ import RxSwift public protocol FetchProfileUseCase { - func execute() -> Observable + func execute() -> Observable } diff --git a/MLS/MLS/Application/SceneDelegate.swift b/MLS/MLS/Application/SceneDelegate.swift index a35df616..979f6eb8 100644 --- a/MLS/MLS/Application/SceneDelegate.swift +++ b/MLS/MLS/Application/SceneDelegate.swift @@ -61,7 +61,8 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { coordinator.showLogin(exitRoute: .home) } }, - onError: { _ in + onError: { error in + print(error) coordinator.showLogin(exitRoute: .home) } ) diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainReactor.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainReactor.swift index 29154718..89617b49 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainReactor.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainReactor.swift @@ -69,7 +69,7 @@ public final class MyPageMainReactor: Reactor { // MARK: - Mutation public enum Mutation { case toNavigate(Route) - case setProfile(MyPageResponse) + case setProfile(MyPageResponse?) } // MARK: - State @@ -113,6 +113,10 @@ public final class MyPageMainReactor: Reactor { case .viewWillAppear: return fetchProfileUseCase.execute() .map { .setProfile($0) } + .catch { error in + print(error) + return .just(.setProfile(nil)) + } } } From 7146358fd7b8be3d9cb3e127fc6e496b180b4419 Mon Sep 17 00:00:00 2001 From: p2glet Date: Thu, 6 Nov 2025 23:54:28 +0900 Subject: [PATCH 05/14] =?UTF-8?q?fix/#264:=20=EC=BB=AC=EB=A0=89=EC=85=98?= =?UTF-8?q?=20->=20=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLS/Application/AppCoordinator.swift | 7 +++- MLS/MLS/Application/AppDelegate.swift | 4 +- .../CollectionDetailEmptyView.swift | 2 - .../CollectionDetailFactoryImpl.swift | 8 +++- .../CollectionDetailReactor.swift | 40 ++++++++++++++----- .../CollectionDetailView.swift | 13 +++--- .../CollectionDetailViewController.swift | 19 ++++++++- .../CollectionListViewController.swift | 4 +- 8 files changed, 70 insertions(+), 27 deletions(-) diff --git a/MLS/MLS/Application/AppCoordinator.swift b/MLS/MLS/Application/AppCoordinator.swift index df326387..520823cf 100644 --- a/MLS/MLS/Application/AppCoordinator.swift +++ b/MLS/MLS/Application/AppCoordinator.swift @@ -1,4 +1,3 @@ -import UIKit import AuthFeature import AuthFeatureInterface import BaseFeature @@ -6,6 +5,7 @@ import BookmarkFeatureInterface import DesignSystem import DictionaryFeatureInterface import MyPageFeatureInterface +import UIKit import RxSwift @@ -41,7 +41,10 @@ public final class AppCoordinator { bookmarkMainFactory.make(), myPageMainFactory.make() ]) - setRoot(tabBar) + + let navigationController = UINavigationController(rootViewController: tabBar) + navigationController.isNavigationBarHidden = true + setRoot(navigationController) } public func showLogin(exitRoute: LoginExitRoute) { diff --git a/MLS/MLS/Application/AppDelegate.swift b/MLS/MLS/Application/AppDelegate.swift index 620fc1c3..3d58435c 100644 --- a/MLS/MLS/Application/AppDelegate.swift +++ b/MLS/MLS/Application/AppDelegate.swift @@ -487,7 +487,9 @@ private extension AppDelegate { addCollectionFactory: DIContainer .resolve(type: AddCollectionFactory.self), collectionEditFactory: DIContainer - .resolve(type: CollectionEditFactory.self) + .resolve(type: CollectionEditFactory.self), + dictionaryDetailFactory: DIContainer + .resolve(type: DictionaryDetailFactory.self) ) } DIContainer.register(type: CollectionSettingFactory.self) { diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailEmptyView.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailEmptyView.swift index 62eb0122..15387f47 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailEmptyView.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailEmptyView.swift @@ -60,7 +60,6 @@ private extension CollectionDetailEmptyView { func setupConstraints() { imageView.snp.makeConstraints { make in -// make.top.equalToSuperview() make.centerX.equalToSuperview() make.size.equalTo(Constant.imageSize) } @@ -79,7 +78,6 @@ private extension CollectionDetailEmptyView { make.top.equalTo(subLabel.snp.bottom).offset(Constant.buttonSpacing) make.width.equalTo(Constant.buttonWidth) make.centerX.equalToSuperview() -// make.centerX.bottom.equalToSuperview() } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailFactoryImpl.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailFactoryImpl.swift index 0fee018b..efe6dce9 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailFactoryImpl.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailFactoryImpl.swift @@ -9,19 +9,22 @@ public final class CollectionDetailFactoryImpl: CollectionDetailFactory { private let collectionSettingFactory: CollectionSettingFactory private let addCollectionFactory: AddCollectionFactory private let collectionEditFactory: CollectionEditFactory + private let dictionaryDetailFactory: DictionaryDetailFactory public init( setBookmarkUseCase: SetBookmarkUseCase, bookmarkModalFactory: BookmarkModalFactory, collectionSettingFactory: CollectionSettingFactory, addCollectionFactory: AddCollectionFactory, - collectionEditFactory: CollectionEditFactory + collectionEditFactory: CollectionEditFactory, + dictionaryDetailFactory: DictionaryDetailFactory ) { self.setBookmarkUseCase = setBookmarkUseCase self.bookmarkModalFactory = bookmarkModalFactory self.collectionSettingFactory = collectionSettingFactory self.addCollectionFactory = addCollectionFactory self.collectionEditFactory = collectionEditFactory + self.dictionaryDetailFactory = dictionaryDetailFactory } public func make(collection: BookmarkCollection) -> BaseViewController { @@ -34,7 +37,8 @@ public final class CollectionDetailFactoryImpl: CollectionDetailFactory { bookmarkModalFactory: bookmarkModalFactory, collectionSettingFactory: collectionSettingFactory, addCollectionFactory: addCollectionFactory, - collectionEditFactory: collectionEditFactory + collectionEditFactory: collectionEditFactory, + dictionaryDetailFactory: dictionaryDetailFactory ) return viewController } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailReactor.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailReactor.swift index 271e4f55..c848a242 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailReactor.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailReactor.swift @@ -9,10 +9,11 @@ public final class CollectionDetailReactor: Reactor { case toMain case dismiss case edit + case detail(DictionaryType, Int) } public enum Action { - case viewDidAppear + case viewWillAppear case backButtonTapped case editButtonTapped case addButtonTapped @@ -20,6 +21,8 @@ public final class CollectionDetailReactor: Reactor { case toggleBookmark(Int, Bool) case selectSetting(CollectionSettingMenu) case changeName(String) + case undoLastDeletedBookmark + case dataTapped(Int) } public enum Mutation { @@ -27,17 +30,18 @@ public final class CollectionDetailReactor: Reactor { case setItems([DictionaryItem]) case setMenu(CollectionSettingMenu) case setName(String) + case setLastDeletedBookmark(BookmarkResponse?) } public struct State { @Pulse var route: Route - let type = DictionaryMainViewType.bookmark - var collection: BookmarkCollection + @Pulse var collectionMenu: CollectionSettingMenu? var sections: [String] { return type.pageTabList.map { $0.title } } - - @Pulse var collectionMenu: CollectionSettingMenu? + let type = DictionaryMainViewType.bookmark + var collection: BookmarkCollection + var lastDeletedBookmark: BookmarkResponse? } // MARK: - Properties @@ -55,26 +59,40 @@ public final class CollectionDetailReactor: Reactor { public func mutate(action: Action) -> Observable { switch action { case .toggleBookmark(let id, let isSelected): -// return toggleBookmarkUseCase.execute(id: id, type: .total) -// .map { Mutation.setItems($0) } + // 북마크 설정 및 마지막 삭제 데이터 저장 후 컬렉션 데이터 가져오는 동작 필요 return .empty() case .backButtonTapped: return .just(.navigateTo(.dismiss)) case .editButtonTapped: return .just(.navigateTo(.edit)) - case .viewDidAppear: + case .viewWillAppear: // 데이터 불러오기? return .empty() case .addButtonTapped: // 컬렉션 추가 return .empty() case .bookmarkButtonTapped: - // 북마크로 이동 - return .empty() + return .just(.navigateTo(.toMain)) case .selectSetting(let menu): return .just(.setMenu(menu)) case .changeName(let name): return .just(.setName(name)) + case .undoLastDeletedBookmark: + guard let lastDeleted = currentState.lastDeletedBookmark else { return .empty() } + return setBookmarkUseCase.execute( + bookmarkId: lastDeleted.originalId, + isBookmark: .set(lastDeleted.type) + ) + .andThen( + Observable.concat([ + // 불러오기 + .just(.setLastDeletedBookmark(nil)) + ]) + ) + case .dataTapped(let index): + let item = currentState.collection.items[index] + guard let type = item.type.toDictionaryType else { return .empty() } + return .just(.navigateTo(.detail(type, item.id))) } } @@ -89,6 +107,8 @@ public final class CollectionDetailReactor: Reactor { newState.collectionMenu = menu case .setName(let name): newState.collection.title = name + case .setLastDeletedBookmark(let bookmark): + newState.lastDeletedBookmark = bookmark } return newState } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailView.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailView.swift index 5dd158b1..8e5c532a 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailView.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailView.swift @@ -7,7 +7,8 @@ import SnapKit final class CollectionDetailView: UIView { // MARK: - Type enum Constant { - static let TopMargin: CGFloat = 12 + static let topMargin: CGFloat = 12 + static let collectionViewMargin: CGFloat = 24 } // MARK: - Components @@ -61,16 +62,16 @@ private extension CollectionDetailView { spacer.snp.makeConstraints { make in make.top.equalTo(navigation.snp.bottom) make.horizontalEdges.equalToSuperview() - make.height.equalTo(Constant.TopMargin) + make.height.equalTo(Constant.topMargin) } listCollectionView.snp.makeConstraints { make in - make.top.equalTo(spacer.snp.bottom) + make.top.equalTo(spacer.snp.bottom).offset(Constant.collectionViewMargin) make.horizontalEdges.bottom.equalToSuperview() } emptyContainerView.snp.makeConstraints { make in - make.top.equalTo(navigation.snp.bottom).offset(Constant.TopMargin) + make.top.equalTo(navigation.snp.bottom).offset(Constant.collectionViewMargin) make.horizontalEdges.bottom.equalTo(safeAreaLayoutGuide) } @@ -90,7 +91,7 @@ private extension CollectionDetailView { // MARK: - Methods extension CollectionDetailView { func isEmptyData(isEmpty: Bool) { - listCollectionView.isHidden = !isEmpty - emptyContainerView.isHidden = isEmpty + listCollectionView.isHidden = isEmpty + emptyContainerView.isHidden = !isEmpty } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift index d5417a6e..418e4f12 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift @@ -2,6 +2,7 @@ import UIKit import BaseFeature import BookmarkFeatureInterface +import DictionaryFeatureInterface import ReactorKit import RxCocoa @@ -14,23 +15,25 @@ public final class CollectionDetailViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let bookmarkModalFactory: BookmarkModalFactory private let collectionSettingFactory: CollectionSettingFactory private let addCollectionFactory: AddCollectionFactory private let collectionEditFactory: CollectionEditFactory + private let dictionaryDetailFactory: DictionaryDetailFactory private var selectedSortIndex = 0 // MARK: - Components private var mainView: CollectionDetailView - public init(reactor: CollectionDetailReactor, bookmarkModalFactory: BookmarkModalFactory, collectionSettingFactory: CollectionSettingFactory, addCollectionFactory: AddCollectionFactory, collectionEditFactory: CollectionEditFactory) { + public init(reactor: CollectionDetailReactor, bookmarkModalFactory: BookmarkModalFactory, collectionSettingFactory: CollectionSettingFactory, addCollectionFactory: AddCollectionFactory, collectionEditFactory: CollectionEditFactory, dictionaryDetailFactory: DictionaryDetailFactory) { self.mainView = CollectionDetailView(navTitle: reactor.currentState.collection.title) self.bookmarkModalFactory = bookmarkModalFactory self.collectionSettingFactory = collectionSettingFactory self.addCollectionFactory = addCollectionFactory self.collectionEditFactory = collectionEditFactory + self.dictionaryDetailFactory = dictionaryDetailFactory super.init() self.reactor = reactor navigationController?.navigationBar.isHidden = true @@ -88,6 +91,11 @@ extension CollectionDetailViewController { } func bindUserActions(reactor: Reactor) { + rx.viewWillAppear + .map { _ in Reactor.Action.viewWillAppear } + .bind(to: reactor.action) + .disposed(by: disposeBag) + mainView.emptyView.bookmarkButton.rx.tap .map { Reactor.Action.bookmarkButtonTapped } .bind(to: reactor.action) @@ -165,6 +173,9 @@ extension CollectionDetailViewController { owner.reactor?.action.onNext(.selectSetting(menu)) }) owner.presentModal(viewController) + case .detail(let type,let id): + let viewController = owner.dictionaryDetailFactory.make(type: type, id: id) + owner.navigationController?.pushViewController(viewController, animated: true) default: break } @@ -243,4 +254,8 @@ extension CollectionDetailViewController: UICollectionViewDelegate, UICollection return cell } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + reactor?.action.onNext(.dataTapped(indexPath.row)) + } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionList/CollectionListViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionList/CollectionListViewController.swift index d63c2f28..902cc963 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionList/CollectionListViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionList/CollectionListViewController.swift @@ -11,9 +11,9 @@ public final class CollectionListViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + public var onDismissWithMessage: ((BookmarkCollection?) -> Void)? - + private let addCollectionFactory: AddCollectionFactory private let detailFactory: CollectionDetailFactory From dba053268e5671de4c4295cbc4b18387309956d1 Mon Sep 17 00:00:00 2001 From: p2glet Date: Mon, 10 Nov 2025 01:56:51 +0900 Subject: [PATCH 06/14] =?UTF-8?q?fix/#264:=20=EB=8F=84=EA=B0=90=EB=A9=94?= =?UTF-8?q?=EC=9D=B8-=EC=95=8C=EB=A6=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/DTO/AuthDTO/MemberDTO.swift | 5 +- .../Network/Endpoints/AlarmEndPoint.swift | 2 +- .../DataMock/Factory/LoginFactoryMock.swift | 5 ++ .../Repository/AuthAPIRepositoryMock.swift | 4 + .../Domain/Interceptor/TokenInterceptor.swift | 12 +-- ...l.swift => FetchAllAlarmUseCaseImpl.swift} | 2 +- .../AuthAPI/CheckLoginUseCaseImpl.swift | 28 +----- .../FetchNotificationUseCaseImpl.swift | 19 ---- .../DictionaryNotification/Notification.swift | 11 --- .../Entity/MyPage/MyPageResponse.swift | 8 +- ...eCase.swift => FetchAllAlarmUseCase.swift} | 2 +- .../FetchNotificationUseCase.swift | 5 -- MLS/MLS/Application/AppDelegate.swift | 8 +- .../BaseFeature/Utills/Extension/Int+.swift | 5 ++ .../DictionaryMainViewController.swift | 1 + .../DictionaryNotificationFactoryImpl.swift | 10 ++- .../DictionaryNotificationReactor.swift | 86 +++++++++++++------ ...DictionaryNotificationViewController.swift | 31 ++++--- .../CustomerSupportBaseViewController.swift | 6 +- .../Event/EventViewController.swift | 1 + .../Main/MyPageMainViewController.swift | 4 +- .../NotificationSettingFactoryImpl.swift | 12 ++- .../NotificationSettingReactor.swift | 15 ++-- .../NotificationSettingViewController.swift | 2 +- .../NotificationSettingFactory.swift | 2 +- 25 files changed, 152 insertions(+), 134 deletions(-) rename MLS/Domain/Domain/UseCaseImpl/Alarm/{FetchAllUseCaseImpl.swift => FetchAllAlarmUseCaseImpl.swift} (86%) delete mode 100644 MLS/Domain/Domain/UseCaseImpl/DictionaryNotification/FetchNotificationUseCaseImpl.swift delete mode 100644 MLS/Domain/DomainInterface/Entity/DictionaryNotification/Notification.swift rename MLS/Domain/DomainInterface/UseCase/Alarm/{FetchAllUseCase.swift => FetchAllAlarmUseCase.swift} (74%) delete mode 100644 MLS/Domain/DomainInterface/UseCase/DictionaryNotification/FetchNotificationUseCase.swift create mode 100644 MLS/Presentation/BaseFeature/BaseFeature/Utills/Extension/Int+.swift diff --git a/MLS/Data/Data/Network/DTO/AuthDTO/MemberDTO.swift b/MLS/Data/Data/Network/DTO/AuthDTO/MemberDTO.swift index ba381c0a..5126bcd5 100644 --- a/MLS/Data/Data/Network/DTO/AuthDTO/MemberDTO.swift +++ b/MLS/Data/Data/Network/DTO/AuthDTO/MemberDTO.swift @@ -20,7 +20,10 @@ public struct MemberDTO: Decodable { jobName: "", level: level, profileUrl: profileImageUrl, - platform: provider == "APPLE" ? .apple : .kakao + platform: provider == "APPLE" ? .apple : .kakao, + noticeAgreement: noticeAgreement, + patchNoteAgreement: patchNoteAgreement, + eventAgreement: eventAgreement ) } diff --git a/MLS/Data/Data/Network/Endpoints/AlarmEndPoint.swift b/MLS/Data/Data/Network/Endpoints/AlarmEndPoint.swift index ad39ec39..721edf77 100644 --- a/MLS/Data/Data/Network/Endpoints/AlarmEndPoint.swift +++ b/MLS/Data/Data/Network/Endpoints/AlarmEndPoint.swift @@ -42,7 +42,7 @@ public enum AlarmEndPoint { public static func fetchAll(query: Encodable) -> ResponsableEndPoint { .init( baseURL: base, - path: "/api/v1/alrim/list/all", + path: "/api/v1/alrim/all", method: .GET, query: query ) diff --git a/MLS/Data/DataMock/Factory/LoginFactoryMock.swift b/MLS/Data/DataMock/Factory/LoginFactoryMock.swift index acc23bb1..ce48cb8c 100644 --- a/MLS/Data/DataMock/Factory/LoginFactoryMock.swift +++ b/MLS/Data/DataMock/Factory/LoginFactoryMock.swift @@ -11,4 +11,9 @@ public final class LoginFactoryMock: LoginFactory { viewController.view.backgroundColor = .blue return viewController } + + public func make(exitRoute: LoginExitRoute, onLoginCompleted: (() -> Void)?) -> BaseViewController { + return BaseViewController() + } + } diff --git a/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift b/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift index 6a5b8bd7..74ad0f42 100644 --- a/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift +++ b/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift @@ -6,6 +6,10 @@ import DomainInterface import RxSwift public class AuthAPIRepositoryMock: AuthAPIRepository { + public func fetchProfile() -> Observable { + return .empty() + } + public func fetchJob(jobId: String) -> Observable { return .empty() } diff --git a/MLS/Domain/Domain/Interceptor/TokenInterceptor.swift b/MLS/Domain/Domain/Interceptor/TokenInterceptor.swift index 6cef609a..1bfb0cfd 100644 --- a/MLS/Domain/Domain/Interceptor/TokenInterceptor.swift +++ b/MLS/Domain/Domain/Interceptor/TokenInterceptor.swift @@ -11,15 +11,9 @@ public class TokenInterceptor: Interceptor { } public func adapt(_ request: URLRequest) -> URLRequest { - let accessFetchResult = fetchTokenUseCase.execute(type: .accessToken) - switch accessFetchResult { - case .success(let token): - var adaptedRequest = request - adaptedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - return adaptedRequest - case .failure: - return request - } + var adaptedRequest = request + adaptedRequest.setValue("Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MjU2MDIyNDk4IiwiaWF0IjoxNzYyMzUyODE0LCJleHAiOjIwNzc3MTI4MTR9.6pMpn6kF7zPbVW1U4Lbo1NbhlYc2IwRcDChAERoIo14", forHTTPHeaderField: "Authorization") + return adaptedRequest } public func retry(data: Data?, response: URLResponse?, error: Error?) -> Bool { diff --git a/MLS/Domain/Domain/UseCaseImpl/Alarm/FetchAllUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/Alarm/FetchAllAlarmUseCaseImpl.swift similarity index 86% rename from MLS/Domain/Domain/UseCaseImpl/Alarm/FetchAllUseCaseImpl.swift rename to MLS/Domain/Domain/UseCaseImpl/Alarm/FetchAllAlarmUseCaseImpl.swift index b1258d03..60f51b83 100644 --- a/MLS/Domain/Domain/UseCaseImpl/Alarm/FetchAllUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/Alarm/FetchAllAlarmUseCaseImpl.swift @@ -4,7 +4,7 @@ import DomainInterface import RxSwift -public class FetchAllUseCaseImpl: FetchAllUseCase { +public class FetchAllAlarmUseCaseImpl: FetchAllAlarmUseCase { private var repository: AlarmAPIRepository public init(repository: AlarmAPIRepository) { diff --git a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/CheckLoginUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/CheckLoginUseCaseImpl.swift index c3cd42df..fbe72466 100644 --- a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/CheckLoginUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/CheckLoginUseCaseImpl.swift @@ -12,32 +12,6 @@ public class CheckLoginUseCaseImpl: CheckLoginUseCase { } public func execute() -> Observable { - switch tokenRepository.fetchToken(type: .refreshToken) { - case .success(let token): - return authRepository.reissueToken(refreshToken: token) - .map { [weak self] response in - guard let self else { return false } - - let accessResult = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) - let refreshResult = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) - - switch (accessResult, refreshResult) { - case (.success, .success): - return true - case (.failure(let error), _), - (_, .failure(let error)): - print("Token 저장 실패:", error.localizedDescription) - return false - } - } - .catch { error in - print("reissueToken 실패:", error.localizedDescription) - return .just(false) - } - - case .failure(let error): - print("refreshToken 불러오기 실패:", error.localizedDescription) - return .just(false) - } + return .just(true) } } diff --git a/MLS/Domain/Domain/UseCaseImpl/DictionaryNotification/FetchNotificationUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/DictionaryNotification/FetchNotificationUseCaseImpl.swift deleted file mode 100644 index 396e57ec..00000000 --- a/MLS/Domain/Domain/UseCaseImpl/DictionaryNotification/FetchNotificationUseCaseImpl.swift +++ /dev/null @@ -1,19 +0,0 @@ -import DomainInterface - -import RxSwift - -public class FetchNotificationUseCaseImpl: FetchNotificationUseCase { - public init() {} - - public func execute() -> Observable<[Notification]> { - return Observable.just([ - Notification(title: "신규 업데이트 알림", date: "2025년 1월 1일"), - Notification(title: "신규 업데이트 알림", date: "2025년 2월 1일", isChecked: true), - Notification(title: "신규 업데이트 알림", date: "2025년 3월 1일"), - Notification(title: "신규 업데이트 알림", date: "2025년 4월 1일"), - Notification(title: "신규 업데이트 알림", date: "2025년 5월 1일", isChecked: true), - Notification(title: "신규 업데이트 알림", date: "2025년 6월 1일"), - Notification(title: "신규 업데이트 알림", date: "2025년 7월 1일") - ]) - } -} diff --git a/MLS/Domain/DomainInterface/Entity/DictionaryNotification/Notification.swift b/MLS/Domain/DomainInterface/Entity/DictionaryNotification/Notification.swift deleted file mode 100644 index 9821b1c0..00000000 --- a/MLS/Domain/DomainInterface/Entity/DictionaryNotification/Notification.swift +++ /dev/null @@ -1,11 +0,0 @@ -public struct Notification { - public let title: String - public let date: String - public let isChecked: Bool - - public init(title: String, date: String, isChecked: Bool = false) { - self.title = title - self.date = date - self.isChecked = isChecked - } -} diff --git a/MLS/Domain/DomainInterface/Entity/MyPage/MyPageResponse.swift b/MLS/Domain/DomainInterface/Entity/MyPage/MyPageResponse.swift index d1698674..89f01c7e 100644 --- a/MLS/Domain/DomainInterface/Entity/MyPage/MyPageResponse.swift +++ b/MLS/Domain/DomainInterface/Entity/MyPage/MyPageResponse.swift @@ -5,13 +5,19 @@ public struct MyPageResponse { public let level: Int? public let profileUrl: String public let platform: LoginPlatform + public let noticeAgreement: Bool + public let patchNoteAgreement: Bool + public let eventAgreement: Bool - public init(nickname: String, jobId: Int?, jobName: String, level: Int?, profileUrl: String, platform: LoginPlatform) { + public init(nickname: String, jobId: Int?, jobName: String, level: Int?, profileUrl: String, platform: LoginPlatform, noticeAgreement: Bool?, patchNoteAgreement: Bool?, eventAgreement: Bool?) { self.nickname = nickname self.jobId = jobId self.jobName = jobName self.level = level self.profileUrl = profileUrl self.platform = platform + self.noticeAgreement = noticeAgreement ?? false + self.patchNoteAgreement = patchNoteAgreement ?? false + self.eventAgreement = eventAgreement ?? false } } diff --git a/MLS/Domain/DomainInterface/UseCase/Alarm/FetchAllUseCase.swift b/MLS/Domain/DomainInterface/UseCase/Alarm/FetchAllAlarmUseCase.swift similarity index 74% rename from MLS/Domain/DomainInterface/UseCase/Alarm/FetchAllUseCase.swift rename to MLS/Domain/DomainInterface/UseCase/Alarm/FetchAllAlarmUseCase.swift index f62d902c..72a34087 100644 --- a/MLS/Domain/DomainInterface/UseCase/Alarm/FetchAllUseCase.swift +++ b/MLS/Domain/DomainInterface/UseCase/Alarm/FetchAllAlarmUseCase.swift @@ -1,5 +1,5 @@ import RxSwift -public protocol FetchAllUseCase { +public protocol FetchAllAlarmUseCase { func execute(cursor: [Int]?, pageSize: Int) -> Observable> } diff --git a/MLS/Domain/DomainInterface/UseCase/DictionaryNotification/FetchNotificationUseCase.swift b/MLS/Domain/DomainInterface/UseCase/DictionaryNotification/FetchNotificationUseCase.swift deleted file mode 100644 index f7e47f12..00000000 --- a/MLS/Domain/DomainInterface/UseCase/DictionaryNotification/FetchNotificationUseCase.swift +++ /dev/null @@ -1,5 +0,0 @@ -import RxSwift - -public protocol FetchNotificationUseCase { - func execute() -> Observable<[Notification]> -} diff --git a/MLS/MLS/Application/AppDelegate.swift b/MLS/MLS/Application/AppDelegate.swift index 3d58435c..346a538b 100644 --- a/MLS/MLS/Application/AppDelegate.swift +++ b/MLS/MLS/Application/AppDelegate.swift @@ -199,9 +199,6 @@ private extension AppDelegate { DIContainer.register(type: UpdateNotificationAgreementUseCase.self) { UpdateNotificationAgreementUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self)) } - DIContainer.register(type: FetchNotificationUseCase.self) { - FetchNotificationUseCaseImpl() - } DIContainer.register(type: CheckLoginUseCase.self) { CheckLoginUseCaseImpl(authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self)) } @@ -337,6 +334,9 @@ private extension AppDelegate { DIContainer.register(type: SetReadUseCase.self) { SetReadUseCaseImpl(repository: DIContainer.resolve(type: AlarmAPIRepository.self)) } + DIContainer.register(type: FetchAllAlarmUseCase.self) { + FetchAllAlarmUseCaseImpl(repository: DIContainer.resolve(type: AlarmAPIRepository.self)) + } } func registerFactory() { @@ -393,7 +393,7 @@ private extension AppDelegate { NotificationSettingFactoryImpl(checkNotificationPermissionUseCase: DIContainer.resolve(type: CheckNotificationPermissionUseCase.self), updateNotificationAgreementUseCase: DIContainer.resolve(type: UpdateNotificationAgreementUseCase.self)) } DIContainer.register(type: DictionaryNotificationFactory.self) { - DictionaryNotificationFactoryImpl(fetchNotificationUseCase: DIContainer.resolve(type: FetchNotificationUseCase.self), notificationSettingFactory: DIContainer.resolve(type: NotificationSettingFactory.self)) + DictionaryNotificationFactoryImpl(notificationSettingFactory: DIContainer.resolve(type: NotificationSettingFactory.self), fetchAllAlarmUseCase: DIContainer.resolve(type: FetchAllAlarmUseCase.self), fetchProfileUseCase: DIContainer.resolve(type: FetchProfileUseCase.self)) } DIContainer.register(type: DictionaryMainViewFactory.self) { DictionaryMainViewFactoryImpl( diff --git a/MLS/Presentation/BaseFeature/BaseFeature/Utills/Extension/Int+.swift b/MLS/Presentation/BaseFeature/BaseFeature/Utills/Extension/Int+.swift new file mode 100644 index 00000000..5443d97d --- /dev/null +++ b/MLS/Presentation/BaseFeature/BaseFeature/Utills/Extension/Int+.swift @@ -0,0 +1,5 @@ +extension Array where Element == Int { + public func changeKoreanDate() -> String { + return "\(self[0])년 \(self[1])월 \(self[2])일 \(self[3]):\(String(format: "%02d", self[4]))" + } +} diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift index 51ab261c..d72f8ea0 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift @@ -136,6 +136,7 @@ public extension DictionaryMainViewController { .take(1) .flatMapLatest { _ in return reactor.pulse(\.$route) } .withUnretained(self) + .observe(on: MainScheduler.instance) .subscribe { (owner, route) in switch route { case .search: diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationFactoryImpl.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationFactoryImpl.swift index eb4b53fb..d7875a36 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationFactoryImpl.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationFactoryImpl.swift @@ -4,16 +4,18 @@ import DomainInterface import MyPageFeatureInterface public final class DictionaryNotificationFactoryImpl: DictionaryNotificationFactory { - private let fetchNotificationUseCase: FetchNotificationUseCase private let notificationSettingFactory: NotificationSettingFactory + private let fetchAllAlarmUseCase: FetchAllAlarmUseCase + private let fetchProfileUseCase: FetchProfileUseCase - public init(fetchNotificationUseCase: FetchNotificationUseCase, notificationSettingFactory: NotificationSettingFactory) { - self.fetchNotificationUseCase = fetchNotificationUseCase + public init(notificationSettingFactory: NotificationSettingFactory, fetchAllAlarmUseCase: FetchAllAlarmUseCase, fetchProfileUseCase: FetchProfileUseCase) { self.notificationSettingFactory = notificationSettingFactory + self.fetchAllAlarmUseCase = fetchAllAlarmUseCase + self.fetchProfileUseCase = fetchProfileUseCase } public func make() -> BaseViewController { - let reactor = DictionaryNotificationReactor(fetchNotificationUseCase: fetchNotificationUseCase) + let reactor = DictionaryNotificationReactor(fetchAllAlarmUseCase: fetchAllAlarmUseCase, fetchProfileUseCase: fetchProfileUseCase) let viewController = DictionaryNotificationViewController(notificationSettingFactory: notificationSettingFactory) viewController.reactor = reactor return viewController diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationReactor.swift index a0693030..1333cbeb 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationReactor.swift @@ -1,6 +1,6 @@ import ReactorKit - import DomainInterface +import RxSwift public final class DictionaryNotificationReactor: Reactor { // MARK: - Reactor @@ -12,58 +12,96 @@ public final class DictionaryNotificationReactor: Reactor { } public enum Action { - case backbuttonTapped + case viewWillAppear + case loadMore + case backButtonTapped case settingButtonTapped case notificationTapped(String) - case viewWillAppear } public enum Mutation { + case setNotifications([AllAlarmResponse], hasMore: Bool, reset: Bool) + case setLoading(Bool) + case setProfile(MyPageResponse?) case navigateTo(Route) - case setNotifications([Notification]) } public struct State { @Pulse var route: Route = .none - var notifications: [Notification] = [] - var isAgreeNotification: Bool = true + var notifications: [AllAlarmResponse] = [] + var profile: MyPageResponse? = nil + var hasMore: Bool = false + var isLoading: Bool = false } - // MARK: - properties + // MARK: - Properties public var initialState: State - var disposeBag = DisposeBag() + private let disposeBag = DisposeBag() + private let fetchAllAlarmUseCase: FetchAllAlarmUseCase + private let fetchProfileUseCase: FetchProfileUseCase - private let fetchNotificationUseCase: FetchNotificationUseCase - - // MARK: - init - public init(fetchNotificationUseCase: FetchNotificationUseCase) { + // MARK: - Init + public init(fetchAllAlarmUseCase: FetchAllAlarmUseCase, fetchProfileUseCase: FetchProfileUseCase) { self.initialState = State() - self.fetchNotificationUseCase = fetchNotificationUseCase + self.fetchAllAlarmUseCase = fetchAllAlarmUseCase + self.fetchProfileUseCase = fetchProfileUseCase } - // MARK: - Reactor Methods + // MARK: - Mutate public func mutate(action: Action) -> Observable { switch action { - case .backbuttonTapped: - return Observable.just(.navigateTo(.dismiss)) + case .viewWillAppear: + return .concat([ + .just(.setLoading(true)), + fetchAllAlarmUseCase.execute(cursor: nil, pageSize: 20) + .map { paged in + .setNotifications(paged.items, hasMore: paged.hasMore, reset: true) + }, + .just(.setLoading(false)) + ]) + case .loadMore: + guard currentState.hasMore, !currentState.isLoading else { return .empty() } + let cursor = currentState.notifications.last?.date + + return .concat([ + .just(.setLoading(true)), + fetchAllAlarmUseCase.execute(cursor: cursor, pageSize: 20) + .map { paged in + .setNotifications(paged.items, hasMore: paged.hasMore, reset: false) + }, + .just(.setLoading(false)) + ]) + + case .backButtonTapped: + return .just(.navigateTo(.dismiss)) case .settingButtonTapped: - return Observable.just(.navigateTo(.setting)) + return .just(.navigateTo(.setting)) case .notificationTapped(let notification): - return Observable.just(.navigateTo(.notification(notification))) - case .viewWillAppear: - return fetchNotificationUseCase.execute() - .map { Mutation.setNotifications($0) } + return .just(.navigateTo(.notification(notification))) } } + // MARK: - Reduce public func reduce(state: State, mutation: Mutation) -> State { var newState = state switch mutation { - case .navigateTo(let route): + case let .setNotifications(newItems, hasMore, reset): + if reset { + newState.notifications = newItems + } else { + newState.notifications.append(contentsOf: newItems) + } + newState.hasMore = hasMore + + case let .setLoading(isLoading): + newState.isLoading = isLoading + + case let .setProfile(profile): + newState.profile = profile + + case let .navigateTo(route): newState.route = route - case .setNotifications(let notifications): - newState.notifications = notifications } return newState diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationViewController.swift index a8ffe35b..c249de58 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationViewController.swift @@ -57,7 +57,7 @@ private extension DictionaryNotificationViewController { func configureUI() { isBottomTabbarHidden = true guard let reactor = reactor else { return } - mainView.setEmpty(isEmpty: reactor.currentState.isAgreeNotification) + mainView.setEmpty(isEmpty: reactor.currentState.profile?.noticeAgreement == false) mainView.notificationCollectionView.delegate = self mainView.notificationCollectionView.dataSource = self @@ -81,8 +81,13 @@ public extension DictionaryNotificationViewController { } func bindUserActions(reactor: Reactor) { + rx.viewWillAppear + .map { _ in Reactor.Action.viewWillAppear } + .bind(to: reactor.action) + .disposed(by: disposeBag) + mainView.header.leftButton.rx.tap - .map { Reactor.Action.backbuttonTapped } + .map { Reactor.Action.backButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) @@ -102,20 +107,24 @@ public extension DictionaryNotificationViewController { case .dismiss: owner.navigationController?.popViewController(animated: true) case .setting: - let viewController = owner.notificationSettingFactory.make() + guard let reactor = owner.reactor, + let profile = reactor.currentState.profile else { return } + let viewController = owner.notificationSettingFactory.make(isAgreeEventNotification: profile.eventAgreement, isAgreeNoticeNotification: profile.noticeAgreement, isAgreePatchNoteNotification: profile.patchNoteAgreement) owner.navigationController?.pushViewController(viewController, animated: true) default: break } } .disposed(by: disposeBag) - - rx.viewWillAppear - .take(1) - .map { _ in Reactor.Action.viewWillAppear } - .bind(to: reactor.action) - .disposed(by: disposeBag) - } + + reactor.state.map { $0.notifications } + .distinctUntilChanged() + .withUnretained(self) + .observe(on: MainScheduler.instance) + .subscribe { owner, _ in + owner.mainView.notificationCollectionView.reloadData() + } + .disposed(by: disposeBag) } } // MARK: - Delegate @@ -129,7 +138,7 @@ extension DictionaryNotificationViewController: UICollectionViewDelegate, UIColl guard let reactor = reactor else { return UICollectionViewCell() } guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: DictionaryNotificationCell.identifier, for: indexPath) as? DictionaryNotificationCell else { return UICollectionViewCell() } let item = reactor.currentState.notifications[indexPath.row] - cell.inject(input: DictionaryNotificationCell.Input(title: item.title, subTitle: item.date, isChecked: item.isChecked)) + cell.inject(input: DictionaryNotificationCell.Input(title: item.title, subTitle: item.date.changeKoreanDate(), isChecked: item.alreadyRead)) return cell } } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift index e02b49fb..945f64de 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift @@ -44,7 +44,7 @@ class CustomerSupportBaseViewController: BaseViewController { func createDetailItem(items: [AlarmResponse]) { for (index, item) in items.enumerated() { - let view = mainView.createDetailItem(titleText: item.title, dateText: changeKoreanDate(date: item.date)) + let view = mainView.createDetailItem(titleText: item.title, dateText: item.date.changeKoreanDate()) view.tag = index urlStrings.append(item.link) @@ -75,10 +75,6 @@ class CustomerSupportBaseViewController: BaseViewController { .disposed(by: disposeBag) } } - - func changeKoreanDate(date: [Int]) -> String? { - return "\(date[0])년 \(date[1])월 \(date[2])일 \(date[3]):\(String(format: "%02d", date[4]))" - } } // MARK: - SetUp diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Event/EventViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Event/EventViewController.swift index 1a842c98..bf6c5bbd 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Event/EventViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Event/EventViewController.swift @@ -5,6 +5,7 @@ import UIKit final class EventViewController: CustomerSupportBaseViewController, View { typealias Reactor = EventReactor + // MARK: - Init override init(type: CustomerSupportType) { super.init(type: type) diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift index 72c40bf7..bb071102 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift @@ -152,7 +152,9 @@ extension MyPageMainViewController { let viewController = owner.customerSupportFactory.make(type: .terms) owner.navigationController?.pushViewController(viewController, animated: true) case .notificationSetting: - let viewController = owner.notificationSettingFactory.make() + guard let reactor = owner.reactor, + let profile = reactor.currentState.profile else { return } + let viewController = owner.notificationSettingFactory.make(isAgreeEventNotification: profile.eventAgreement, isAgreeNoticeNotification: profile.noticeAgreement, isAgreePatchNoteNotification: profile.patchNoteAgreement) owner.navigationController?.pushViewController(viewController, animated: true) case .login: let viewController = owner.loginFactory.make(exitRoute: .pop) diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingFactoryImpl.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingFactoryImpl.swift index c5646198..10548ef8 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingFactoryImpl.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingFactoryImpl.swift @@ -11,8 +11,16 @@ public final class NotificationSettingFactoryImpl: NotificationSettingFactory { self.updateNotificationAgreementUseCase = updateNotificationAgreementUseCase } - public func make() -> BaseViewController { - let viewController = NotificationSettingViewController(reactor: NotificationSettingReactor(checkNotificationPermissionUseCase: checkNotificationPermissionUseCase, updateNotificationAgreementUseCase: updateNotificationAgreementUseCase)) + public func make(isAgreeEventNotification: Bool, isAgreeNoticeNotification: Bool, isAgreePatchNoteNotification: Bool) -> BaseViewController { + let viewController = NotificationSettingViewController( + reactor: NotificationSettingReactor( + checkNotificationPermissionUseCase: checkNotificationPermissionUseCase, + updateNotificationAgreementUseCase: updateNotificationAgreementUseCase, + isAgreeEventNotification: isAgreeEventNotification, + isAgreeNoticeNotification: isAgreeNoticeNotification, + isAgreePatchNoteNotification: isAgreePatchNoteNotification + ) + ) return viewController } } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingReactor.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingReactor.swift index d652c5f4..b1e68dcb 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingReactor.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingReactor.swift @@ -30,9 +30,9 @@ public final class NotificationSettingReactor: Reactor { public struct State { @Pulse var route = Route.none var authorized = false - var isAgreeEventNotification = false - var isAgreeNoticeNotification = false - var isAgreePatchNoteNotification = false + var isAgreeEventNotification: Bool + var isAgreeNoticeNotification: Bool + var isAgreePatchNoteNotification: Bool } public var initialState: State @@ -41,8 +41,13 @@ public final class NotificationSettingReactor: Reactor { private let checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase private let updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase - init(checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase) { - self.initialState = .init() + init(checkNotificationPermissionUseCase: CheckNotificationPermissionUseCase, updateNotificationAgreementUseCase: UpdateNotificationAgreementUseCase, + isAgreeEventNotification: Bool, + isAgreeNoticeNotification: Bool, + isAgreePatchNoteNotification: Bool + + ) { + self.initialState = .init(isAgreeEventNotification: isAgreeEventNotification, isAgreeNoticeNotification: isAgreeNoticeNotification, isAgreePatchNoteNotification: isAgreePatchNoteNotification) self.checkNotificationPermissionUseCase = checkNotificationPermissionUseCase self.updateNotificationAgreementUseCase = updateNotificationAgreementUseCase } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingViewController.swift index e78a33f5..1b936661 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingViewController.swift @@ -8,7 +8,7 @@ import RxSwift final class NotificationSettingViewController: BaseViewController, View, UNUserNotificationCenterDelegate { typealias Reactor = NotificationSettingReactor - + public var disposeBag = DisposeBag() // MARK: - Properties diff --git a/MLS/Presentation/MyPageFeature/MyPageFeatureInterface/NotificationSettingFactory.swift b/MLS/Presentation/MyPageFeature/MyPageFeatureInterface/NotificationSettingFactory.swift index 7d7a9c51..45a4e762 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeatureInterface/NotificationSettingFactory.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeatureInterface/NotificationSettingFactory.swift @@ -1,5 +1,5 @@ import BaseFeature public protocol NotificationSettingFactory { - func make() -> BaseViewController + func make(isAgreeEventNotification: Bool, isAgreeNoticeNotification: Bool, isAgreePatchNoteNotification: Bool) -> BaseViewController } From e94955aac8e4703bdfbbb99072c7676f8de1ceae Mon Sep 17 00:00:00 2001 From: p2glet Date: Tue, 11 Nov 2025 15:37:41 +0900 Subject: [PATCH 07/14] =?UTF-8?q?fix/#264:=20editCollection=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=ED=99=94=EB=A9=B4=20=EC=A0=84=ED=99=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLS/Application/AppDelegate.swift | 2 +- .../BookmarkList/BookmarkListFactoryImpl.swift | 8 ++++++-- .../BookmarkList/BookmarkListReactor.swift | 4 ++++ .../BookmarkList/BookmarkListViewController.swift | 13 ++++++++++++- .../BookmarkMain/BookmarkMainReactor.swift | 1 + .../CollectionEdit/CollectionEditFactoryImpl.swift | 1 + .../MyPageFeature.xcodeproj/project.pbxproj | 4 ++++ 7 files changed, 29 insertions(+), 4 deletions(-) diff --git a/MLS/MLS/Application/AppDelegate.swift b/MLS/MLS/Application/AppDelegate.swift index 346a538b..d13caa1b 100644 --- a/MLS/MLS/Application/AppDelegate.swift +++ b/MLS/MLS/Application/AppDelegate.swift @@ -471,7 +471,7 @@ private extension AppDelegate { BookmarkOnBoardingFactoryImpl() } DIContainer.register(type: BookmarkListFactory.self) { - BookmarkListFactoryImpl(itemFilterFactory: DIContainer.resolve(type: ItemFilterBottomSheetFactory.self), monsterFilterFactory: DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self), sortedFactory: DIContainer.resolve(type: SortedBottomSheetFactory.self), bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), loginFactory: DIContainer.resolve(type: LoginFactory.self), dictionaryDetailFactory: DIContainer.resolve(type: DictionaryDetailFactory.self), setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), fetchBookmarkUseCase: DIContainer.resolve(type: FetchBookmarkUseCase.self), fetchMonsterBookmarkUseCase: DIContainer.resolve(type: FetchMonsterBookmarkUseCase.self), fetchItemBookmarkUseCase: DIContainer.resolve(type: FetchItemBookmarkUseCase.self), fetchNPCBookmarkUseCase: DIContainer.resolve(type: FetchNPCBookmarkUseCase.self), fetchQuestBookmarkUseCase: DIContainer.resolve(type: FetchQuestBookmarkUseCase.self), fetchMapBookmarkUseCase: DIContainer.resolve(type: FetchMapBookmarkUseCase.self)) + BookmarkListFactoryImpl(itemFilterFactory: DIContainer.resolve(type: ItemFilterBottomSheetFactory.self), monsterFilterFactory: DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self), sortedFactory: DIContainer.resolve(type: SortedBottomSheetFactory.self), bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), loginFactory: DIContainer.resolve(type: LoginFactory.self), dictionaryDetailFactory: DIContainer.resolve(type: DictionaryDetailFactory.self), setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), fetchBookmarkUseCase: DIContainer.resolve(type: FetchBookmarkUseCase.self), fetchMonsterBookmarkUseCase: DIContainer.resolve(type: FetchMonsterBookmarkUseCase.self), fetchItemBookmarkUseCase: DIContainer.resolve(type: FetchItemBookmarkUseCase.self), fetchNPCBookmarkUseCase: DIContainer.resolve(type: FetchNPCBookmarkUseCase.self), fetchQuestBookmarkUseCase: DIContainer.resolve(type: FetchQuestBookmarkUseCase.self), fetchMapBookmarkUseCase: DIContainer.resolve(type: FetchMapBookmarkUseCase.self), collectionEditFactory: DIContainer.resolve(type: CollectionEditFactory.self)) } DIContainer.register(type: CollectionListFactory.self) { CollectionListFactoryImpl(addCollectionFactory: DIContainer.resolve(type: AddCollectionFactory.self), bookmarkDetailFactory: DIContainer.resolve(type: CollectionDetailFactory.self)) diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift index 53528256..dec82dda 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListFactoryImpl.swift @@ -20,6 +20,7 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { private let fetchNPCBookmarkUseCase: FetchNPCBookmarkUseCase private let fetchQuestBookmarkUseCase: FetchQuestBookmarkUseCase private let fetchMapBookmarkUseCase: FetchMapBookmarkUseCase + private let collectionEditFactory: CollectionEditFactory public init( itemFilterFactory: ItemFilterBottomSheetFactory, @@ -35,7 +36,8 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { fetchItemBookmarkUseCase: FetchItemBookmarkUseCase, fetchNPCBookmarkUseCase: FetchNPCBookmarkUseCase, fetchQuestBookmarkUseCase: FetchQuestBookmarkUseCase, - fetchMapBookmarkUseCase: FetchMapBookmarkUseCase + fetchMapBookmarkUseCase: FetchMapBookmarkUseCase, + collectionEditFactory: CollectionEditFactory ) { self.itemFilterFactory = itemFilterFactory self.monsterFilterFactory = monsterFilterFactory @@ -51,6 +53,7 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { self.fetchItemBookmarkUseCase = fetchItemBookmarkUseCase self.fetchQuestBookmarkUseCase = fetchQuestBookmarkUseCase self.fetchMapBookmarkUseCase = fetchMapBookmarkUseCase + self.collectionEditFactory = collectionEditFactory } public func make(type: DictionaryType, listType: DictionaryMainViewType) -> BaseViewController { @@ -72,7 +75,8 @@ public final class BookmarkListFactoryImpl: BookmarkListFactory { sortedFactory: sortedFactory, bookmarkModalFactory: bookmarkModalFactory, loginFactory: loginFactory, - dictionaryDetailFactory: dictionaryDetailFactory + dictionaryDetailFactory: dictionaryDetailFactory, + collectionEditFactory: collectionEditFactory ) if listType == .search { viewController.isBottomTabbarHidden = true diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListReactor.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListReactor.swift index 20a54f6f..795752fa 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListReactor.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListReactor.swift @@ -12,6 +12,7 @@ public final class BookmarkListReactor: Reactor { case detail(DictionaryType, Int) case dictionary case login + case edit } enum ViewState: Equatable { @@ -26,6 +27,7 @@ public final class BookmarkListReactor: Reactor { case toggleBookmark(Int, Bool) case sortButtonTapped case filterButtonTapped + case editButtonTapped case fetchList case sortOptionSelected(SortType) case filterOptionSelected(startLevel: Int, endLevel: Int) @@ -180,6 +182,8 @@ public final class BookmarkListReactor: Reactor { } else { return .just(.toNavagate(.dictionary)) } + case .editButtonTapped: + return .just(.toNavagate(.edit)) } } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift index 50b70dfb..4e392efb 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift @@ -21,6 +21,7 @@ public final class BookmarkListViewController: BaseViewController, View { private let sortedFactory: SortedBottomSheetFactory private let loginFactory: LoginFactory private let dictionaryDetailFactory: DictionaryDetailFactory + private let collectionEditFactory: CollectionEditFactory private var selectedSortIndex = 0 @@ -35,7 +36,8 @@ public final class BookmarkListViewController: BaseViewController, View { sortedFactory: SortedBottomSheetFactory, bookmarkModalFactory: BookmarkModalFactory, loginFactory: LoginFactory, - dictionaryDetailFactory: DictionaryDetailFactory + dictionaryDetailFactory: DictionaryDetailFactory, + collectionEditFactory: CollectionEditFactory ) { self.itemFilterFactory = itemFilterFactory self.monsterFilterFactory = monsterFilterFactory @@ -43,6 +45,7 @@ public final class BookmarkListViewController: BaseViewController, View { self.bookmarkModalFactory = bookmarkModalFactory self.loginFactory = loginFactory self.dictionaryDetailFactory = dictionaryDetailFactory + self.collectionEditFactory = collectionEditFactory self.mainView = BookmarkListView(isFilterHidden: reactor.currentState.type.isBookmarkSortHidden, bookmarkEmptyView: emptyView) super.init() self.reactor = reactor @@ -115,6 +118,11 @@ extension BookmarkListViewController { .bind(to: reactor.action) .disposed(by: disposeBag) + mainView.editButton?.rx.tap + .map { Reactor.Action.editButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) + emptyView.button.rx.tap .map { .emptyButtonTapped } .bind(to: reactor.action) @@ -172,6 +180,9 @@ extension BookmarkListViewController { if let tabBarController = owner.tabBarController as? BottomTabBarController { tabBarController.selectTab(index: 0) } + case .edit: + let viewController = owner.collectionEditFactory.make() + owner.navigationController?.pushViewController(viewController, animated: true) default: break } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainReactor.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainReactor.swift index b794ecfa..7526273c 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainReactor.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainReactor.swift @@ -8,6 +8,7 @@ public final class BookmarkMainReactor: Reactor { case search case onBoarding case notification + case edit } public enum Action { diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditFactoryImpl.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditFactoryImpl.swift index f5762e27..020c1c91 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditFactoryImpl.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditFactoryImpl.swift @@ -15,6 +15,7 @@ public final class CollectionEditFactoryImpl: CollectionEditFactory { let reactor = CollectionEditReactor() let viewController = CollectionEditViewController(bookmarkModalFactory: bookmarkModalFactory) viewController.reactor = reactor + viewController.isBottomTabbarHidden = true return viewController } } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature.xcodeproj/project.pbxproj b/MLS/Presentation/MyPageFeature/MyPageFeature.xcodeproj/project.pbxproj index db77c710..77c3b916 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature.xcodeproj/project.pbxproj +++ b/MLS/Presentation/MyPageFeature/MyPageFeature.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 7746ABB12E84245D0046F603 /* RxGesture in Frameworks */ = {isa = PBXBuildFile; productRef = 7746ABB02E84245D0046F603 /* RxGesture */; }; 7746ABB32E8425E00046F603 /* DesignSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7746ABB22E8425E00046F603 /* DesignSystem.framework */; }; 7746ABB72E8427F80046F603 /* RxKeyboard in Frameworks */ = {isa = PBXBuildFile; productRef = 7746ABB62E8427F80046F603 /* RxKeyboard */; }; + 775966F32EC30B3A00CC389B /* AuthFeatureInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 775966F22EC30B3A00CC389B /* AuthFeatureInterface.framework */; }; 776FEA342EA4D29B0039ACE2 /* AuthFeatureInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 776FEA332EA4D29B0039ACE2 /* AuthFeatureInterface.framework */; }; 776FEA352EA4D29B0039ACE2 /* AuthFeatureInterface.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 776FEA332EA4D29B0039ACE2 /* AuthFeatureInterface.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 777C28142E7D87B8000765F2 /* RxKeyboard in Frameworks */ = {isa = PBXBuildFile; productRef = 777C28132E7D87B8000765F2 /* RxKeyboard */; }; @@ -108,6 +109,7 @@ 7746ABA92E84225A0046F603 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7746ABAC2E84227A0046F603 /* DomainInterface.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DomainInterface.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7746ABB22E8425E00046F603 /* DesignSystem.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DesignSystem.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 775966F22EC30B3A00CC389B /* AuthFeatureInterface.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AuthFeatureInterface.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 776FEA332EA4D29B0039ACE2 /* AuthFeatureInterface.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AuthFeatureInterface.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 77BE55B82E78596900522216 /* Domain.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Domain.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 77BE55B92E78596900522216 /* DomainInterface.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DomainInterface.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -159,6 +161,7 @@ 7746ABB32E8425E00046F603 /* DesignSystem.framework in Frameworks */, 773A3C092E71712300F75B30 /* MyPageFeatureInterface.framework in Frameworks */, 777C28142E7D87B8000765F2 /* RxKeyboard in Frameworks */, + 775966F32EC30B3A00CC389B /* AuthFeatureInterface.framework in Frameworks */, 773A3BEB2E716FD800F75B30 /* SnapKit in Frameworks */, 773A3F712E71736A00F75B30 /* BaseFeature.framework in Frameworks */, ); @@ -223,6 +226,7 @@ 773A3BF62E71701D00F75B30 /* Frameworks */ = { isa = PBXGroup; children = ( + 775966F22EC30B3A00CC389B /* AuthFeatureInterface.framework */, 776FEA332EA4D29B0039ACE2 /* AuthFeatureInterface.framework */, 7746ABB22E8425E00046F603 /* DesignSystem.framework */, 7746ABAC2E84227A0046F603 /* DomainInterface.framework */, From 03dbe7a7efb39117784182755ab92206406fd245 Mon Sep 17 00:00:00 2001 From: p2glet Date: Tue, 11 Nov 2025 17:10:40 +0900 Subject: [PATCH 08/14] =?UTF-8?q?fix/#264:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EA=B4=80=EB=A0=A8=20UI=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repository/AuthAPIRepositoryImpl.swift | 3 +- .../Repository/AuthAPIRepositoryMock.swift | 2 +- .../Domain/Interceptor/TokenInterceptor.swift | 12 ++++-- .../AuthAPI/CheckLoginUseCaseImpl.swift | 28 ++++++++++++- .../MyPage/UpdateNickNameUseCaseImpl.swift | 2 +- .../Entity/MyPage/MyPageResponse.swift | 4 +- .../Repository/AuthAPIRepository.swift | 4 +- .../MyPage/UpdateNickNameUseCase.swift | 2 +- MLS/MLS/Application/AppDelegate.swift | 2 +- .../SelectImage/SelectImageReactor.swift | 5 ++- .../SelectImageViewContoller.swift | 11 +++++ .../SetProfile/SetProfileFactoryImpl.swift | 6 ++- .../SetProfile/SetProfileReactor.swift | 27 +++++++++--- .../SetProfile/SetProfileView.swift | 7 +++- .../SetProfile/SetProfileViewController.swift | 42 +++++++++++++++---- 15 files changed, 125 insertions(+), 32 deletions(-) diff --git a/MLS/Data/Data/Repository/AuthAPIRepositoryImpl.swift b/MLS/Data/Data/Repository/AuthAPIRepositoryImpl.swift index 7874b605..6ff36b5b 100644 --- a/MLS/Data/Data/Repository/AuthAPIRepositoryImpl.swift +++ b/MLS/Data/Data/Repository/AuthAPIRepositoryImpl.swift @@ -109,9 +109,10 @@ public class AuthAPIRepositoryImpl: AuthAPIRepository { return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) } - public func updateNickName(nickName: String) -> Completable { + public func updateNickName(nickName: String) -> Observable { let endPoint = AuthEndPoint.updateNickName(body: NickNameBody(nickname: nickName)) return provider.requestData(endPoint: endPoint, interceptor: tokenInterceptor) + .map { $0.toMyPageDomain() } } public func updateProfileImage(url: String) -> Completable { diff --git a/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift b/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift index 74ad0f42..80855a2a 100644 --- a/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift +++ b/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift @@ -98,7 +98,7 @@ public class AuthAPIRepositoryMock: AuthAPIRepository { return .empty() } - public func updateNickName(nickName: String) -> RxSwift.Completable { + public func updateNickName(nickName: String) -> Observable { return .empty() } } diff --git a/MLS/Domain/Domain/Interceptor/TokenInterceptor.swift b/MLS/Domain/Domain/Interceptor/TokenInterceptor.swift index 1bfb0cfd..6cef609a 100644 --- a/MLS/Domain/Domain/Interceptor/TokenInterceptor.swift +++ b/MLS/Domain/Domain/Interceptor/TokenInterceptor.swift @@ -11,9 +11,15 @@ public class TokenInterceptor: Interceptor { } public func adapt(_ request: URLRequest) -> URLRequest { - var adaptedRequest = request - adaptedRequest.setValue("Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MjU2MDIyNDk4IiwiaWF0IjoxNzYyMzUyODE0LCJleHAiOjIwNzc3MTI4MTR9.6pMpn6kF7zPbVW1U4Lbo1NbhlYc2IwRcDChAERoIo14", forHTTPHeaderField: "Authorization") - return adaptedRequest + let accessFetchResult = fetchTokenUseCase.execute(type: .accessToken) + switch accessFetchResult { + case .success(let token): + var adaptedRequest = request + adaptedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + return adaptedRequest + case .failure: + return request + } } public func retry(data: Data?, response: URLResponse?, error: Error?) -> Bool { diff --git a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/CheckLoginUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/CheckLoginUseCaseImpl.swift index fbe72466..c3cd42df 100644 --- a/MLS/Domain/Domain/UseCaseImpl/AuthAPI/CheckLoginUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/AuthAPI/CheckLoginUseCaseImpl.swift @@ -12,6 +12,32 @@ public class CheckLoginUseCaseImpl: CheckLoginUseCase { } public func execute() -> Observable { - return .just(true) + switch tokenRepository.fetchToken(type: .refreshToken) { + case .success(let token): + return authRepository.reissueToken(refreshToken: token) + .map { [weak self] response in + guard let self else { return false } + + let accessResult = self.tokenRepository.saveToken(type: .accessToken, value: response.accessToken) + let refreshResult = self.tokenRepository.saveToken(type: .refreshToken, value: response.refreshToken) + + switch (accessResult, refreshResult) { + case (.success, .success): + return true + case (.failure(let error), _), + (_, .failure(let error)): + print("Token 저장 실패:", error.localizedDescription) + return false + } + } + .catch { error in + print("reissueToken 실패:", error.localizedDescription) + return .just(false) + } + + case .failure(let error): + print("refreshToken 불러오기 실패:", error.localizedDescription) + return .just(false) + } } } diff --git a/MLS/Domain/Domain/UseCaseImpl/MyPage/UpdateNickNameUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/MyPage/UpdateNickNameUseCaseImpl.swift index e8d5fd25..64fabd1e 100644 --- a/MLS/Domain/Domain/UseCaseImpl/MyPage/UpdateNickNameUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/MyPage/UpdateNickNameUseCaseImpl.swift @@ -9,7 +9,7 @@ public class UpdateNickNameUseCaseImpl: UpdateNickNameUseCase { self.repository = repository } - public func execute(nickName: String) -> Completable { + public func execute(nickName: String) -> Observable { return repository.updateNickName(nickName: nickName) } } diff --git a/MLS/Domain/DomainInterface/Entity/MyPage/MyPageResponse.swift b/MLS/Domain/DomainInterface/Entity/MyPage/MyPageResponse.swift index 89f01c7e..05fed33d 100644 --- a/MLS/Domain/DomainInterface/Entity/MyPage/MyPageResponse.swift +++ b/MLS/Domain/DomainInterface/Entity/MyPage/MyPageResponse.swift @@ -1,5 +1,5 @@ -public struct MyPageResponse { - public let nickname: String +public struct MyPageResponse: Equatable { + public var nickname: String public let jobId: Int? public var jobName: String public let level: Int? diff --git a/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift b/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift index dff7bb8d..56206ab7 100644 --- a/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift +++ b/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift @@ -60,9 +60,9 @@ public protocol AuthAPIRepository { func updateNotificationAgreement(noticeAgreement: Bool, patchNoteAgreement: Bool, eventAgreement: Bool) -> Completable - func updateNickName(nickName: String) -> Completable + func updateNickName(nickName: String) -> Observable func updateProfileImage(url: String) -> Completable - + func fetchProfile() -> Observable } diff --git a/MLS/Domain/DomainInterface/UseCase/MyPage/UpdateNickNameUseCase.swift b/MLS/Domain/DomainInterface/UseCase/MyPage/UpdateNickNameUseCase.swift index 2eca5651..19638955 100644 --- a/MLS/Domain/DomainInterface/UseCase/MyPage/UpdateNickNameUseCase.swift +++ b/MLS/Domain/DomainInterface/UseCase/MyPage/UpdateNickNameUseCase.swift @@ -1,5 +1,5 @@ import RxSwift public protocol UpdateNickNameUseCase { - func execute(nickName: String) -> Completable + func execute(nickName: String) -> Observable } diff --git a/MLS/MLS/Application/AppDelegate.swift b/MLS/MLS/Application/AppDelegate.swift index d13caa1b..0afc6101 100644 --- a/MLS/MLS/Application/AppDelegate.swift +++ b/MLS/MLS/Application/AppDelegate.swift @@ -514,7 +514,7 @@ private extension AppDelegate { CustomerSupportBaseViewFactoryImpl(fetchNoticesUseCase: DIContainer.resolve(type: FetchNoticesUseCase.self), fetchOngoingEventsUseCase: DIContainer.resolve(type: FetchOngoingEventsUseCase.self), fetchOutdatedEventsUseCase: DIContainer.resolve(type: FetchOutdatedEventsUseCase.self), fetchPatchNotesUseCase: DIContainer.resolve(type: FetchPatchNotesUseCase.self), setReadUseCase: DIContainer.resolve(type: SetReadUseCase.self)) } DIContainer.register(type: SetProfileFactory.self) { - SetProfileFactoryImpl(selectImageFactory: DIContainer.resolve(type: SelectImageFactory.self), checkNickNameUseCase: DIContainer.resolve(type: CheckNickNameUseCase.self), updateNickNameUseCase: DIContainer.resolve(type: UpdateNickNameUseCase.self), logoutUseCase: DIContainer.resolve(type: LogoutUseCase.self), withdrawUseCase: DIContainer.resolve(type: WithdrawUseCase.self)) + SetProfileFactoryImpl(selectImageFactory: DIContainer.resolve(type: SelectImageFactory.self), checkNickNameUseCase: DIContainer.resolve(type: CheckNickNameUseCase.self), updateNickNameUseCase: DIContainer.resolve(type: UpdateNickNameUseCase.self), logoutUseCase: DIContainer.resolve(type: LogoutUseCase.self), withdrawUseCase: DIContainer.resolve(type: WithdrawUseCase.self), fetchProfileUseCase: DIContainer.resolve(type: FetchProfileUseCase.self)) } DIContainer.register(type: SetCharacterFactory.self) { SetCharacterFactoryImpl( diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageReactor.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageReactor.swift index 4774a212..49be8575 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageReactor.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageReactor.swift @@ -16,7 +16,7 @@ public final class SelectImageReactor: Reactor { public enum Action { case cancelButtonTapped case applyButtonTapped - case imageTapped(MapleIllustration) + case imageTapped(Int) } public enum Mutation { @@ -61,7 +61,8 @@ public final class SelectImageReactor: Reactor { guard let url = currentState.selectedImage?.url else { return .empty() } return updateProfileImageUseCase.execute(url: url) .andThen(.just(.navigateTo(route: .dismissWithSave))) - case .imageTapped(let image): + case .imageTapped(let index): + let image = currentState.images[index] return .just(.selectImage(image)) } } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift index 51339c81..e0d3a63f 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift @@ -73,6 +73,11 @@ extension SelectImageViewContoller { .map { Reactor.Action.cancelButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) + + mainView.applyButton.rx.tap + .map { Reactor.Action.applyButtonTapped } + .bind(to: reactor.action) + .disposed(by: disposeBag) } func bindViewState(reactor: Reactor) { @@ -80,6 +85,7 @@ extension SelectImageViewContoller { .take(1) .flatMapLatest { _ in reactor.pulse(\.$route) } .withUnretained(self) + .observe(on: MainScheduler.instance) .subscribe { owner, route in switch route { case .dismiss: @@ -106,4 +112,9 @@ extension SelectImageViewContoller: UICollectionViewDelegate, UICollectionViewDa cell.inject(input: SelectImageCell.Input(type: reactor.currentState.images[indexPath.row])) return cell } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let reactor = reactor else { return } + reactor.action.onNext(.imageTapped(indexPath.row)) + } } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileFactoryImpl.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileFactoryImpl.swift index acf6f55d..dfaac96f 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileFactoryImpl.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileFactoryImpl.swift @@ -8,18 +8,20 @@ public final class SetProfileFactoryImpl: SetProfileFactory { private let updateNickNameUseCase: UpdateNickNameUseCase private let logoutUseCase: LogoutUseCase private let withdrawUseCase: WithdrawUseCase + private let fetchProfileUseCase: FetchProfileUseCase - public init(selectImageFactory: SelectImageFactory, checkNickNameUseCase: CheckNickNameUseCase, updateNickNameUseCase: UpdateNickNameUseCase, logoutUseCase: LogoutUseCase, withdrawUseCase: WithdrawUseCase) { + public init(selectImageFactory: SelectImageFactory, checkNickNameUseCase: CheckNickNameUseCase, updateNickNameUseCase: UpdateNickNameUseCase, logoutUseCase: LogoutUseCase, withdrawUseCase: WithdrawUseCase, fetchProfileUseCase: FetchProfileUseCase) { self.selectImageFactory = selectImageFactory self.checkNickNameUseCase = checkNickNameUseCase self.updateNickNameUseCase = updateNickNameUseCase self.logoutUseCase = logoutUseCase self.withdrawUseCase = withdrawUseCase + self.fetchProfileUseCase = fetchProfileUseCase } public func make() -> BaseViewController { let viewController = SetProfileViewController(selectImageFactory: selectImageFactory) - viewController.reactor = SetProfileReactor(checkNickNameUseCase: checkNickNameUseCase, updateNickNameUseCase: updateNickNameUseCase, logoutUseCase: logoutUseCase, withdrawUseCase: withdrawUseCase) + viewController.reactor = SetProfileReactor(checkNickNameUseCase: checkNickNameUseCase, updateNickNameUseCase: updateNickNameUseCase, logoutUseCase: logoutUseCase, withdrawUseCase: withdrawUseCase, fetchProfileUseCase: fetchProfileUseCase) viewController.isBottomTabbarHidden = true return viewController } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileReactor.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileReactor.swift index 0ca02bda..1c4ba994 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileReactor.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileReactor.swift @@ -15,6 +15,7 @@ public final class SetProfileReactor: Reactor { // MARK: - Action public enum Action { + case viewWillAppear case backButtonTapped case editButtonTapped case logoutButtonTapped @@ -30,6 +31,7 @@ public final class SetProfileReactor: Reactor { public enum Mutation { case toNavigate(Route) case setNickName(String) + case setProfile(MyPageResponse?) case showError(Bool) case beginSetText(Bool) case beginEditting @@ -41,9 +43,9 @@ public final class SetProfileReactor: Reactor { public struct State { @Pulse var route: Route = .none var setProfileState: SetProfileView.SetProfileState - var nickName: String = "" var isShowError = false var isEditingNickName = false + var profile: MyPageResponse? = nil } // MARK: - Properties @@ -53,13 +55,15 @@ public final class SetProfileReactor: Reactor { private let updateNickNameUseCase: UpdateNickNameUseCase private let logoutUseCase: LogoutUseCase private let withdrawUseCase: WithdrawUseCase + private let fetchProfileUseCase: FetchProfileUseCase // MARK: - Init - public init(checkNickNameUseCase: CheckNickNameUseCase, updateNickNameUseCase: UpdateNickNameUseCase, logoutUseCase: LogoutUseCase, withdrawUseCase: WithdrawUseCase) { + public init(checkNickNameUseCase: CheckNickNameUseCase, updateNickNameUseCase: UpdateNickNameUseCase, logoutUseCase: LogoutUseCase, withdrawUseCase: WithdrawUseCase, fetchProfileUseCase: FetchProfileUseCase) { self.checkNickNameUseCase = checkNickNameUseCase self.updateNickNameUseCase = updateNickNameUseCase self.logoutUseCase = logoutUseCase self.withdrawUseCase = withdrawUseCase + self.fetchProfileUseCase = fetchProfileUseCase } // MARK: - Mutate @@ -85,8 +89,14 @@ public final class SetProfileReactor: Reactor { case .editButtonTapped: switch currentState.setProfileState { case .edit: - return updateNickNameUseCase.execute(nickName: currentState.nickName) - .andThen(Observable.just(.completeEditting)) + guard let profile = currentState.profile else { return .empty() } + return updateNickNameUseCase.execute(nickName: profile.nickname) + .flatMap { profile in + Observable.concat([ + .just(.setProfile(profile)), + .just(.completeEditting) + ]) + } case .normal: return .just(.beginEditting) } @@ -100,6 +110,9 @@ public final class SetProfileReactor: Reactor { case .withdraw: return withdrawUseCase.execute() .andThen(.empty()) + case .viewWillAppear: + return fetchProfileUseCase.execute() + .map { Mutation.setProfile($0)} } } @@ -110,8 +123,6 @@ public final class SetProfileReactor: Reactor { switch mutation { case .toNavigate(let route): newState.route = route - case .setNickName(let nickName): - newState.nickName = nickName case .showError(let error): newState.isShowError = error case .beginSetText(let isEditing): @@ -122,6 +133,10 @@ public final class SetProfileReactor: Reactor { newState.setProfileState = .edit case .completeEditting: newState.route = .dismissWithUpdate + case .setProfile(let profile): + newState.profile = profile + case .setNickName(let nickName): + newState.profile?.nickname = nickName } return newState diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileView.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileView.swift index e14464d4..42ed9c89 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileView.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileView.swift @@ -1,5 +1,6 @@ import UIKit +import BaseFeature import DesignSystem import DomainInterface @@ -381,8 +382,10 @@ public extension SetProfileView { } } - func setImage(image: UIImage) { - imageView.image = image + func setImage(imageUrl: String) { + ImageLoader.shared.loadImage(stringURL: imageUrl) { [weak self] image in + self?.imageView.image = image + } } func setName(name: String) { diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileViewController.swift index a4070d53..bc29b5ac 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileViewController.swift @@ -18,7 +18,7 @@ public final class SetProfileViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + var didReturn = PublishRelay() private var selectImageFactory: SelectImageFactory @@ -34,9 +34,6 @@ public final class SetProfileViewController: BaseViewController, View { public init(selectImageFactory: SelectImageFactory) { self.selectImageFactory = selectImageFactory super.init() - mainView.setName(name: "익명의 오무라이스케챱") - mainView.setImage(image: .add) - mainView.setPlatform(platform: .kakao) } @available(*, unavailable) @@ -79,6 +76,11 @@ extension SetProfileViewController { } private func bindUserActions(reactor: Reactor) { + rx.viewWillAppear + .map { Reactor.Action.viewWillAppear } + .bind(to: reactor.action) + .disposed(by: disposeBag) + mainView.backButton.rx.tap .map { Reactor.Action.backButtonTapped } .bind(to: reactor.action) @@ -120,6 +122,7 @@ extension SetProfileViewController { .map(\.setProfileState) .distinctUntilChanged() .withUnretained(self) + .observe(on: MainScheduler.instance) .bind(onNext: { owner, state in owner.view.backgroundColor = state == .edit ? .whiteMLS : .neutral100 owner.mainView.setCountHidden(state: state) @@ -127,13 +130,26 @@ extension SetProfileViewController { }) .disposed(by: disposeBag) + reactor.state + .compactMap(\.profile) + .distinctUntilChanged() + .withUnretained(self) + .observe(on: MainScheduler.instance) + .bind(onNext: { owner, profile in + owner.mainView.setName(name: profile.nickname) + owner.mainView.setImage(imageUrl: profile.profileUrl) + owner.mainView.setPlatform(platform: profile.platform) + }) + .disposed(by: disposeBag) + reactor.state .filter(\.isEditingNickName) - .map(\.nickName) + .compactMap(\.profile?.nickname) .distinctUntilChanged() .withUnretained(self) - .bind(onNext: { owner, nickName in - owner.mainView.setCount(count: nickName.count) + .observe(on: MainScheduler.instance) + .bind(onNext: { owner, nickname in + owner.mainView.setCount(count: nickname.count) }) .disposed(by: disposeBag) @@ -141,6 +157,7 @@ extension SetProfileViewController { .map(\.isShowError) .distinctUntilChanged() .withUnretained(self) + .observe(on: MainScheduler.instance) .bind(onNext: { owner, isShowError in owner.mainView.setError(isError: isShowError) }) @@ -150,10 +167,21 @@ extension SetProfileViewController { .take(1) .flatMapLatest { _ in reactor.pulse(\.$route) } .withUnretained(self) + .observe(on: MainScheduler.instance) .subscribe { owner, route in switch route { case .imageBottomSheet: let viewController = owner.selectImageFactory.make() + + if let viewController = viewController as? UIViewController { + viewController.rx + .methodInvoked(#selector(UIViewController.viewDidDisappear)) + .take(1) + .map { _ in Reactor.Action.viewWillAppear } + .bind(to: reactor.action) + .disposed(by: owner.disposeBag) + } + owner.presentModal(viewController) case .dismiss: owner.didReturn.accept(false) From 93e3c2c3f7444f8862bc6bb4f48ba6fce6beb435 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Nov 2025 07:58:30 +0000 Subject: [PATCH 09/14] style/#264: Apply SwiftLint autocorrect --- .../DTO/DictionaryDTO/DictionaryAllDTO.swift | 1 - .../DTO/SearchCountDTO/SearchCountDTO.swift | 2 +- .../Endpoints/DictionaryListEndPoint.swift | 4 +- .../Network/NetworkProviderImpl.swift | 2 +- .../UserDefaultsRepositoryImpl.swift | 3 +- .../DataMock/Factory/LoginFactoryMock.swift | 4 +- .../Repository/AuthAPIRepositoryMock.swift | 2 +- .../FetchDictionaryAllListUseCaseImpl.swift | 6 +- ...FetchDictionarySearchListUseCaseImpl.swift | 4 +- .../FetchDictionaryListCountUseCaseImpl.swift | 4 +- .../RecentSearchAddUseCaseImpl.swift | 6 +- .../RecentSearchFetchUseCaseImpl.swift | 4 +- .../DictionaryList/DictionnaryItemType.swift | 2 +- .../SearchCount/SearchCountResponse.swift | 2 +- .../Repository/AuthAPIRepository.swift | 2 +- .../Repository/UserDefaultsRepository.swift | 2 +- .../AuthFeature/Login/LoginReactor.swift | 4 +- .../AuthFeature/Login/LoginView.swift | 4 +- .../Login/LoginViewController.swift | 2 +- ...OnBoardingNotificationViewController.swift | 4 +- .../OnBoardingInputViewController.swift | 2 +- ...rdingNotificationSheetViewController.swift | 2 +- .../OnBoardingQuestionViewController.swift | 4 +- .../TermsAgreementViewController.swift | 2 +- .../AddCollectionViewController.swift | 2 +- .../BookmarkEmpty/BookmarkEmptyView.swift | 2 +- .../BookmarkListViewController.swift | 10 +-- .../BookmarkMainViewController.swift | 2 +- .../BookmarkModalViewController.swift | 2 +- .../CollectionDetailViewController.swift | 4 +- .../CollectionEditViewController.swift | 2 +- .../CollectionSettingViewController.swift | 2 +- .../Tabbar/BottomTabBarController.swift | 2 +- .../DictionaryDetailBaseView.swift | 2 +- .../DictionaryDetailBaseViewController.swift | 68 +++++++++---------- .../Item/ItemDictionaryDetailReactor.swift | 2 +- .../ItemDictionaryDetailViewController.swift | 2 +- .../Map/MapDictionaryDetailReactor.swift | 1 - ...onsterDictionaryDetailViewController.swift | 2 +- .../NpcDictionaryDetailViewController.swift | 2 +- .../QuestDictionaryDetailViewController.swift | 2 +- .../DictionaryListViewController.swift | 18 ++--- .../DictionaryMainViewController.swift | 2 +- .../DictionaryNotificationReactor.swift | 6 +- ...DictionaryNotificationViewController.swift | 4 +- .../DictionarySearchFactoryImpl.swift | 2 +- .../DictionarySearchReactor.swift | 12 ++-- .../DictionarySearchViewController.swift | 8 +-- .../DictionarySearchResultReactor.swift | 14 ++-- ...DictionarySearchResultViewController.swift | 22 +++--- .../SortedBottomSheetViewController.swift | 2 +- .../DictionaryFeatureDemo/AppDelegate.swift | 2 +- .../ViewController.swift | 2 +- .../AnnouncementViewController.swift | 2 +- .../CustomerSupportBaseViewController.swift | 4 +- .../Event/EventViewController.swift | 1 - .../Main/MyPageMainViewController.swift | 2 +- .../NotificationSettingReactor.swift | 2 +- .../SelectImageViewContoller.swift | 4 +- .../SetCharacterViewController.swift | 2 +- .../SetProfile/SetProfileReactor.swift | 2 +- 61 files changed, 142 insertions(+), 154 deletions(-) diff --git a/MLS/Data/Data/Network/DTO/DictionaryDTO/DictionaryAllDTO.swift b/MLS/Data/Data/Network/DTO/DictionaryDTO/DictionaryAllDTO.swift index 0ac1e908..074cd7fe 100644 --- a/MLS/Data/Data/Network/DTO/DictionaryDTO/DictionaryAllDTO.swift +++ b/MLS/Data/Data/Network/DTO/DictionaryDTO/DictionaryAllDTO.swift @@ -7,4 +7,3 @@ public struct DictionaryAllDTO: DictionaryDTOProtocol { public let bookmarkId: Int? public var id: Int { originalId } } - diff --git a/MLS/Data/Data/Network/DTO/SearchCountDTO/SearchCountDTO.swift b/MLS/Data/Data/Network/DTO/SearchCountDTO/SearchCountDTO.swift index bdbd49f0..885b5795 100644 --- a/MLS/Data/Data/Network/DTO/SearchCountDTO/SearchCountDTO.swift +++ b/MLS/Data/Data/Network/DTO/SearchCountDTO/SearchCountDTO.swift @@ -2,7 +2,7 @@ import DomainInterface public struct SearchCountDTO: Decodable { public let counts: Int? - + public func toDomain() -> SearchCountResponse { return SearchCountResponse(count: counts) } diff --git a/MLS/Data/Data/Network/Endpoints/DictionaryListEndPoint.swift b/MLS/Data/Data/Network/Endpoints/DictionaryListEndPoint.swift index abb885b3..3dd3af99 100644 --- a/MLS/Data/Data/Network/Endpoints/DictionaryListEndPoint.swift +++ b/MLS/Data/Data/Network/Endpoints/DictionaryListEndPoint.swift @@ -11,8 +11,8 @@ public enum DictionaryListEndPoint { return .init(baseURL: base, path: "/api/v1/\(type)/counts", method: .GET, query: query) } // 전체 리스트 - public static func fetchAllList(keyword: String?, page: Int? = nil, size: Int? = nil) -> ResponsableEndPoint>{ - let query = DictionaryListQuery(keyword: keyword ?? "",page: page ?? 0, size: size ?? 20, sort: nil) + public static func fetchAllList(keyword: String?, page: Int? = nil, size: Int? = nil) -> ResponsableEndPoint> { + let query = DictionaryListQuery(keyword: keyword ?? "", page: page ?? 0, size: size ?? 20, sort: nil) return .init(baseURL: base, path: "/api/v1/search", method: .GET, query: query) } // 몬스터 리스트 diff --git a/MLS/Data/Data/Providers/Network/NetworkProviderImpl.swift b/MLS/Data/Data/Providers/Network/NetworkProviderImpl.swift index eece6493..959eb5d0 100644 --- a/MLS/Data/Data/Providers/Network/NetworkProviderImpl.swift +++ b/MLS/Data/Data/Providers/Network/NetworkProviderImpl.swift @@ -22,7 +22,7 @@ public final class NetworkProviderImpl: NetworkProvider { print("🚀 requestData: 요청 시작 - \(endPoint)") self?.sendRequest(endPoint: endPoint, interceptor: interceptor, completion: { result in - + switch result { case .success(let data): print("✅ requestData: 응답 수신") diff --git a/MLS/Data/Data/Repository/UserDefaultsRepositoryImpl.swift b/MLS/Data/Data/Repository/UserDefaultsRepositoryImpl.swift index 1fb04a83..f0e8f118 100644 --- a/MLS/Data/Data/Repository/UserDefaultsRepositoryImpl.swift +++ b/MLS/Data/Data/Repository/UserDefaultsRepositoryImpl.swift @@ -50,8 +50,7 @@ public final class UserDefaultsRepositoryImpl: UserDefaultsRepository { public func fetchPlatform() -> Observable { return Observable.create { observer in if let rawValue = UserDefaults.standard.string(forKey: self.platformKey), - let platform = LoginPlatform(rawValue: rawValue) - { + let platform = LoginPlatform(rawValue: rawValue) { observer.onNext(platform) } else { observer.onNext(nil) diff --git a/MLS/Data/DataMock/Factory/LoginFactoryMock.swift b/MLS/Data/DataMock/Factory/LoginFactoryMock.swift index ce48cb8c..1bb84a2d 100644 --- a/MLS/Data/DataMock/Factory/LoginFactoryMock.swift +++ b/MLS/Data/DataMock/Factory/LoginFactoryMock.swift @@ -11,9 +11,9 @@ public final class LoginFactoryMock: LoginFactory { viewController.view.backgroundColor = .blue return viewController } - + public func make(exitRoute: LoginExitRoute, onLoginCompleted: (() -> Void)?) -> BaseViewController { return BaseViewController() } - + } diff --git a/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift b/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift index 80855a2a..587b54f9 100644 --- a/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift +++ b/MLS/Data/DataMock/Repository/AuthAPIRepositoryMock.swift @@ -9,7 +9,7 @@ public class AuthAPIRepositoryMock: AuthAPIRepository { public func fetchProfile() -> Observable { return .empty() } - + public func fetchJob(jobId: String) -> Observable { return .empty() } diff --git a/MLS/Domain/Domain/UseCaseImpl/DictionaryList/FetchDictionaryAllListUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/DictionaryList/FetchDictionaryAllListUseCaseImpl.swift index 37b5b4a1..b146cee6 100644 --- a/MLS/Domain/Domain/UseCaseImpl/DictionaryList/FetchDictionaryAllListUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/DictionaryList/FetchDictionaryAllListUseCaseImpl.swift @@ -3,13 +3,13 @@ import DomainInterface import RxSwift public final class FetchDictionaryAllListUseCaseImpl: FetchDictionaryAllListUseCase { - + private let repository: DictionaryListAPIRepository - + public init(repository: DictionaryListAPIRepository) { self.repository = repository } - + public func execute(keyword: String?, page: Int?) -> Observable { return repository.fetchAllList(keyword: keyword, page: page) } diff --git a/MLS/Domain/Domain/UseCaseImpl/DictionaryList/FetchDictionarySearchListUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/DictionaryList/FetchDictionarySearchListUseCaseImpl.swift index 6c5cfaa2..69e0fcb4 100644 --- a/MLS/Domain/Domain/UseCaseImpl/DictionaryList/FetchDictionarySearchListUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/DictionaryList/FetchDictionarySearchListUseCaseImpl.swift @@ -4,11 +4,11 @@ import RxSwift public final class FetchDictionarySearchListUseCaseImpl: FetchDictionarySearchListUseCase { private let repository: DictionaryListAPIRepository - + public init(repository: DictionaryListAPIRepository) { self.repository = repository } - + public func execute(keyword: String) -> Observable { return repository.fetchAllList(keyword: keyword, page: nil) } diff --git a/MLS/Domain/Domain/UseCaseImpl/DictionaryListCount/FetchDictionaryListCountUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/DictionaryListCount/FetchDictionaryListCountUseCaseImpl.swift index 75743ab4..eb58efa8 100644 --- a/MLS/Domain/Domain/UseCaseImpl/DictionaryListCount/FetchDictionaryListCountUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/DictionaryListCount/FetchDictionaryListCountUseCaseImpl.swift @@ -4,11 +4,11 @@ import RxSwift public final class FetchDictionaryListCountUseCaseImpl: FetchDictionaryListCountUseCase { private let repository: DictionaryListAPIRepository - + public init(repository: DictionaryListAPIRepository) { self.repository = repository } - + public func execute(type: String, keyword: String?) -> Observable { return repository.fetchSearchListCount(type: type, keyword: keyword) } diff --git a/MLS/Domain/Domain/UseCaseImpl/RecentSearch/RecentSearchAddUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/RecentSearch/RecentSearchAddUseCaseImpl.swift index 566d981c..a56d8544 100644 --- a/MLS/Domain/Domain/UseCaseImpl/RecentSearch/RecentSearchAddUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/RecentSearch/RecentSearchAddUseCaseImpl.swift @@ -5,13 +5,13 @@ import RxSwift public class RecentSearchAddUseCaseImpl: RecentSearchAddUseCase { var repository: UserDefaultsRepository - + public init(repository: UserDefaultsRepository) { self.repository = repository } - + public func add(keyword: String) -> Completable { return repository.addRecentSearch(keyword: keyword) } - + } diff --git a/MLS/Domain/Domain/UseCaseImpl/RecentSearch/RecentSearchFetchUseCaseImpl.swift b/MLS/Domain/Domain/UseCaseImpl/RecentSearch/RecentSearchFetchUseCaseImpl.swift index 87afda9e..e4e440d7 100644 --- a/MLS/Domain/Domain/UseCaseImpl/RecentSearch/RecentSearchFetchUseCaseImpl.swift +++ b/MLS/Domain/Domain/UseCaseImpl/RecentSearch/RecentSearchFetchUseCaseImpl.swift @@ -4,11 +4,11 @@ import RxSwift public final class RecentSearchFetchUseCaseImpl: RecentSearchFetchUseCase { private let repository: UserDefaultsRepository - + public init(repository: UserDefaultsRepository) { self.repository = repository } - + public func fetch() -> Observable<[String]> { return repository.fetchRecentSearch() } diff --git a/MLS/Domain/DomainInterface/Entity/DictionaryList/DictionnaryItemType.swift b/MLS/Domain/DomainInterface/Entity/DictionaryList/DictionnaryItemType.swift index 6591e63d..3ea6cc2e 100644 --- a/MLS/Domain/DomainInterface/Entity/DictionaryList/DictionnaryItemType.swift +++ b/MLS/Domain/DomainInterface/Entity/DictionaryList/DictionnaryItemType.swift @@ -36,7 +36,7 @@ public enum DictionaryItemType: String { "퀘스트 상세 정보" } } - + public var toDictionaryType: DictionaryType? { switch self { case .item: diff --git a/MLS/Domain/DomainInterface/Entity/SearchCount/SearchCountResponse.swift b/MLS/Domain/DomainInterface/Entity/SearchCount/SearchCountResponse.swift index 5401eeaa..ef99985a 100644 --- a/MLS/Domain/DomainInterface/Entity/SearchCount/SearchCountResponse.swift +++ b/MLS/Domain/DomainInterface/Entity/SearchCount/SearchCountResponse.swift @@ -1,6 +1,6 @@ public struct SearchCountResponse: Decodable { public let count: Int? - + public init(count: Int?) { self.count = count } diff --git a/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift b/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift index 56206ab7..1b0b28c6 100644 --- a/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift +++ b/MLS/Domain/DomainInterface/Repository/AuthAPIRepository.swift @@ -63,6 +63,6 @@ public protocol AuthAPIRepository { func updateNickName(nickName: String) -> Observable func updateProfileImage(url: String) -> Completable - + func fetchProfile() -> Observable } diff --git a/MLS/Domain/DomainInterface/Repository/UserDefaultsRepository.swift b/MLS/Domain/DomainInterface/Repository/UserDefaultsRepository.swift index d9badd8e..b469b8c1 100644 --- a/MLS/Domain/DomainInterface/Repository/UserDefaultsRepository.swift +++ b/MLS/Domain/DomainInterface/Repository/UserDefaultsRepository.swift @@ -4,7 +4,7 @@ public protocol UserDefaultsRepository { func fetchRecentSearch() -> Observable<[String]> func addRecentSearch(keyword: String) -> Completable func removeRecentSearch(keyword: String) -> Completable - + func fetchPlatform() -> Observable func savePlatform(platform: LoginPlatform) -> Completable } diff --git a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginReactor.swift b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginReactor.swift index d710266e..5aa15ac3 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginReactor.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginReactor.swift @@ -27,7 +27,7 @@ public final class LoginReactor: Reactor { public struct State { @Pulse var route: Route = .none - var platform: LoginPlatform? = nil + var platform: LoginPlatform? } // MARK: - properties @@ -66,7 +66,7 @@ public final class LoginReactor: Reactor { switch action { case .viewWillAppear: return fetchPlatformUseCase.execute() - .map{ Mutation.setRelogin($0) } + .map { Mutation.setRelogin($0) } case .kakaoLoginButtonTapped: return handleKakaoLogin() case .appleLoginButtonTapped: diff --git a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginView.swift b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginView.swift index d2302a0e..51437863 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginView.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginView.swift @@ -111,7 +111,7 @@ private extension LoginView { func addViews() { addSubview(loginImageView) addSubview(buttonStackView) - + buttonStackView.addArrangedSubview(kakaoLoginButton) buttonStackView.addArrangedSubview(appleLoginButton) @@ -194,7 +194,7 @@ extension LoginView { guestLoginButton.snp.remakeConstraints { make in make.height.equalTo(Constant.buttonHeight) } - + } setNeedsLayout() diff --git a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginViewController.swift index 2852d204..5a1ba53e 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/Login/LoginViewController.swift @@ -13,7 +13,7 @@ public final class LoginViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + public let routeToHome = PublishRelay() private let mainView: LoginView diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoadingNotification/OnBoardingNotificationViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoadingNotification/OnBoardingNotificationViewController.swift index 6005c9ed..4606d009 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoadingNotification/OnBoardingNotificationViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoadingNotification/OnBoardingNotificationViewController.swift @@ -12,9 +12,9 @@ import SnapKit public class OnBoardingNotificationViewController: BaseViewController, View { // MARK: - Properties public typealias Reactor = OnBoardingNotificationReactor - + public var disposeBag = DisposeBag() - + private let onBoardingNotificationSheetFactory: OnBoardingNotificationSheetFactory // MARK: - Components diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingInput/OnBoardingInputViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingInput/OnBoardingInputViewController.swift index 8002c956..93ec8eb6 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingInput/OnBoardingInputViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingInput/OnBoardingInputViewController.swift @@ -15,7 +15,7 @@ public class OnBoardingInputViewController: BaseViewController, View { public typealias Reactor = OnBoardingInputReactor public var disposeBag = DisposeBag() - + private let onBoardingNotificationFactory: OnBoardingNotificationFactory // MARK: - Components diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift index 024a866a..5738ad10 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingNotificationSheet/OnBoardingNotificationSheetViewController.swift @@ -13,7 +13,7 @@ public final class OnBoardingNotificationSheetViewController: BaseViewController public var modalHeight: CGFloat? public typealias Reactor = OnBoardingNotificationSheetReactor - + public var disposeBag = DisposeBag() // MARK: - Properties diff --git a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingQuestion/OnBoardingQuestionViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingQuestion/OnBoardingQuestionViewController.swift index 90ca08b4..c1dbcf47 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingQuestion/OnBoardingQuestionViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/OnBoardingQuestion/OnBoardingQuestionViewController.swift @@ -11,9 +11,9 @@ import SnapKit public class OnBoardingQuestionViewController: BaseViewController, View { // MARK: - Properties public typealias Reactor = OnBoardingQuestionReactor - + public var disposeBag = DisposeBag() - + private let onBoardingInputFactory: OnBoardingInputFactory // MARK: - Components diff --git a/MLS/Presentation/AuthFeature/AuthFeature/TermsAgreement/TermsAgreementViewController.swift b/MLS/Presentation/AuthFeature/AuthFeature/TermsAgreement/TermsAgreementViewController.swift index a520bf41..bb7d2325 100644 --- a/MLS/Presentation/AuthFeature/AuthFeature/TermsAgreement/TermsAgreementViewController.swift +++ b/MLS/Presentation/AuthFeature/AuthFeature/TermsAgreement/TermsAgreementViewController.swift @@ -13,7 +13,7 @@ public class TermsAgreementViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let onBoardingQuestionFactory: OnBoardingQuestionFactory private var mainView = TermsAgreementView() diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/AddCollection/AddCollectionViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/AddCollection/AddCollectionViewController.swift index cd42f150..72aff19c 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/AddCollection/AddCollectionViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/AddCollection/AddCollectionViewController.swift @@ -14,7 +14,7 @@ public final class AddCollectionViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + public var onDismissWithMessage: ((BookmarkCollection?) -> Void)? // MARK: - Components diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift index 553cd23f..3d677c56 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift @@ -66,7 +66,7 @@ private extension BookmarkEmptyView { make.width.equalTo(Constant.buttonWidth) } } - + func configureUI() { backgroundColor = .neutral100 } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift index 4e392efb..87b2f5a8 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkList/BookmarkListViewController.swift @@ -117,12 +117,12 @@ extension BookmarkListViewController { .map { Reactor.Action.filterButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.editButton?.rx.tap .map { Reactor.Action.editButtonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + emptyView.button.rx.tap .map { .emptyButtonTapped } .bind(to: reactor.action) @@ -135,7 +135,7 @@ extension BookmarkListViewController { .distinctUntilChanged() .withUnretained(self) .observe(on: MainScheduler.instance) - .bind(onNext: { owner, items in + .bind(onNext: { owner, _ in owner.mainView.listCollectionView.reloadData() }) .disposed(by: disposeBag) @@ -158,8 +158,8 @@ extension BookmarkListViewController { switch type { case .item: break - //let viewController = owner.itemFilterFactory.make() - //owner.present(viewController, animated: true) + // let viewController = owner.itemFilterFactory.make() + // owner.present(viewController, animated: true) case .monster: let viewController = owner.monsterFilterFactory.make(startLevel: reactor.currentState.startLevel ?? 1, endLevel: reactor.currentState.endLevel ?? 200) { startLevel, endLevel in diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainViewController.swift index 43f580c8..1352a597 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkMain/BookmarkMainViewController.swift @@ -14,7 +14,7 @@ public final class BookmarkMainViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let initialIndex: Int private lazy var currentPageIndex = BehaviorRelay(value: initialIndex) diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkModal/BookmarkModalViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkModal/BookmarkModalViewController.swift index 20cb1f97..1faa22d3 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkModal/BookmarkModalViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkModal/BookmarkModalViewController.swift @@ -16,7 +16,7 @@ public final class BookmarkModalViewController: BaseViewController, View { public var onDismissWithMessage: ((BookmarkCollection?) -> Void)? public var onDismissWithCollections: (([BookmarkCollection?]) -> Void)? - + private let addCollectionFactory: AddCollectionFactory // MARK: - Components diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift index 418e4f12..dfbcae9a 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionDetail/CollectionDetailViewController.swift @@ -173,7 +173,7 @@ extension CollectionDetailViewController { owner.reactor?.action.onNext(.selectSetting(menu)) }) owner.presentModal(viewController) - case .detail(let type,let id): + case .detail(let type, let id): let viewController = owner.dictionaryDetailFactory.make(type: type, id: id) owner.navigationController?.pushViewController(viewController, animated: true) default: @@ -254,7 +254,7 @@ extension CollectionDetailViewController: UICollectionViewDelegate, UICollection return cell } - + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { reactor?.action.onNext(.dataTapped(indexPath.row)) } diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditViewController.swift index 06fb8bd4..c97eee38 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionEdit/CollectionEditViewController.swift @@ -12,7 +12,7 @@ public final class CollectionEditViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let bookmarkModalFactory: BookmarkModalFactory // MARK: - Components diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionSettingSheet/CollectionSettingViewController.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionSettingSheet/CollectionSettingViewController.swift index 082b11c4..30f06bb2 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionSettingSheet/CollectionSettingViewController.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/CollectionSettingSheet/CollectionSettingViewController.swift @@ -15,7 +15,7 @@ public final class CollectionSettingViewController: BaseViewController, ModalPre // MARK: - Properties public var disposeBag = DisposeBag() - + public var setMenu: ((CollectionSettingMenu) -> Void)? private var mainView = CollectionSettingView() diff --git a/MLS/Presentation/DesignSystem/DesignSystem/Components/Tabbar/BottomTabBarController.swift b/MLS/Presentation/DesignSystem/DesignSystem/Components/Tabbar/BottomTabBarController.swift index 2482b338..adfb9941 100644 --- a/MLS/Presentation/DesignSystem/DesignSystem/Components/Tabbar/BottomTabBarController.swift +++ b/MLS/Presentation/DesignSystem/DesignSystem/Components/Tabbar/BottomTabBarController.swift @@ -95,7 +95,7 @@ public extension BottomTabBarController { divider.alpha = hidden ? 0 : 1 } } - + func selectTab(index: Int, animated: Bool = false) { UIView.performWithoutAnimation { selectedIndex = index diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseView.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseView.swift index 9c323797..f8e4f3ae 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseView.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseView.swift @@ -439,7 +439,7 @@ extension DictionaryDetailBaseView { tabBarStackView.addArrangedSubview(spacerView) tabBarStickyStackView.addArrangedSubview(stickySpacerView) } - + func setBookmark(isBookmarked: Bool) { bookmarkButton.setImage(DesignSystemAsset.image(named: isBookmarked ? "bookmark" : "bookmarkGrayBorder"), for: .normal) } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift index 4cd7a15a..808f7349 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift @@ -26,15 +26,15 @@ class DictionaryDetailBaseViewController: BaseViewController { /// 현재 보여지고 있는 뷰의 인덱스 private var currentTabIndex: Int? - + public let bookmarkModalFactory: BookmarkModalFactory // MARK: - Components public var mainView = DictionaryDetailBaseView() - + // 타입설정 public var type: DictionaryItemType - + public init(type: DictionaryItemType, bookmarkModalFactory: BookmarkModalFactory) { self.type = type self.bookmarkModalFactory = bookmarkModalFactory @@ -42,25 +42,25 @@ class DictionaryDetailBaseViewController: BaseViewController { super.init() isBottomTabbarHidden = true } - + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() - + addViews() setupConstraints() configureUI() bind() // 액션 바인딩 setupMenu(type.detailTypes) } - + override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - + // 처음 진입 및 뷰가 추가 되었는지 확인 if !didSelectInitialTab { didSelectMenuTab(index: 0) @@ -74,14 +74,14 @@ private extension DictionaryDetailBaseViewController { func addViews() { view.addSubview(mainView) } - + func setupConstraints() { mainView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) make.horizontalEdges.bottom.equalToSuperview() } } - + func configureUI() { mainView.scrollView.delegate = self } @@ -92,7 +92,7 @@ extension DictionaryDetailBaseViewController: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // 탭바의 frame을 self.view 기준으로 변환 let tabBarY = mainView.tabBarStackView.convert(mainView.tabBarStackView.bounds, to: view) - + if tabBarY.origin.y <= view.safeAreaInsets.top + DictionaryDetailBaseView.Constant.buttonSize + DictionaryDetailBaseView.Constant.horizontalInset - DictionaryDetailBaseView.Constant.tabBarStackViewInset.top { // safearea + 헤더뷰 + 헤더뷰와의 간격 16 = 122 mainView.tabBarStickyStackView.isHidden = false mainView.stickyTabBarDividerView.isHidden = false @@ -100,9 +100,9 @@ extension DictionaryDetailBaseViewController: UIScrollViewDelegate { mainView.tabBarStickyStackView.isHidden = true mainView.stickyTabBarDividerView.isHidden = true } - + let nameY = mainView.nameLabel.convert(mainView.nameLabel.bounds, to: view) - + if nameY.origin.y <= view.safeAreaInsets.top + DictionaryDetailBaseView.Constant.buttonSize + DictionaryDetailBaseView.Constant.horizontalInset { // 메랜에서 이름이 가장 긴 몬스터의 경우 '다크 주니어 예티와 페페'로 알고 있는데, 따로 텍스트 길이에 대한 제약사항을 안줘도 다크 주니어 예티와 페페가 잘 표시가 됨. 제약사항 필요한가? mainView.titleLabel.attributedText = .makeStyledString(font: .sub_m_b, text: mainView.nameLabel.text) @@ -123,7 +123,7 @@ extension DictionaryDetailBaseViewController { let name: String let subText: String? // 없는 경우도 있는듯 } - + func inject(input: Input) { // Load image if URL exists if let imageUrlString = input.imageUrl { @@ -131,7 +131,7 @@ extension DictionaryDetailBaseViewController { guard let self = self, let image = image else { return } self.mainView.imageView.image = image } - + } else { mainView.imageView.image = nil // Clear image if no URL } @@ -139,32 +139,32 @@ extension DictionaryDetailBaseViewController { mainView.nameLabel.attributedText = .makeStyledString(font: .sub_l_m, text: input.name, color: .textColor) mainView.subTextLabel.attributedText = .makeStyledString(font: .b_s_r, text: input.subText, color: .neutral500) } - + func makeTagsRow(_ tags: Effectiveness) { let maxWidth = UIScreen.main.bounds.width - DictionaryDetailBaseView.Constant.horizontalInset // 좌우 여백 고려 (16 * 2) let tagSpacing: CGFloat = DictionaryDetailBaseView.Constant.tagVerticalSpacing - + var tagsRowStackView = mainView.createHorizontalStackView() mainView.tagsVerticalStackView.addArrangedSubview(tagsRowStackView) - + var currentRowWidth: CGFloat = 0 for (element, value) in tags.nonNilElements() { let badge = Badge(style: .element("\(element.rawValue) \(value)")) let fittingSize = badge.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) let badgeWidth = fittingSize.width - + if currentRowWidth + badgeWidth + tagSpacing > maxWidth { tagsRowStackView = mainView.createHorizontalStackView() mainView.tagsVerticalStackView.addArrangedSubview(tagsRowStackView) currentRowWidth = 0 } - + tagsRowStackView.addArrangedSubview(badge) currentRowWidth += badgeWidth + tagSpacing mainView.setBadgeConstraints(badge, width: badgeWidth) } } - + // 탭바 메뉴 버튼 구성하기(스티키 쪽도 똑같이 구현) func setupMenu(_ menus: [DetailType]) { var firstIndexButton: UIButton? // 처음 화면 나올때 첫번째 버튼 클릭되게 하기 위해 첫번째 버튼 저장할 변수 @@ -176,16 +176,16 @@ extension DictionaryDetailBaseViewController { self?.menuTabTapped(button) } .disposed(by: disposeBag) - + let stickyButton = mainView.createMenuButton(title: menu.description, tag: index) stickyButton.rx.tap.bind { [weak self] _ in self?.menuTabTapped(stickyButton) } .disposed(by: disposeBag) - + mainView.tabBarStackView.addArrangedSubview(button) mainView.tabBarStickyStackView.addArrangedSubview(stickyButton) // 스티키 역할을 할 스택뷰에다가도 똑같은 버튼 추가 - + if index == 0 { firstIndexButton = button firstStickyIndexButton = button @@ -198,26 +198,26 @@ extension DictionaryDetailBaseViewController { menuTabTapped(firstStickyIndexButton) } } - + private func menuTabTapped(_ sender: UIButton) { let selectedTag = sender.tag - + updateButtonStates(in: mainView.tabBarStackView, selectedTag: selectedTag) // 스티키 탭 버튼 상태 변경 updateButtonStates(in: mainView.tabBarStickyStackView, selectedTag: selectedTag) - + // 자식 디테일 뷰컨에서 오버라이드 해서 사용하기? didSelectMenuTab(index: sender.tag) } - + // 버튼 상태 변경 함수 private func updateButtonStates(in stackView: UIStackView, selectedTag: Int) { for (index, subview) in stackView.arrangedSubviews.enumerated() { guard let button = subview as? UIButton else { continue } let title = button.titleLabel?.text ?? "" - + let underline = button.subviews.first { $0.tag == DictionaryDetailBaseView.Constant.underTag } - + if index == selectedTag { button.setAttributedTitle(.makeStyledString(font: .sub_m_b, text: title, color: .black), for: .normal) underline?.isHidden = false @@ -227,18 +227,18 @@ extension DictionaryDetailBaseViewController { } } } - + func didSelectMenuTab(index: Int) { // 인덱스 유효성 검사 guard index < contentViews.count else { return } - + // 현재 뷰가 같다면 변경 안함 if currentTabIndex == index { return } // 각 탭에 맞는 뷰 설정 mainView.setTabView(index: index, contentViews: contentViews) currentTabIndex = index } - + func bindBookmarkButton( buttonTap: ControlEvent, currentItem: Observable, @@ -316,7 +316,7 @@ private extension DictionaryDetailBaseViewController { self?.navigationController?.popViewController(animated: true) } .disposed(by: disposeBag) - + mainView.dictButton.rx.tap .bind { [weak self] in self?.navigationController?.popToRootViewController(animated: true) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Item/ItemDictionaryDetailReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Item/ItemDictionaryDetailReactor.swift index 44166a48..83e8b92e 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Item/ItemDictionaryDetailReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Item/ItemDictionaryDetailReactor.swift @@ -28,7 +28,7 @@ public final class ItemDictionaryDetailReactor: Reactor { case setLastDeletedBookmark(DictionaryDetailItemResponse?) case setLoginState(Bool) } - + // MARK: State public struct State { @Pulse var route: Route = .none diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Item/ItemDictionaryDetailViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Item/ItemDictionaryDetailViewController.swift index 8c158c61..49ed775a 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Item/ItemDictionaryDetailViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Item/ItemDictionaryDetailViewController.swift @@ -165,7 +165,7 @@ extension ItemDictionaryDetailViewController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + monsterCardView.filterButton.rx.tap .map { Reactor.Action.filterButtonTapped } .bind(to: reactor.action) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Map/MapDictionaryDetailReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Map/MapDictionaryDetailReactor.swift index eb8388c2..0ed69b92 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Map/MapDictionaryDetailReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Map/MapDictionaryDetailReactor.swift @@ -31,7 +31,6 @@ public final class MapDictionaryDetailReactor: Reactor { private let checkLoginUseCase: CheckLoginUseCase private let setBookmarkUseCase: SetBookmarkUseCase - public struct State { @Pulse var route: Route = .none var mapDetailInfo: DictionaryDetailMapResponse diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Monster/MonsterDictionaryDetailViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Monster/MonsterDictionaryDetailViewController.swift index a530f77d..4c655911 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Monster/MonsterDictionaryDetailViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Monster/MonsterDictionaryDetailViewController.swift @@ -115,7 +115,7 @@ extension MonsterDictionaryDetailViewController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + dropItemView.filterButton.rx.tap .map { Reactor.Action.filterButtonTapped(.item) } .bind(to: reactor.action) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/NPC/NpcDictionaryDetailViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/NPC/NpcDictionaryDetailViewController.swift index 16723738..2b3a8619 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/NPC/NpcDictionaryDetailViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/NPC/NpcDictionaryDetailViewController.swift @@ -147,7 +147,7 @@ extension NpcDictionaryDetailViewController { owner.setUpQuestView() }) .disposed(by: disposeBag) - + bindBookmarkButton( buttonTap: mainView.bookmarkButton.rx.tap, currentItem: reactor.state.map { $0.npcDetailInfo }, diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Quest/QuestDictionaryDetailViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Quest/QuestDictionaryDetailViewController.swift index 48ce4f04..195c9b83 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Quest/QuestDictionaryDetailViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/Quest/QuestDictionaryDetailViewController.swift @@ -149,7 +149,7 @@ extension QuestDictionaryDetailViewController { owner.setUpQuestView() }) .disposed(by: disposeBag) - + bindBookmarkButton( buttonTap: mainView.bookmarkButton.rx.tap, currentItem: reactor.state.map { $0.detailInfo }, diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift index de85896a..04d7666c 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift @@ -49,7 +49,7 @@ public final class DictionaryListViewController: BaseViewController, View { addViews() setupConstraints() configureUI() - + } } @@ -103,13 +103,13 @@ extension DictionaryListViewController { } func bindViewState(reactor: Reactor) { - + reactor.state .map { $0.totalCounts } .distinctUntilChanged() .bind(to: itemCountRelay) .disposed(by: disposeBag) - + reactor.state.map(\.listItems) .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -144,8 +144,8 @@ extension DictionaryListViewController { case .filter(let type): switch type { case .item: - let viewController = owner.itemFilterFactory.make() { result in - + let viewController = owner.itemFilterFactory.make { _ in + } owner.present(viewController, animated: true) case .monster: @@ -274,10 +274,10 @@ extension DictionaryListViewController: UICollectionViewDelegate, UICollectionVi public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let reactor = reactor else { return } let item: DictionaryMainItemResponse - + item = reactor.currentState.listItems[indexPath.item] let viewController: UIViewController - + switch reactor.currentState.type { case .total: // 전체 타입일 때는 item.type에 따라 분기 @@ -299,10 +299,10 @@ extension DictionaryListViewController: UICollectionViewDelegate, UICollectionVi // 단일 타입일 경우 리액터 타입에 따라 처리 viewController = detailFactory.make(type: reactor.currentState.type, id: item.id) } - + navigationController?.pushViewController(viewController, animated: true) } - + public func scrollViewDidScroll(_ scrollView: UIScrollView) { let offsetY = scrollView.contentOffset.y let contentHeight = scrollView.contentSize.height diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift index d72f8ea0..dd969c9c 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryMain/DictionaryMainViewController.swift @@ -14,7 +14,7 @@ public final class DictionaryMainViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let initialIndex: Int private lazy var currentPageIndex = BehaviorRelay(value: initialIndex) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationReactor.swift index 1333cbeb..36a40a99 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationReactor.swift @@ -1,5 +1,5 @@ -import ReactorKit import DomainInterface +import ReactorKit import RxSwift public final class DictionaryNotificationReactor: Reactor { @@ -29,7 +29,7 @@ public final class DictionaryNotificationReactor: Reactor { public struct State { @Pulse var route: Route = .none var notifications: [AllAlarmResponse] = [] - var profile: MyPageResponse? = nil + var profile: MyPageResponse? var hasMore: Bool = false var isLoading: Bool = false } @@ -62,7 +62,7 @@ public final class DictionaryNotificationReactor: Reactor { case .loadMore: guard currentState.hasMore, !currentState.isLoading else { return .empty() } let cursor = currentState.notifications.last?.date - + return .concat([ .just(.setLoading(true)), fetchAllAlarmUseCase.execute(cursor: cursor, pageSize: 20) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationViewController.swift index c249de58..da8b6589 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryNotification/DictionaryNotificationViewController.swift @@ -85,7 +85,7 @@ public extension DictionaryNotificationViewController { .map { _ in Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.header.leftButton.rx.tap .map { Reactor.Action.backButtonTapped } .bind(to: reactor.action) @@ -116,7 +116,7 @@ public extension DictionaryNotificationViewController { } } .disposed(by: disposeBag) - + reactor.state.map { $0.notifications } .distinctUntilChanged() .withUnretained(self) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchFactoryImpl.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchFactoryImpl.swift index 285e7bde..10857c85 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchFactoryImpl.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchFactoryImpl.swift @@ -7,7 +7,7 @@ public final class DictionarySearchFactoryImpl: DictionarySearchFactory { private let recentSearchAddUseCase: RecentSearchAddUseCase private let recentSearchFetchUseCase: RecentSearchFetchUseCase private let searchResultFactory: DictionarySearchResultFactory - + public init(recentSearchRemoveUseCase: RecentSearchRemoveUseCase, recentSearchAddUseCase: RecentSearchAddUseCase, searchResultFactory: DictionarySearchResultFactory, recentSearchFetchUseCase: RecentSearchFetchUseCase) { self.recentSearchRemoveUseCase = recentSearchRemoveUseCase self.recentSearchAddUseCase = recentSearchAddUseCase diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchReactor.swift index 2b5c5911..5f7cb884 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchReactor.swift @@ -1,5 +1,5 @@ -import Foundation import DomainInterface +import Foundation import ReactorKit struct PopularItem { @@ -39,7 +39,7 @@ public final class DictionarySearchReactor: Reactor { let popularResult: [PopularItem] } - + public let recentSearchAddUseCase: RecentSearchAddUseCase public let recentSearchRemoveUseCase: RecentSearchRemoveUseCase public let recentSearchFetchUseCase: RecentSearchFetchUseCase @@ -47,8 +47,6 @@ public final class DictionarySearchReactor: Reactor { // MARK: - properties public var initialState: State var disposeBag = DisposeBag() - - // MARK: - init public init(recentSearchAddUseCase: RecentSearchAddUseCase, recentSearchRemoveUseCase: RecentSearchRemoveUseCase, recentSearchFetchUseCase: RecentSearchFetchUseCase) { @@ -76,11 +74,11 @@ public final class DictionarySearchReactor: Reactor { } let newItems = grid.flatMap { $0.compactMap { $0 } } - + self.recentSearchAddUseCase = recentSearchAddUseCase self.recentSearchRemoveUseCase = recentSearchRemoveUseCase self.recentSearchFetchUseCase = recentSearchFetchUseCase - + let savedRecentResult: [String] = [] self.initialState = State( @@ -89,7 +87,7 @@ public final class DictionarySearchReactor: Reactor { popularResult: newItems ) } - + // MARK: - Reactor Methods public func mutate(action: Action) -> Observable { switch action { diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift index dbb9ab41..80139835 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift @@ -14,7 +14,7 @@ public final class DictionarySearchViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private var searchResultFactory: DictionarySearchResultFactory private let chipTapRelay = PublishRelay() @@ -177,13 +177,13 @@ extension DictionarySearchViewController { } } .disposed(by: disposeBag) - + rx.viewWillAppear .take(1) .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + } } @@ -285,7 +285,7 @@ extension DictionarySearchViewController: UICollectionViewDelegate, UICollection for: indexPath ) as! PopularSearchHeaderView // TODO: 인기검색어 추후에 - //view.inject(mainText: "인기 검색어", subText: "업데이트 일자", hasRecent: reactor.currentState.hasRecent) + // view.inject(mainText: "인기 검색어", subText: "업데이트 일자", hasRecent: reactor.currentState.hasRecent) return view default: diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultReactor.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultReactor.swift index 72b88e05..57614151 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultReactor.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultReactor.swift @@ -30,14 +30,14 @@ public final class DictionarySearchResultReactor: Reactor { } var keyword: String? - + var counts: [Int] = [0, 0, 0, 0, 0, 0] } // MARK: - properties public var initialState: State var disposeBag = DisposeBag() - + // MARK: - UseCases private let dictionarySearchUseCase: FetchDictionarySearchListUseCase private let dictionarySearchCountUseCase: FetchDictionaryListCountUseCase @@ -67,14 +67,14 @@ public final class DictionarySearchResultReactor: Reactor { // 검색 결과 화면에서 재검색 시 case .searchButtonTapped(let keyword): let keyword = keyword ?? "" - + return Observable.just(.setKeyword(keyword)) } } - + public func reduce(state: State, mutation: Mutation) -> State { var newState = state - + switch mutation { case .navigateTo(let route): newState.route = route @@ -83,10 +83,10 @@ public final class DictionarySearchResultReactor: Reactor { case .setCounts(let counts): newState.counts = counts } - + return newState } - + public func transform(mutation: Observable) -> Observable { let keywordChanges = mutation .compactMap { mutation -> String? in diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultViewController.swift index 11732a41..fa38a881 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearchResult/DictionarySearchResultViewController.swift @@ -14,7 +14,7 @@ public final class DictionarySearchResultViewController: BaseViewController, Vie // MARK: - Properties public var disposeBag = DisposeBag() - + private let initialIndex: Int private lazy var currentPageIndex = BehaviorRelay(value: initialIndex) @@ -23,7 +23,7 @@ public final class DictionarySearchResultViewController: BaseViewController, Vie private var mainView: DictionaryMainView private let underLineController = TabBarUnderlineController() private let dictionaryListFactory: DictionaryMainListFactory - + private var didSetInitialIndex = false public init( @@ -44,12 +44,9 @@ public final class DictionarySearchResultViewController: BaseViewController, Vie @MainActor required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - } - // MARK: - Life Cycle public extension DictionarySearchResultViewController { override func viewDidLoad() { @@ -64,8 +61,7 @@ public extension DictionarySearchResultViewController { didSetInitialIndex = true setInitialIndex() } - - + private func updateViewControllers(keyword: String) { guard let reactor = reactor else { return } let type = reactor.currentState.type @@ -104,7 +100,6 @@ public extension DictionarySearchResultViewController { } } - } // MARK: - SetUp @@ -155,11 +150,11 @@ private extension DictionarySearchResultViewController { ) mainView.tabCollectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredHorizontally) - + // 전체 레이아웃 강제 갱신 mainView.tabCollectionView.collectionViewLayout.invalidateLayout() mainView.tabCollectionView.layoutIfNeeded() - + DispatchQueue.main.async { [weak self] in self?.underLineController.setInitialIndicator() } @@ -178,7 +173,7 @@ public extension DictionarySearchResultViewController { .map { Reactor.Action.backbuttonTapped } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.searchBar.searchButton.rx.tap .withLatestFrom(mainView.searchBar.textField.rx.text.orEmpty) .map { text in Reactor.Action.searchButtonTapped(text) } @@ -210,12 +205,12 @@ public extension DictionarySearchResultViewController { owner.updateViewControllers(keyword: newKeyword) } .disposed(by: disposeBag) - + rx.viewWillAppear .map {_ in Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + reactor.state .map(\.counts) .distinctUntilChanged() @@ -289,4 +284,3 @@ extension DictionarySearchResultViewController: UICollectionViewDataSource, UICo underLineController.animateIndicatorToSelectedItem() } } - diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/SortedBottomSheet/SortedBottomSheetViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/SortedBottomSheet/SortedBottomSheetViewController.swift index 5b6fcd92..a5e5ae06 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/SortedBottomSheet/SortedBottomSheetViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/SortedBottomSheet/SortedBottomSheetViewController.swift @@ -16,7 +16,7 @@ public final class SortedBottomSheetViewController: BaseViewController, ModalPre // MARK: - Properties public var disposeBag = DisposeBag() - + public var onSelectedIndex: ((Int) -> Void)? // MARK: - Components diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift index b74269b4..de2ed119 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/AppDelegate.swift @@ -73,7 +73,7 @@ private extension AppDelegate { DIContainer.register(type: BookmarkRepository.self) { BookmarkRepositoryImpl(provider: DIContainer.resolve(type: NetworkProvider.self), interceptor: TokenInterceptor(fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self))) } - + DIContainer.register(type: UserDefaultsRepository.self) { UserDefaultsRepositoryImpl() } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/ViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/ViewController.swift index 2628f0a4..28d2d119 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/ViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeatureDemo/ViewController.swift @@ -19,7 +19,7 @@ class ViewController: UIViewController { }() lazy var views: [[UIViewController]] = { - let itemFilterBottomSheetVC = DIContainer.resolve(type: ItemFilterBottomSheetFactory.self).make() { _ in } + let itemFilterBottomSheetVC = DIContainer.resolve(type: ItemFilterBottomSheetFactory.self).make { _ in } itemFilterBottomSheetVC.title = "아이템 필터 바텀시트" let monsterBottomSheetVC = DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self).make(startLevel: 0, endLevel: 200) { _, _ in } diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Announcement/AnnouncementViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Announcement/AnnouncementViewController.swift index 66b658cc..a759a41c 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Announcement/AnnouncementViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Announcement/AnnouncementViewController.swift @@ -6,7 +6,7 @@ import ReactorKit final class AnnouncementViewController: CustomerSupportBaseViewController, View { typealias Reactor = AnnouncementReactor - + // MARK: - Init override init(type: CustomerSupportType) { super.init(type: type) diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift index 945f64de..8c89045d 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/CustomerSupportBaseViewController.swift @@ -13,12 +13,12 @@ import RxSwift class CustomerSupportBaseViewController: BaseViewController { // MARK: - Properties public var disposeBag = DisposeBag() - + /// 현재 보여지고 있는 뷰의 인덱스 public var currentTabIndex: Int? public var urlStrings: [String] = [] var onItemTapped: ((Int) -> Void)? - + // MARK: - Components public var mainView = CustomerSupportBaseView() public var type: CustomerSupportType diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Event/EventViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Event/EventViewController.swift index bf6c5bbd..1a842c98 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Event/EventViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/CustomerSupport/Event/EventViewController.swift @@ -5,7 +5,6 @@ import UIKit final class EventViewController: CustomerSupportBaseViewController, View { typealias Reactor = EventReactor - // MARK: - Init override init(type: CustomerSupportType) { super.init(type: type) diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift index bb071102..b8a09526 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/Main/MyPageMainViewController.swift @@ -18,7 +18,7 @@ public final class MyPageMainViewController: BaseViewController, View { // MARK: - Properties public var disposeBag = DisposeBag() - + private let setProfileFactory: SetProfileFactory private let customerSupportFactory: CustomerSupportFactory private let notificationSettingFactory: NotificationSettingFactory diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingReactor.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingReactor.swift index b1e68dcb..6596009d 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingReactor.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/NotificationSetting/NotificationSettingReactor.swift @@ -45,7 +45,7 @@ public final class NotificationSettingReactor: Reactor { isAgreeEventNotification: Bool, isAgreeNoticeNotification: Bool, isAgreePatchNoteNotification: Bool - + ) { self.initialState = .init(isAgreeEventNotification: isAgreeEventNotification, isAgreeNoticeNotification: isAgreeNoticeNotification, isAgreePatchNoteNotification: isAgreePatchNoteNotification) self.checkNotificationPermissionUseCase = checkNotificationPermissionUseCase diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift index e0d3a63f..23938438 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SelectImage/SelectImageViewContoller.swift @@ -15,7 +15,7 @@ public final class SelectImageViewContoller: BaseViewController, ModalPresentabl public var modalHeight: CGFloat? = 16 + 32 + UIScreen.main.bounds.size.width + 4 + 24 + 54 + 4 public typealias Reactor = SelectImageReactor - + public var disposeBag = DisposeBag() // MARK: - Components @@ -112,7 +112,7 @@ extension SelectImageViewContoller: UICollectionViewDelegate, UICollectionViewDa cell.inject(input: SelectImageCell.Input(type: reactor.currentState.images[indexPath.row])) return cell } - + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let reactor = reactor else { return } reactor.action.onNext(.imageTapped(indexPath.row)) diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SetCharacter/SetCharacterViewController.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SetCharacter/SetCharacterViewController.swift index a50490ea..f9f22874 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SetCharacter/SetCharacterViewController.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SetCharacter/SetCharacterViewController.swift @@ -12,7 +12,7 @@ import SnapKit public class SetCharacterViewController: BaseViewController, View { // MARK: - Properties public typealias Reactor = SetCharacterReactor - + public var disposeBag = DisposeBag() // MARK: - Components diff --git a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileReactor.swift b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileReactor.swift index 1c4ba994..0f91357b 100644 --- a/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileReactor.swift +++ b/MLS/Presentation/MyPageFeature/MyPageFeature/SetProfile/SetProfileReactor.swift @@ -45,7 +45,7 @@ public final class SetProfileReactor: Reactor { var setProfileState: SetProfileView.SetProfileState var isShowError = false var isEditingNickName = false - var profile: MyPageResponse? = nil + var profile: MyPageResponse? } // MARK: - Properties From 07c091a18053f1f7714bbc357c251c4bfb3fab79 Mon Sep 17 00:00:00 2001 From: p2glet Date: Wed, 12 Nov 2025 18:11:06 +0900 Subject: [PATCH 10/14] =?UTF-8?q?fix/#264:=20=EB=B6=81=EB=A7=88=ED=81=AC?= =?UTF-8?q?=20GuideAlert=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MLS/MLS/Application/AppDelegate.swift | 26 +-- .../DictionaryDetailBaseViewController.swift | 14 +- .../DictionaryDetailFactoryImpl.swift | 14 +- .../DictionaryListFactoryImpl.swift | 9 +- .../DictionaryListViewController.swift | 156 ++++++++++++------ 5 files changed, 139 insertions(+), 80 deletions(-) diff --git a/MLS/MLS/Application/AppDelegate.swift b/MLS/MLS/Application/AppDelegate.swift index 0afc6101..e813ac44 100644 --- a/MLS/MLS/Application/AppDelegate.swift +++ b/MLS/MLS/Application/AppDelegate.swift @@ -355,8 +355,19 @@ private extension AppDelegate { DIContainer.register(type: BookmarkModalFactory.self) { BookmarkModalFactoryImpl(addCollectionFactory: DIContainer.resolve(type: AddCollectionFactory.self)) } + DIContainer.register(type: LoginFactory.self) { + LoginFactoryImpl( + termsAgreementsFactory: DIContainer.resolve(type: TermsAgreementFactory.self), + appleLoginUseCase: DIContainer.resolve(type: FetchSocialCredentialUseCase.self, name: "apple"), + kakaoLoginUseCase: DIContainer.resolve(type: FetchSocialCredentialUseCase.self, name: "kakao"), + loginWithAppleUseCase: DIContainer.resolve(type: LoginWithAppleUseCase.self), + loginWithKakaoUseCase: DIContainer.resolve(type: LoginWithKakaoUseCase.self), + fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self), + putFCMTokenUseCase: DIContainer.resolve(type: PutFCMTokenUseCase.self), fetchPlatformUseCase: DIContainer.resolve(type: FetchPlatformUseCase.self) + ) + } DIContainer.register(type: DictionaryDetailFactory.self) { - DictionaryDetailFactoryImpl(bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), dictionaryDetailMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapUseCase.self), dictionaryDetailMapSpawnMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapSpawnMonsterUseCase.self), dictionaryDetailMapNpcUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapNpcUseCase.self), dictionaryDetailQuestLinkedQuestsUseCase: DIContainer.resolve(type: FetchDictionaryDetailQuestLinkedQuestsUseCase.self), dictionaryDetailQuestUseCase: DIContainer.resolve(type: FetchDictionaryDetailQuestUseCase.self), dictionaryDetailItemDropMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailItemDropMonsterUseCase.self), dictionaryDetailItemUseCase: DIContainer.resolve(type: FetchDictionaryDetailItemUseCase.self), dictionaryDetailNpcUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcUseCase.self), dictionaryDetailNpcQuestUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcQuestUseCase.self), dictionaryDetailNpcMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcMapUseCase.self), dictionaryDetailMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterUseCase.self), dictionaryDetailMonsterDropItemUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterItemsUseCase.self), dictionaryDetailMonsterMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterMapUseCase.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self)) + DictionaryDetailFactoryImpl(loginFactory: { DIContainer.resolve(type: LoginFactory.self) }, bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), dictionaryDetailMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapUseCase.self), dictionaryDetailMapSpawnMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapSpawnMonsterUseCase.self), dictionaryDetailMapNpcUseCase: DIContainer.resolve(type: FetchDictionaryDetailMapNpcUseCase.self), dictionaryDetailQuestLinkedQuestsUseCase: DIContainer.resolve(type: FetchDictionaryDetailQuestLinkedQuestsUseCase.self), dictionaryDetailQuestUseCase: DIContainer.resolve(type: FetchDictionaryDetailQuestUseCase.self), dictionaryDetailItemDropMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailItemDropMonsterUseCase.self), dictionaryDetailItemUseCase: DIContainer.resolve(type: FetchDictionaryDetailItemUseCase.self), dictionaryDetailNpcUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcUseCase.self), dictionaryDetailNpcQuestUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcQuestUseCase.self), dictionaryDetailNpcMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailNpcMapUseCase.self), dictionaryDetailMonsterUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterUseCase.self), dictionaryDetailMonsterDropItemUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterItemsUseCase.self), dictionaryDetailMonsterMapUseCase: DIContainer.resolve(type: FetchDictionaryDetailMonsterMapUseCase.self), checkLoginUseCase: DIContainer.resolve(type: CheckLoginUseCase.self), setBookmarkUseCase: DIContainer.resolve(type: SetBookmarkUseCase.self)) } DIContainer.register(type: DictionaryMainListFactory.self) { DictionaryListFactoryImpl( @@ -372,7 +383,7 @@ private extension AppDelegate { monsterFilterFactory: DIContainer.resolve(type: MonsterFilterBottomSheetFactory.self), sortedFactory: DIContainer.resolve(type: SortedBottomSheetFactory.self), bookmarkModalFactory: DIContainer.resolve(type: BookmarkModalFactory.self), - detailFactory: DIContainer.resolve(type: DictionaryDetailFactory.self) + detailFactory: DIContainer.resolve(type: DictionaryDetailFactory.self), loginFactory: { DIContainer.resolve(type: LoginFactory.self) } ) } DIContainer.register(type: DictionarySearchResultFactory.self) { @@ -436,17 +447,6 @@ private extension AppDelegate { fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self), updateMarketingAgreementUseCase: DIContainer.resolve(type: UpdateMarketingAgreementUseCase.self) ) } - DIContainer.register(type: LoginFactory.self) { - LoginFactoryImpl( - termsAgreementsFactory: DIContainer.resolve(type: TermsAgreementFactory.self), - appleLoginUseCase: DIContainer.resolve(type: FetchSocialCredentialUseCase.self, name: "apple"), - kakaoLoginUseCase: DIContainer.resolve(type: FetchSocialCredentialUseCase.self, name: "kakao"), - loginWithAppleUseCase: DIContainer.resolve(type: LoginWithAppleUseCase.self), - loginWithKakaoUseCase: DIContainer.resolve(type: LoginWithKakaoUseCase.self), - fetchTokenUseCase: DIContainer.resolve(type: FetchTokenFromLocalUseCase.self), - putFCMTokenUseCase: DIContainer.resolve(type: PutFCMTokenUseCase.self), fetchPlatformUseCase: DIContainer.resolve(type: FetchPlatformUseCase.self) - ) - } DIContainer.register(type: OnBoardingNotificationFactory.self) { OnBoardingNotificationFactoryImpl(onBoardingNotificationSheetFactory: DIContainer.resolve(type: OnBoardingNotificationSheetFactory.self)) } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift index 4cd7a15a..01ec62de 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift @@ -1,5 +1,6 @@ import UIKit +import AuthFeatureInterface import BaseFeature import BookmarkFeatureInterface import DesignSystem @@ -27,7 +28,8 @@ class DictionaryDetailBaseViewController: BaseViewController { /// 현재 보여지고 있는 뷰의 인덱스 private var currentTabIndex: Int? - public let bookmarkModalFactory: BookmarkModalFactory + private let bookmarkModalFactory: BookmarkModalFactory + private let loginFactory: LoginFactory // MARK: - Components public var mainView = DictionaryDetailBaseView() @@ -35,9 +37,10 @@ class DictionaryDetailBaseViewController: BaseViewController { // 타입설정 public var type: DictionaryItemType - public init(type: DictionaryItemType, bookmarkModalFactory: BookmarkModalFactory) { + public init(type: DictionaryItemType, bookmarkModalFactory: BookmarkModalFactory, loginFactory: LoginFactory) { self.type = type self.bookmarkModalFactory = bookmarkModalFactory + self.loginFactory = loginFactory mainView.titleLabel.attributedText = .makeStyledString(font: .sub_m_b, text: type.detailTitle) super.init() isBottomTabbarHidden = true @@ -259,8 +262,11 @@ extension DictionaryDetailBaseViewController { mainText: "북마크를 하려면 로그인이 필요해요.", ctaText: "로그인 하기", cancelText: "취소", - ctaAction: { print("로그인 화면으로 이동") }, - cancelAction: { print("취소됨") } + ctaAction: { + let viewController = self.loginFactory.make(exitRoute: .pop) + self.navigationController?.pushViewController(viewController, animated: true) + }, + cancelAction: nil ) return } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailFactoryImpl.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailFactoryImpl.swift index ffc05c73..2b672093 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailFactoryImpl.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailFactoryImpl.swift @@ -1,9 +1,11 @@ +import AuthFeatureInterface import BaseFeature import BookmarkFeatureInterface import DictionaryFeatureInterface import DomainInterface public final class DictionaryDetailFactoryImpl: DictionaryDetailFactory { + private let loginFactory: () -> LoginFactory private let bookmarkModalFactory: BookmarkModalFactory private let dictionaryDetailMapUseCase: FetchDictionaryDetailMapUseCase private let dictionaryDetailMapSpawnMonsterUseCase: FetchDictionaryDetailMapSpawnMonsterUseCase @@ -23,6 +25,7 @@ public final class DictionaryDetailFactoryImpl: DictionaryDetailFactory { private let setBookmarkUseCase: SetBookmarkUseCase public init( + loginFactory: @escaping () -> LoginFactory, bookmarkModalFactory: BookmarkModalFactory, dictionaryDetailMapUseCase: FetchDictionaryDetailMapUseCase, dictionaryDetailMapSpawnMonsterUseCase: FetchDictionaryDetailMapSpawnMonsterUseCase, @@ -40,6 +43,7 @@ public final class DictionaryDetailFactoryImpl: DictionaryDetailFactory { checkLoginUseCase: CheckLoginUseCase, setBookmarkUseCase: SetBookmarkUseCase ) { + self.loginFactory = loginFactory self.bookmarkModalFactory = bookmarkModalFactory self.dictionaryDetailMapUseCase = dictionaryDetailMapUseCase self.dictionaryDetailMapSpawnMonsterUseCase = dictionaryDetailMapSpawnMonsterUseCase @@ -66,13 +70,13 @@ public final class DictionaryDetailFactoryImpl: DictionaryDetailFactory { case .collection: break case .item: - viewController = ItemDictionaryDetailViewController(type: .item, bookmarkModalFactory: bookmarkModalFactory) + viewController = ItemDictionaryDetailViewController(type: .item, bookmarkModalFactory: bookmarkModalFactory, loginFactory: loginFactory()) let reactor = ItemDictionaryDetailReactor(dictionaryDetailItemUseCase: dictionaryDetailItemUseCase, dictionaryDetailItemDropMonsterUseCase: dictionaryDetailItemDropMonsterUseCase, checkLoginUseCase: checkLoginUseCase, setBookmarkUseCase: setBookmarkUseCase, id: id) if let viewController = viewController as? ItemDictionaryDetailViewController { viewController.reactor = reactor } case .monster: - viewController = MonsterDictionaryDetailViewController(type: .monster, bookmarkModalFactory: bookmarkModalFactory) + viewController = MonsterDictionaryDetailViewController(type: .monster, bookmarkModalFactory: bookmarkModalFactory, loginFactory: loginFactory()) let reactor = MonsterDictionaryDetailReactor( dictionaryDetailMonsterUseCase: dictionaryDetailMonsterUseCase, dictionaryDetailMonsterDropItemUseCase: dictionaryDetailMonsterDropItemUseCase, @@ -93,7 +97,7 @@ public final class DictionaryDetailFactoryImpl: DictionaryDetailFactory { setBookmarkUseCase: setBookmarkUseCase, id: id ) - viewController = MapDictionaryDetailViewController(type: .map, bookmarkModalFactory: bookmarkModalFactory) + viewController = MapDictionaryDetailViewController(type: .map, bookmarkModalFactory: bookmarkModalFactory, loginFactory: loginFactory()) if let viewController = viewController as? MapDictionaryDetailViewController { viewController.reactor = reactor } @@ -106,12 +110,12 @@ public final class DictionaryDetailFactoryImpl: DictionaryDetailFactory { setBookmarkUseCase: setBookmarkUseCase, id: id ) - viewController = NpcDictionaryDetailViewController(type: .npc, bookmarkModalFactory: bookmarkModalFactory) + viewController = NpcDictionaryDetailViewController(type: .npc, bookmarkModalFactory: bookmarkModalFactory, loginFactory: loginFactory()) if let viewController = viewController as? NpcDictionaryDetailViewController { viewController.reactor = reactor } case .quest: - viewController = QuestDictionaryDetailViewController(type: .quest, bookmarkModalFactory: bookmarkModalFactory) + viewController = QuestDictionaryDetailViewController(type: .quest, bookmarkModalFactory: bookmarkModalFactory, loginFactory: loginFactory()) let reactor = QuestDictionaryDetailReactor(dictionaryDetailQuestUseCase: dictionaryDetailQuestUseCase, dictionaryDetailQuestLinkedQuestUseCase: dictionaryDetailQuestLinkedQuestsUseCase, checkLoginUseCase: checkLoginUseCase, setBookmarkUseCase: setBookmarkUseCase, id: id) if let viewController = viewController as? QuestDictionaryDetailViewController { viewController.reactor = reactor diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListFactoryImpl.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListFactoryImpl.swift index cf196293..f8d8eb8d 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListFactoryImpl.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListFactoryImpl.swift @@ -1,3 +1,4 @@ +import AuthFeatureInterface import BaseFeature import BookmarkFeatureInterface import DictionaryFeatureInterface @@ -11,7 +12,6 @@ public final class DictionaryListFactoryImpl: DictionaryMainListFactory { private let dictionaryQuestListItemUseCase: FetchDictionaryQuestListUseCase private let dictionaryNpcListItemUseCase: FetchDictionaryNpcListUseCase private let dictionaryListItemUseCase: FetchDictionaryMonsterListUseCase - private let setBookmarkUseCase: SetBookmarkUseCase private let itemFilterFactory: ItemFilterBottomSheetFactory @@ -19,6 +19,7 @@ public final class DictionaryListFactoryImpl: DictionaryMainListFactory { private let sortedFactory: SortedBottomSheetFactory private let bookmarkModalFactory: BookmarkModalFactory private let detailFactory: DictionaryDetailFactory + private let loginFactory: () -> LoginFactory public init( checkLoginUseCase: CheckLoginUseCase, @@ -33,7 +34,8 @@ public final class DictionaryListFactoryImpl: DictionaryMainListFactory { monsterFilterFactory: MonsterFilterBottomSheetFactory, sortedFactory: SortedBottomSheetFactory, bookmarkModalFactory: BookmarkModalFactory, - detailFactory: DictionaryDetailFactory + detailFactory: DictionaryDetailFactory, + loginFactory: @escaping () -> LoginFactory ) { self.checkLoginUseCase = checkLoginUseCase self.dictionaryAllListItemUseCase = dictionaryAllListItemUseCase @@ -48,6 +50,7 @@ public final class DictionaryListFactoryImpl: DictionaryMainListFactory { self.sortedFactory = sortedFactory self.bookmarkModalFactory = bookmarkModalFactory self.detailFactory = detailFactory + self.loginFactory = loginFactory } public func make(type: DictionaryType, listType: DictionaryMainViewType, keyword: String? = "") -> BaseViewController { @@ -63,7 +66,7 @@ public final class DictionaryListFactoryImpl: DictionaryMainListFactory { dictionaryListUseCase: dictionaryListItemUseCase, setBookmarkUseCase: setBookmarkUseCase ) - let viewController = DictionaryListViewController(reactor: reactor, itemFilterFactory: itemFilterFactory, monsterFilterFactory: monsterFilterFactory, sortedFactory: sortedFactory, bookmarkModalFactory: bookmarkModalFactory, detailFactory: detailFactory) + let viewController = DictionaryListViewController(reactor: reactor, itemFilterFactory: itemFilterFactory, monsterFilterFactory: monsterFilterFactory, sortedFactory: sortedFactory, bookmarkModalFactory: bookmarkModalFactory, detailFactory: detailFactory, loginFactory: loginFactory()) if listType == .search { viewController.isBottomTabbarHidden = true } diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift index de85896a..d9dadd5d 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift @@ -1,13 +1,12 @@ -import UIKit - +import AuthFeatureInterface import BaseFeature import BookmarkFeatureInterface import DictionaryFeatureInterface import DomainInterface - import ReactorKit import RxCocoa import RxSwift +import UIKit public final class DictionaryListViewController: BaseViewController, View { public typealias Reactor = DictionaryListReactor @@ -20,6 +19,7 @@ public final class DictionaryListViewController: BaseViewController, View { private let bookmarkModalFactory: BookmarkModalFactory private let sortedFactory: SortedBottomSheetFactory private let detailFactory: DictionaryDetailFactory + private let loginFactory: LoginFactory private var selectedSortIndex = 0 public let itemCountRelay = PublishRelay() @@ -27,13 +27,22 @@ public final class DictionaryListViewController: BaseViewController, View { // MARK: - Components private var mainView: DictionaryListView - public init(reactor: DictionaryListReactor, itemFilterFactory: ItemFilterBottomSheetFactory, monsterFilterFactory: MonsterFilterBottomSheetFactory, sortedFactory: SortedBottomSheetFactory, bookmarkModalFactory: BookmarkModalFactory, detailFactory: DictionaryDetailFactory) { + public init( + reactor: DictionaryListReactor, + itemFilterFactory: ItemFilterBottomSheetFactory, + monsterFilterFactory: MonsterFilterBottomSheetFactory, + sortedFactory: SortedBottomSheetFactory, + bookmarkModalFactory: BookmarkModalFactory, + detailFactory: DictionaryDetailFactory, loginFactory: LoginFactory + ) { self.itemFilterFactory = itemFilterFactory self.monsterFilterFactory = monsterFilterFactory self.sortedFactory = sortedFactory self.bookmarkModalFactory = bookmarkModalFactory self.detailFactory = detailFactory - self.mainView = DictionaryListView(isFilterHidden: reactor.currentState.type.isSortHidden) + self.loginFactory = loginFactory + self.mainView = DictionaryListView( + isFilterHidden: reactor.currentState.type.isSortHidden) super.init() self.reactor = reactor } @@ -49,36 +58,40 @@ public final class DictionaryListViewController: BaseViewController, View { addViews() setupConstraints() configureUI() - + } } // MARK: - SetUp -private extension DictionaryListViewController { - func addViews() { +extension DictionaryListViewController { + fileprivate func addViews() { view.addSubview(mainView) } - func setupConstraints() { + fileprivate func setupConstraints() { mainView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) make.horizontalEdges.bottom.equalToSuperview() } } - func configureUI() { + fileprivate func configureUI() { mainView.listCollectionView.collectionViewLayout = createListLayout() mainView.listCollectionView.delegate = self mainView.listCollectionView.dataSource = self - mainView.listCollectionView.register(DictionaryListCell.self, forCellWithReuseIdentifier: DictionaryListCell.identifier) + mainView.listCollectionView.register( + DictionaryListCell.self, + forCellWithReuseIdentifier: DictionaryListCell.identifier) } - func createListLayout() -> UICollectionViewLayout { + fileprivate func createListLayout() -> UICollectionViewLayout { let layoutFactory = LayoutFactory() let layout = CompositionalLayoutBuilder() .section { _ in layoutFactory.getDictionaryListLayout() } .build() - layout.register(Neutral300DividerView.self, forDecorationViewOfKind: Neutral300DividerView.identifier) + layout.register( + Neutral300DividerView.self, + forDecorationViewOfKind: Neutral300DividerView.identifier) return layout } } @@ -103,13 +116,13 @@ extension DictionaryListViewController { } func bindViewState(reactor: Reactor) { - + reactor.state .map { $0.totalCounts } .distinctUntilChanged() .bind(to: itemCountRelay) .disposed(by: disposeBag) - + reactor.state.map(\.listItems) .distinctUntilChanged() .observe(on: MainScheduler.instance) @@ -133,25 +146,37 @@ extension DictionaryListViewController { .subscribe { owner, route in switch route { case .sort(let type): - let viewController = owner.sortedFactory.make(sortedOptions: type.bookmarkSortedFilter, selectedIndex: owner.selectedSortIndex) { index in + let viewController = owner.sortedFactory.make( + sortedOptions: type.bookmarkSortedFilter, + selectedIndex: owner.selectedSortIndex + ) { index in owner.selectedSortIndex = index - let selectedFilter = reactor.currentState.type.bookmarkSortedFilter[index] - reactor.action.onNext(.sortOptionSelected(selectedFilter)) - owner.mainView.selectFilter(selectedType: selectedFilter) + let selectedFilter = reactor.currentState.type + .bookmarkSortedFilter[index] + reactor.action.onNext( + .sortOptionSelected(selectedFilter)) + owner.mainView.selectFilter( + selectedType: selectedFilter) reactor.action.onNext(.fetchListFilter) } owner.tabBarController?.presentModal(viewController) case .filter(let type): switch type { case .item: - let viewController = owner.itemFilterFactory.make() { result in - + let viewController = owner.itemFilterFactory.make { + result in + } owner.present(viewController, animated: true) case .monster: - let viewController = owner.monsterFilterFactory.make(startLevel: reactor.currentState.startLevel ?? 1, endLevel: reactor.currentState.endLevel ?? 200) { startLevel, endLevel in - - reactor.action.onNext(.filterOptionSelected(startLevel: startLevel, endLevel: endLevel)) + let viewController = owner.monsterFilterFactory.make( + startLevel: reactor.currentState.startLevel ?? 1, + endLevel: reactor.currentState.endLevel ?? 200 + ) { startLevel, endLevel in + + reactor.action.onNext( + .filterOptionSelected( + startLevel: startLevel, endLevel: endLevel)) } owner.tabBarController?.presentModal(viewController) default: @@ -175,15 +200,23 @@ extension DictionaryListViewController { } // MARK: - Delegate -extension DictionaryListViewController: UICollectionViewDelegate, UICollectionViewDataSource { - public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { +extension DictionaryListViewController: UICollectionViewDelegate, + UICollectionViewDataSource +{ + public func collectionView( + _ collectionView: UICollectionView, numberOfItemsInSection section: Int + ) -> Int { guard let state = reactor?.currentState else { return 0 } return state.listItems.count } - public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let state = reactor?.currentState else { return UICollectionViewCell() } + public func collectionView( + _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath + ) -> UICollectionViewCell { + guard let state = reactor?.currentState else { + return UICollectionViewCell() + } guard let cell = collectionView.dequeueReusableCell( withReuseIdentifier: DictionaryListCell.identifier, @@ -195,7 +228,8 @@ extension DictionaryListViewController: UICollectionViewDelegate, UICollectionVi let item = state.listItems[indexPath.row] var subText: String? { - [.item, .monster, .quest].contains(item.type) ? item.level.map { "Lv. \($0)" } : nil + [.item, .monster, .quest].contains(item.type) + ? item.level.map { "Lv. \($0)" } : nil } cell.inject( type: .bookmark, @@ -215,17 +249,19 @@ extension DictionaryListViewController: UICollectionViewDelegate, UICollectionVi ctaText: "로그인 하기", cancelText: "취소", ctaAction: { - print("로그인 화면으로 이동") + let viewController = self.loginFactory.make( + exitRoute: .pop) + self.navigationController?.pushViewController( + viewController, animated: true) }, - cancelAction: { - print("취소됨") - } + cancelAction: nil ) return } if item.bookmarkId != nil { - self.reactor?.action.onNext(.toggleBookmark(item.id, isSelected)) + self.reactor?.action.onNext( + .toggleBookmark(item.id, isSelected)) SnackBarFactory.createSnackBar( type: .delete, imageUrl: item.imageUrl, @@ -233,11 +269,13 @@ extension DictionaryListViewController: UICollectionViewDelegate, UICollectionVi text: "아이템을 북마크에서 삭제했어요.", buttonText: "되돌리기", buttonAction: { [weak self] in - self?.reactor?.action.onNext(.undoLastDeletedBookmark) + self?.reactor?.action.onNext( + .undoLastDeletedBookmark) } ) } else { - self.reactor?.action.onNext(.toggleBookmark(item.id, isSelected)) + self.reactor?.action.onNext( + .toggleBookmark(item.id, isSelected)) SnackBarFactory.createSnackBar( type: .normal, imageUrl: item.imageUrl, @@ -246,16 +284,21 @@ extension DictionaryListViewController: UICollectionViewDelegate, UICollectionVi buttonText: "컬렉션 추가", buttonAction: { DispatchQueue.main.async { - let viewController = self.bookmarkModalFactory.make( - onDismissWithColletions: { _ in }, - onDismissWithMessage: { _ in - ToastFactory.createToast( - message: "컬렉션에 추가되었어요. 북마크 탭에서 확인 할 수 있어요." - ) - } - ) - viewController.modalPresentationStyle = .pageSheet - if let sheet = viewController.sheetPresentationController { + let viewController = self.bookmarkModalFactory + .make( + onDismissWithColletions: { _ in }, + onDismissWithMessage: { _ in + ToastFactory.createToast( + message: + "컬렉션에 추가되었어요. 북마크 탭에서 확인 할 수 있어요." + ) + } + ) + viewController.modalPresentationStyle = + .pageSheet + if let sheet = viewController + .sheetPresentationController + { sheet.detents = [.medium(), .large()] sheet.prefersGrabberVisible = true sheet.preferredCornerRadius = 16 @@ -271,13 +314,15 @@ extension DictionaryListViewController: UICollectionViewDelegate, UICollectionVi return cell } - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + public func collectionView( + _ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath + ) { guard let reactor = reactor else { return } let item: DictionaryMainItemResponse - + item = reactor.currentState.listItems[indexPath.item] let viewController: UIViewController - + switch reactor.currentState.type { case .total: // 전체 타입일 때는 item.type에 따라 분기 @@ -293,24 +338,25 @@ extension DictionaryListViewController: UICollectionViewDelegate, UICollectionVi case .map: viewController = detailFactory.make(type: .map, id: item.id) default: - return // 알 수 없는 타입이면 무시 + return // 알 수 없는 타입이면 무시 } default: // 단일 타입일 경우 리액터 타입에 따라 처리 - viewController = detailFactory.make(type: reactor.currentState.type, id: item.id) + viewController = detailFactory.make( + type: reactor.currentState.type, id: item.id) } - + navigationController?.pushViewController(viewController, animated: true) } - + public func scrollViewDidScroll(_ scrollView: UIScrollView) { let offsetY = scrollView.contentOffset.y let contentHeight = scrollView.contentSize.height let height = scrollView.frame.size.height if offsetY > contentHeight - height - 100 { - reactor?.action.onNext(.setCurrentPage) // 페이지 올리고 - reactor?.action.onNext(.fetchList) // 해당 페이지로 데이터 불러오기 + reactor?.action.onNext(.setCurrentPage) // 페이지 올리고 + reactor?.action.onNext(.fetchList) // 해당 페이지로 데이터 불러오기 } } } From 60ef5aaacc4d6e60d00cec5af5f49b2b4d141543 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Nov 2025 09:13:45 +0000 Subject: [PATCH 11/14] style/#264: Apply SwiftLint autocorrect --- .../DictionaryDetailBaseViewController.swift | 4 ++-- .../DictionaryList/DictionaryListViewController.swift | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift index 29971be4..ebb6b598 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryDetail/DictionaryDetailBaseViewController.swift @@ -27,7 +27,7 @@ class DictionaryDetailBaseViewController: BaseViewController { /// 현재 보여지고 있는 뷰의 인덱스 private var currentTabIndex: Int? - + private let bookmarkModalFactory: BookmarkModalFactory private let loginFactory: LoginFactory // MARK: - Components @@ -35,7 +35,7 @@ class DictionaryDetailBaseViewController: BaseViewController { // 타입설정 public var type: DictionaryItemType - + public init(type: DictionaryItemType, bookmarkModalFactory: BookmarkModalFactory, loginFactory: LoginFactory) { self.type = type self.bookmarkModalFactory = bookmarkModalFactory diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift index f600cd37..1f69f492 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionaryList/DictionaryListViewController.swift @@ -200,8 +200,7 @@ extension DictionaryListViewController { // MARK: - Delegate extension DictionaryListViewController: UICollectionViewDelegate, - UICollectionViewDataSource -{ + UICollectionViewDataSource { public func collectionView( _ collectionView: UICollectionView, numberOfItemsInSection section: Int ) -> Int { @@ -296,8 +295,7 @@ extension DictionaryListViewController: UICollectionViewDelegate, viewController.modalPresentationStyle = .pageSheet if let sheet = viewController - .sheetPresentationController - { + .sheetPresentationController { sheet.detents = [.medium(), .large()] sheet.prefersGrabberVisible = true sheet.preferredCornerRadius = 16 From cecd35d00fc2eb8440ea7f5cae779a6a3ad3be13 Mon Sep 17 00:00:00 2001 From: p2glet Date: Wed, 12 Nov 2025 21:40:37 +0900 Subject: [PATCH 12/14] =?UTF-8?q?fix/#264:=20CommonButton=20default=20init?= =?UTF-8?q?ializer=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift | 2 +- .../DesignSystem/Components/CommonButton.swift | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift index 3d677c56..2103c5b6 100644 --- a/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift +++ b/MLS/Presentation/BookmarkFeature/BookmarkFeature/BookmarkEmpty/BookmarkEmptyView.swift @@ -18,7 +18,7 @@ final class BookmarkEmptyView: UIView { private let mainLabel = UILabel() private let subLabel = UILabel() - public let button = CommonButton(style: .normal, title: "ㅌ 가기", disabledTitle: nil) + public let button = CommonButton() // MARK: - Init init() { diff --git a/MLS/Presentation/DesignSystem/DesignSystem/Components/CommonButton.swift b/MLS/Presentation/DesignSystem/DesignSystem/Components/CommonButton.swift index 3d264601..bf10a092 100644 --- a/MLS/Presentation/DesignSystem/DesignSystem/Components/CommonButton.swift +++ b/MLS/Presentation/DesignSystem/DesignSystem/Components/CommonButton.swift @@ -83,6 +83,13 @@ public final class CommonButton: UIButton { super.init(frame: .zero) configureUI() } + + public init() { + self.style = .normal + self.title = nil + self.disabledTitle = nil + super.init(frame: .zero) + } @available(*, unavailable) required init?(coder: NSCoder) { From 1a5c94f9a6403a414dd96c18c4d3b1d364dc7b2f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Nov 2025 12:41:27 +0000 Subject: [PATCH 13/14] style/#264: Apply SwiftLint autocorrect --- .../DesignSystem/DesignSystem/Components/CommonButton.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MLS/Presentation/DesignSystem/DesignSystem/Components/CommonButton.swift b/MLS/Presentation/DesignSystem/DesignSystem/Components/CommonButton.swift index bf10a092..c2744498 100644 --- a/MLS/Presentation/DesignSystem/DesignSystem/Components/CommonButton.swift +++ b/MLS/Presentation/DesignSystem/DesignSystem/Components/CommonButton.swift @@ -83,7 +83,7 @@ public final class CommonButton: UIButton { super.init(frame: .zero) configureUI() } - + public init() { self.style = .normal self.title = nil From 1e259448a3a3a1561e852d5ec1d64449403c9df8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 13 Nov 2025 06:58:28 +0000 Subject: [PATCH 14/14] style/#264: Apply SwiftLint autocorrect --- .../DictionarySearch/DictionarySearchViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift index 94e9dceb..9693f932 100644 --- a/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift +++ b/MLS/Presentation/DictionaryFeature/DictionaryFeature/DictionarySearch/DictionarySearchViewController.swift @@ -128,7 +128,7 @@ extension DictionarySearchViewController { .map { Reactor.Action.viewWillAppear } .bind(to: reactor.action) .disposed(by: disposeBag) - + mainView.searchBar.backButton.rx.tap .map { Reactor.Action.backButtonTapped } .bind(to: reactor.action)