From c38b8590d4247f51a6e831d1268a2bdfca638d82 Mon Sep 17 00:00:00 2001 From: p2glet Date: Mon, 15 Jun 2026 23:26:31 +0900 Subject: [PATCH 1/8] =?UTF-8?q?fix/#341:=20Toast=20=EB=86=92=EC=9D=B4?= =?UTF-8?q?=EC=A0=9C=EC=95=BD=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MLSDesignSystem/Components/Toast.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Toast.swift b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Toast.swift index a889b4c6..fdc07250 100644 --- a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Toast.swift +++ b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Toast.swift @@ -4,8 +4,8 @@ import SnapKit public final class Toast: UIView { private enum Constant { - static let verticalEdgesInset: CGFloat = 16 static let horizontalEdges: CGFloat = 16 + static let height: CGFloat = 44 static let cornerRadius: CGFloat = 8 } @@ -37,23 +37,24 @@ public final class Toast: UIView { private extension Toast { func addViews() { addSubview(self.toastContentView) - toastContentView.addSubview(self.label) + self.toastContentView.addSubview(self.label) } func setupConstraints() { - toastContentView.snp.makeConstraints { make in + self.toastContentView.snp.makeConstraints { make in make.edges.equalToSuperview() + make.height.equalTo(Constant.height) } - label.snp.makeConstraints { make in - make.verticalEdges.equalToSuperview().inset(Constant.verticalEdgesInset) + self.label.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(Constant.horizontalEdges) + make.centerY.equalToSuperview() } } func configureUI(message: String?) { layer.cornerRadius = Constant.cornerRadius clipsToBounds = true - label.attributedText = .makeStyledString(font: .b_s_r, text: message, color: .whiteMLS) + self.label.attributedText = .makeStyledString(font: .b_s_r, text: message, color: .whiteMLS) } } From 1412d7ddd26db06182818b188e1bc2539f8be1ca Mon Sep 17 00:00:00 2001 From: p2glet Date: Mon, 15 Jun 2026 23:33:23 +0900 Subject: [PATCH 2/8] =?UTF-8?q?fix/#341:=20=EC=B6=94=EC=B2=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=99=94=EB=A9=B4=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=98=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 --- .../MLSDesignSystem/Layouts/ToLoginView.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Layouts/ToLoginView.swift b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Layouts/ToLoginView.swift index 3cad3a23..f911f0d5 100644 --- a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Layouts/ToLoginView.swift +++ b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Layouts/ToLoginView.swift @@ -36,8 +36,18 @@ public final class ToLoginView: UIView { // MARK: - Components public let imageView = UIImageView() - private let mainLabel = UILabel() - private let subLabel = UILabel() + + private let mainLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + return label + }() + + private let subLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + return label + }() public let button = CommonButton() From f082b9cfda14a08b926bae17e07ad2382f6d9c4b Mon Sep 17 00:00:00 2001 From: p2glet Date: Thu, 2 Jul 2026 23:25:46 +0900 Subject: [PATCH 3/8] =?UTF-8?q?fix/#341:=20=EC=B6=94=EC=B2=9C=20GuideAlert?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Layouts/Factory/GuideAlertFactory.swift | 5 +- .../RecommendationMainReactor.swift | 78 ++++++++++++------- .../RecommendationMainViewController.swift | 12 ++- 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Layouts/Factory/GuideAlertFactory.swift b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Layouts/Factory/GuideAlertFactory.swift index a19c01ca..4df58a32 100644 --- a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Layouts/Factory/GuideAlertFactory.swift +++ b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Layouts/Factory/GuideAlertFactory.swift @@ -16,9 +16,10 @@ public enum GuideAlertFactory { ctaText: String, cancelText: String? = nil, ctaAction: @escaping () -> Void, - cancelAction: (() -> Void)? = nil + cancelAction: (() -> Void)? = nil, + ctaRatio: Double = 0.7 ) { - let alert = GuideAlert(mainText: mainText, ctaText: ctaText, cancelText: cancelText) + let alert = GuideAlert(mainText: mainText, ctaText: ctaText, cancelText: cancelText, ctaRatio: ctaRatio) presentAlert(alert: alert, ctaAction: ctaAction, cancelAction: cancelAction) } diff --git a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainReactor.swift b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainReactor.swift index b89d2847..efa2a80b 100644 --- a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainReactor.swift +++ b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainReactor.swift @@ -7,7 +7,6 @@ import RxCocoa import RxSwift final class RecommendationMainReactor: Reactor { - // MARK: - Reactor enum Action { case viewWillAppear @@ -20,6 +19,7 @@ final class RecommendationMainReactor: Reactor { case none case added(RecommendationMap) case deleted(RecommendationMap) + case showAlert } enum Mutation { @@ -68,32 +68,51 @@ final class RecommendationMainReactor: Reactor { let fetchAll = repository.fetchProfile() .flatMap { [weak self] profile -> Observable in guard let self else { return .empty() } + let setLogin = Observable.just(Mutation.setLogin(true)) + + // 프로필 저장 let setProfile = Observable.just(Mutation.setProfile(profile)) - let setJobName: Observable - if let jobId = profile.jobId { - setJobName = repository.fetchJobName(jobId: jobId) - .map { Mutation.setJobName($0) } - .catch { error in - print("⚠️ [Recommendation] fetchJobName 실패: \(error)") - return .empty() - } - } else { - setJobName = .empty() + + // 프로필 미입력 상태 + guard + let level = profile.level, + let jobId = profile.jobId + else { + return Observable.concat([ + setLogin, + setProfile, + .just(.setUIEvent(.showAlert)) + ]) } - let setRecommendations: Observable - if let level = profile.level, level >= 1, let jobId = profile.jobId { - setRecommendations = repository.fetchRecommendations(level: level, jobId: jobId, limit: 5) - .map { Mutation.setRecommendations($0) } - .catch { error in - print("⚠️ [Recommendation] fetchRecommendations 실패: \(error)") - return .empty() - } - } else { - setRecommendations = .empty() + + // 프로필 입력 완료 상태 + let setJobName = repository.fetchJobName(jobId: jobId) + .map(Mutation.setJobName) + .catch { error in + print("⚠️ [Recommendation] fetchJobName 실패: \(error)") + return .empty() + } + + let setRecommendations = repository.fetchRecommendations( + level: level, + jobId: jobId, + limit: 5 + ) + .map(Mutation.setRecommendations) + .catch { error in + print("⚠️ [Recommendation] fetchRecommendations 실패: \(error)") + return .empty() } - let parallelRequests = Observable.merge([setJobName, setRecommendations]) - return Observable.concat([setLogin, setProfile, parallelRequests]) + + return Observable.concat([ + setLogin, + setProfile, + Observable.merge([ + setJobName, + setRecommendations + ]) + ]) } .catch { error in print("⚠️ [Recommendation] fetchProfile 실패: \(error)") @@ -121,17 +140,17 @@ final class RecommendationMainReactor: Reactor { var newState = state switch mutation { - case .setProfile(let profile): + case let .setProfile(profile): newState.profile = profile - case .setJobName(let jobName): + case let .setJobName(jobName): newState.jobName = jobName - case .setRecommendations(let maps): + case let .setRecommendations(maps): newState.recommendations = maps - case .setLoading(let isLoading): + case let .setLoading(isLoading): newState.isLoading = isLoading case .informationButtonToggle: newState.informationButtonIsOn.toggle() - case .setLogin(let isLogin): + case let .setLogin(isLogin): newState.isLogin = isLogin case let .updateBookmarkId(mapId, bookmarkId): newState.togglingBookmarkIds.remove(mapId) @@ -160,7 +179,8 @@ final class RecommendationMainReactor: Reactor { private extension RecommendationMainReactor { func handleToggleBookmark(mapId: Int) -> Observable { guard !currentState.togglingBookmarkIds.contains(mapId), - let map = currentState.recommendations.first(where: { $0.mapId == mapId }) else { + let map = currentState.recommendations.first(where: { $0.mapId == mapId }) + else { return .empty() } diff --git a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainViewController.swift b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainViewController.swift index f66b5773..1b18a431 100644 --- a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainViewController.swift +++ b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainViewController.swift @@ -10,7 +10,6 @@ import RxSwift import SnapKit final class RecommendationMainViewController: BaseViewController, View { - typealias Reactor = RecommendationMainReactor // MARK: - Properties @@ -144,6 +143,15 @@ extension RecommendationMainViewController { owner.presentDeleteSnackBar(map: map) case .none: break + case .showAlert: + GuideAlertFactory.show(mainText: "레벨과 직업을 모두 입력하면\n맞춤 사냥터를 추천받을 수 있어요", ctaText: "입력하기", cancelText: "도감보러 가기", ctaAction: { + guard let vc = owner.onEditTapped?() else { return } + owner.navigationController?.pushViewController(vc, animated: true) + }, cancelAction: { + if let tabBarController = owner.tabBarController as? BottomTabBarController { + tabBarController.selectTab(index: 1) + } + }, ctaRatio: 0.5) } }) .disposed(by: disposeBag) @@ -206,7 +214,7 @@ private extension RecommendationMainViewController { buttonAction: { [weak self] in guard let self, let bookmarkId = self.reactor?.currentState.recommendations - .first(where: { $0.mapId == map.mapId })?.bookmarkId else { return } + .first(where: { $0.mapId == map.mapId })?.bookmarkId else { return } let vc = self.onBookmarkModalTapped?([bookmarkId]) { isAdd in if isAdd { ToastFactory.createToast(message: "컬렉션에 추가되었어요. 북마크 탭에서 확인 할 수 있어요.") From 7471809400a7182401ebfbc35d4c152e0269ba3b Mon Sep 17 00:00:00 2001 From: p2glet Date: Thu, 2 Jul 2026 23:33:48 +0900 Subject: [PATCH 4/8] =?UTF-8?q?fix/#341:=20=ED=83=AD=EB=B0=94=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EA=B2=BD=20=EC=B6=94=EC=B2=9C-=EB=8F=84?= =?UTF-8?q?=EA=B0=90-=EB=B6=81=EB=A7=88=ED=81=AC-=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20->=20=EB=8F=84=EA=B0=90-=EC=B6=94?= =?UTF-8?q?=EC=B2=9C-=EB=B6=81=EB=A7=88=ED=81=AC-=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/MLSAppFeature/Coordinator/AppCoordinator.swift | 4 ++-- .../Sources/MLSAppFeature/Launcher/AppLauncher.swift | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/MLS/MLSAppFeature/Sources/MLSAppFeature/Coordinator/AppCoordinator.swift b/MLS/MLSAppFeature/Sources/MLSAppFeature/Coordinator/AppCoordinator.swift index 7c9b0173..edcf8585 100644 --- a/MLS/MLSAppFeature/Sources/MLSAppFeature/Coordinator/AppCoordinator.swift +++ b/MLS/MLSAppFeature/Sources/MLSAppFeature/Coordinator/AppCoordinator.swift @@ -44,16 +44,16 @@ public final class AppCoordinator: AppCoordinatorProtocol { // MARK: - Public Methods public func showMainTab(selectedIndex: Int = 0) { let tabItems: [TabItem] = [ + TabItem(title: "도감", icon: DesignSystemAsset.image(named: "dictionary")), TabItem(title: "추천", icon: DesignSystemAsset.image(named: "favorite") ), - TabItem(title: "도감", icon: DesignSystemAsset.image(named: "dictionary")), TabItem(title: "북마크", icon: DesignSystemAsset.image(named: "bookmarkList")), TabItem(title: "MY", icon: DesignSystemAsset.image(named: "mypage")) ] let tabBar = BottomTabBarController( viewControllers: [ - recommendationMainFactory.make(), dictionaryMainViewFactory.make(), + recommendationMainFactory.make(), bookmarkMainFactory.make(bottomInset: 64), myPageMainFactory.make() ], diff --git a/MLS/MLSAppFeature/Sources/MLSAppFeature/Launcher/AppLauncher.swift b/MLS/MLSAppFeature/Sources/MLSAppFeature/Launcher/AppLauncher.swift index 61f46513..10e6ad59 100644 --- a/MLS/MLSAppFeature/Sources/MLSAppFeature/Launcher/AppLauncher.swift +++ b/MLS/MLSAppFeature/Sources/MLSAppFeature/Launcher/AppLauncher.swift @@ -80,7 +80,9 @@ private extension AppLauncher { object: nil, queue: .main ) { [weak self] _ in - self?.showForceUpdateAlert() + Task { @MainActor in + self?.showForceUpdateAlert() + } } case .optional(let latestVersion): GuideAlertFactory.show( From fd2896dea1b2893f3db375294e670f33bc442503 Mon Sep 17 00:00:00 2001 From: p2glet Date: Wed, 8 Jul 2026 00:14:59 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat/#341:=20=EC=B6=94=EC=B2=9C=20=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=20=EB=A9=98=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dependency/FactoryAssembly.swift | 12 ++++++--- .../Dependency/RepositoryAssembly.swift | 13 +++++---- .../Dependency/UseCaseAssembly.swift | 9 +++---- .../Tabbar/BottomTabBarController.swift | 25 ++++++++++++++++++ .../Contents.json | 21 +++++++++++++++ .../recommendationComment.png | Bin 0 -> 5542 bytes .../UserDefaultsRepositoryImpl.swift | 8 +++++- ...etchVisitDictionaryDetailUseCaseImpl.swift | 4 +-- .../DictionaryMainReactor.swift | 24 ++++++++++++++--- .../DictionaryMainViewController.swift | 10 +++++++ .../DictionaryMainViewFactoryImpl.swift | 6 +++-- ...=> DictionaryUserDefaultsRepository.swift} | 3 ++- ...mmendationUserDefaultsRepositoryImpl.swift | 16 +++++++++++ .../RecommendationMainFactoryImpl.swift | 5 +++- .../RecommendationMainReactor.swift | 15 ++++++++--- .../RecommendationMainViewController.swift | 2 +- .../RecommendationRepository 2.swift | 5 ++++ 17 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 MLS/MLSDesignSystem/Sources/MLSDesignSystem/Resources/Image.xcassets/image/recommendationComment.imageset/Contents.json create mode 100644 MLS/MLSDesignSystem/Sources/MLSDesignSystem/Resources/Image.xcassets/image/recommendationComment.imageset/recommendationComment.png rename MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/{UserDefaultsRepository.swift => DictionaryUserDefaultsRepository.swift} (51%) create mode 100644 MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Data/Repositories/RecommendationUserDefaultsRepositoryImpl.swift create mode 100644 MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeatureInterface/Repositories/RecommendationRepository 2.swift diff --git a/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/FactoryAssembly.swift b/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/FactoryAssembly.swift index 6eb3b6d3..e95bb306 100644 --- a/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/FactoryAssembly.swift +++ b/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/FactoryAssembly.swift @@ -57,8 +57,7 @@ public enum FactoryAssembly { type: SocialLoginUseCase.self ), userDefaultsRepository: DIContainer.resolve( - type: UserDefaultsRepository.self, name: "authUserDefaultsRepository" - ) + type: UserDefaultsRepository.self) ) } DIContainer.register(type: DictionaryDetailFactory.self) { @@ -184,7 +183,12 @@ public enum FactoryAssembly { type: DictionaryNotificationFactory.self ), loginFactory: DIContainer.resolve(type: LoginFactory.self), - fetchProfileUseCase: DIContainer.resolve( + userDefaultsRepository: DIContainer + .resolve( + type: DictionaryUserDefaultsRepository.self + ), + fetchProfileUseCase: DIContainer + .resolve( type: FetchProfileUseCase.self ) ) @@ -432,7 +436,7 @@ public enum FactoryAssembly { DIContainer.register(type: RecommendationMainFactory.self) { RecommendationMainFactoryImpl( repository: DIContainer.resolve(type: RecommendationRepository.self), - bookmarkRepository: DIContainer.resolve(type: BookmarkRepository.self), + bookmarkRepository: DIContainer.resolve(type: BookmarkRepository.self), recommendationUserDefaultsRepository: DIContainer.resolve(type: RecommendationUserDefaultsRepository.self), makeLoginVC: { DIContainer.resolve(type: LoginFactory.self).make(exitRoute: .pop) }, diff --git a/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/RepositoryAssembly.swift b/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/RepositoryAssembly.swift index 1d8eb1b4..020b9ed6 100644 --- a/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/RepositoryAssembly.swift +++ b/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/RepositoryAssembly.swift @@ -38,11 +38,11 @@ public enum RepositoryAssembly { DIContainer.register(type: BookmarkRepository.self) { BookmarkRepositoryImpl() } - DIContainer.register(type: MLSAuthFeatureInterface.UserDefaultsRepository.self, name: "authUserDefaultsRepository") { - MLSAuthFeature.UserDefaultsRepositoryImpl() + DIContainer.register(type: UserDefaultsRepository.self) { + UserDefaultsRepositoryImpl() } - DIContainer.register(type: MLSDictionaryFeatureInterface.UserDefaultsRepository.self, name: "dictionaryUserDefaultsRepository") { - MLSDictionaryFeature.UserDefaultsRepositoryImpl() + DIContainer.register(type: DictionaryUserDefaultsRepository.self) { + UserDefaultsRepositoryImpl() } DIContainer.register(type: AlarmRepository.self) { AlarmRepositoryImpl( @@ -87,5 +87,8 @@ public enum RepositoryAssembly { DIContainer.register(type: UpdateSkipRepositoryProtocol.self) { UpdateSkipRepository() } - } + DIContainer.register(type: RecommendationUserDefaultsRepository.self) { + RecommendationUserDefaultsRepositoryImpl() + } + } } diff --git a/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/UseCaseAssembly.swift b/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/UseCaseAssembly.swift index 2fa2b568..65cffb41 100644 --- a/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/UseCaseAssembly.swift +++ b/MLS/MLSAppFeature/Sources/MLSAppFeature/Dependency/UseCaseAssembly.swift @@ -24,15 +24,14 @@ public enum UseCaseAssembly { type: TokenRepository.self ), userDefaultsRepository: DIContainer.resolve( - type: UserDefaultsRepository.self, name: "authUserDefaultsRepository" - ) + type: UserDefaultsRepository.self) ) } DIContainer.register(type: SocialSignUpUseCaseImpl.self) { SocialSignUpUseCaseImpl( authRepository: DIContainer.resolve(type: AuthAPIRepository.self), tokenRepository: DIContainer.resolve(type: TokenRepository.self), - userDefaultsRepository: DIContainer.resolve(type: UserDefaultsRepository.self, name: "authUserDefaultsRepository") + userDefaultsRepository: DIContainer.resolve(type: UserDefaultsRepository.self) ) } DIContainer.register(type: CheckNotificationPermissionUseCase.self) { @@ -71,7 +70,7 @@ public enum UseCaseAssembly { ParseItemFilterResultUseCaseImpl() } DIContainer.register(type: FetchVisitDictionaryDetailUseCase.self) { - FetchVisitDictionaryDetailUseCaseImpl(repository: DIContainer.resolve(type: UserDefaultsRepository.self, name: "dictionaryUserDefaultsRepository")) + FetchVisitDictionaryDetailUseCaseImpl(repository: DIContainer.resolve(type: DictionaryUserDefaultsRepository.self)) } DIContainer.register(type: SocialSignUpUseCase.self) { SocialSignUpUseCaseImpl( @@ -80,7 +79,7 @@ public enum UseCaseAssembly { tokenRepository: DIContainer .resolve(type: TokenRepository.self), userDefaultsRepository: DIContainer - .resolve(type: UserDefaultsRepository.self, name: "authUserDefaultsRepository") + .resolve(type: UserDefaultsRepository.self) ) } DIContainer.register(type: UpdateCheckerUseCaseProtocol.self) { diff --git a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Tabbar/BottomTabBarController.swift b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Tabbar/BottomTabBarController.swift index faa4ea56..42b3244b 100644 --- a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Tabbar/BottomTabBarController.swift +++ b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Tabbar/BottomTabBarController.swift @@ -6,6 +6,13 @@ public final class BottomTabBarController: UITabBarController { // MARK: - Type private enum Constant { static let horizontalInset: CGFloat = 24 + static let commentBottomMargin: CGFloat = -8 + static let commentWidth: CGFloat = 152 + static let commentHeight: CGFloat = 28 + static let buttonSize: CGFloat = 64 + static let arrowInset: CGFloat = 16 + @MainActor static let spacing: CGFloat = (UIScreen.main.bounds.width - (horizontalInset * 2) - (buttonSize * 4)) / 3 + @MainActor static let commentLeadingOffset: CGFloat = horizontalInset + buttonSize + spacing + (buttonSize / 2) - arrowInset } // MARK: - Components @@ -17,6 +24,12 @@ public final class BottomTabBarController: UITabBarController { view.backgroundColor = .systemBackground return view }() + private let recoomendationCommentView: UIImageView = { + let view = UIImageView() + view.image = DesignSystemAsset.image(named: "recommendationComment") + view.isHidden = true + return view + }() // MARK: - Init public init(viewControllers: [UIViewController], tabItems: [TabItem]? = nil, initialIndex: Int = 0) { @@ -50,6 +63,7 @@ private extension BottomTabBarController { view.addSubview(tabBarBackground) view.addSubview(customTabBar) view.addSubview(divider) + view.addSubview(recoomendationCommentView) } func setupConstraints() { @@ -67,6 +81,13 @@ private extension BottomTabBarController { make.horizontalEdges.bottom.equalToSuperview() make.top.equalTo(divider.snp.top) } + + recoomendationCommentView.snp.makeConstraints { make in + make.bottom.equalTo(customTabBar.snp.top).offset(Constant.commentBottomMargin) + make.leading.equalTo(view.snp.leading).offset(Constant.commentLeadingOffset) + make.width.equalTo(Constant.commentWidth) + make.height.equalTo(Constant.commentHeight) + } } func configureUI(controllers: [UIViewController]) { @@ -132,4 +153,8 @@ public extension BottomTabBarController { customTabBar.selectTab(index: index) } } + + func checkRecommendationFirstLaunch(isFirst: Bool) { + recoomendationCommentView.isHidden = isFirst + } } diff --git a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Resources/Image.xcassets/image/recommendationComment.imageset/Contents.json b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Resources/Image.xcassets/image/recommendationComment.imageset/Contents.json new file mode 100644 index 00000000..97b8a027 --- /dev/null +++ b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Resources/Image.xcassets/image/recommendationComment.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "recommendationComment.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Resources/Image.xcassets/image/recommendationComment.imageset/recommendationComment.png b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Resources/Image.xcassets/image/recommendationComment.imageset/recommendationComment.png new file mode 100644 index 0000000000000000000000000000000000000000..bc409637714677cad4632c90866894f587d867f3 GIT binary patch literal 5542 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 literal 0 HcmV?d00001 diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Data/Repositories/UserDefaultsRepositoryImpl.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Data/Repositories/UserDefaultsRepositoryImpl.swift index 3b31c140..ab4c16e2 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Data/Repositories/UserDefaultsRepositoryImpl.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Data/Repositories/UserDefaultsRepositoryImpl.swift @@ -4,8 +4,9 @@ import MLSDictionaryFeatureInterface import RxSwift -public final class UserDefaultsRepositoryImpl: UserDefaultsRepository { +public final class UserDefaultsRepositoryImpl: DictionaryUserDefaultsRepository { private let dictionaryDetailkey = "dictionaryDetailkey" + private let recommendationkey = "recommendationkey" public init() {} @@ -25,4 +26,9 @@ public final class UserDefaultsRepositoryImpl: UserDefaultsRepository { return Disposables.create() } } + + public func checkFirstLauchRecommendation() -> Observable { + let hasVisited = UserDefaults.standard.bool(forKey: recommendationkey) + return .just(hasVisited) + } } diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Domain/Usecases/FetchVisitDictionaryDetailUseCaseImpl.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Domain/Usecases/FetchVisitDictionaryDetailUseCaseImpl.swift index 0e760ec1..5b6dcc62 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Domain/Usecases/FetchVisitDictionaryDetailUseCaseImpl.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Domain/Usecases/FetchVisitDictionaryDetailUseCaseImpl.swift @@ -4,8 +4,8 @@ import MLSDictionaryFeatureInterface import RxSwift public class FetchVisitDictionaryDetailUseCaseImpl: FetchVisitDictionaryDetailUseCase { - private let repository: UserDefaultsRepository - public init(repository: UserDefaultsRepository) { + private let repository: DictionaryUserDefaultsRepository + public init(repository: DictionaryUserDefaultsRepository) { self.repository = repository } diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainReactor.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainReactor.swift index 14c09a57..aafa9752 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainReactor.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainReactor.swift @@ -23,6 +23,7 @@ public final class DictionaryMainReactor: Reactor { case navigateTo(Route) case setLogin(Bool) case setCurrentTab(oldIndex: Int, newIndex: Int) + case setIsFirstRecommendationLaunch(Bool) } public struct State { @@ -31,20 +32,24 @@ public final class DictionaryMainReactor: Reactor { var sections: [String] { return type.pageTabList.map { $0.title } } + var isLogin = false var currentPageIndex = 0 var oldPageIndex = 0 + var isFirstRecommendationLaunch = false } // MARK: - properties public var initialState: State var disposeBag = DisposeBag() + private let userDefaultsRepository: DictionaryUserDefaultsRepository private let fetchProfileUseCase: FetchProfileUseCase // MARK: - init - public init(fetchProfileUseCase: FetchProfileUseCase) { + public init(userDefaultsRepository: DictionaryUserDefaultsRepository, fetchProfileUseCase: FetchProfileUseCase) { self.initialState = State() + self.userDefaultsRepository = userDefaultsRepository self.fetchProfileUseCase = fetchProfileUseCase } @@ -52,9 +57,18 @@ public final class DictionaryMainReactor: Reactor { public func mutate(action: Action) -> Observable { switch action { case .viewWillAppear: - return fetchProfileUseCase.execute() - .map { .setLogin($0 != nil) } + let firstRecommendation = userDefaultsRepository + .checkFirstLauchRecommendation() + .map(Mutation.setIsFirstRecommendationLaunch) + + let fetchProfile = fetchProfileUseCase.execute() + .map { Mutation.setLogin($0 != nil) } .catchAndReturn(.setLogin(false)) + + return Observable.merge( + firstRecommendation, + fetchProfile + ) case .searchButtonTapped: return .just(.navigateTo(.search)) case .notificationButtonTapped: @@ -69,13 +83,15 @@ public final class DictionaryMainReactor: Reactor { var newState = state switch mutation { - case .navigateTo(let route): + case let .navigateTo(route): newState.route = route case let .setLogin(isLogin): newState.isLogin = isLogin case let .setCurrentTab(oldIndex, newIndex): newState.oldPageIndex = oldIndex newState.currentPageIndex = newIndex + case let .setIsFirstRecommendationLaunch(isFirst): + newState.isFirstRecommendationLaunch = isFirst } return newState diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewController.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewController.swift index f1c16414..663068cf 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewController.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewController.swift @@ -193,6 +193,16 @@ public extension DictionaryMainViewController { owner.moveToTab(oldIndex: oldIndex, newIndex: newIndex) }) .disposed(by: disposeBag) + + reactor.state + .observe(on: MainScheduler.instance) + .map { $0.isFirstRecommendationLaunch } + .withUnretained(self) + .subscribe { owner, isFirst in + (owner.tabBarController as? BottomTabBarController)? + .checkRecommendationFirstLaunch(isFirst: isFirst) + } + .disposed(by: disposeBag) } } diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewFactoryImpl.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewFactoryImpl.swift index 6be090e8..8cd5fa49 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewFactoryImpl.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewFactoryImpl.swift @@ -8,18 +8,20 @@ public final class DictionaryMainViewFactoryImpl: DictionaryMainViewFactory { private let searchFactory: DictionarySearchFactory private let notificationFactory: DictionaryNotificationFactory private let loginFactory: LoginFactory + private let userDefaultsRepository: DictionaryUserDefaultsRepository private let fetchProfileUseCase: FetchProfileUseCase - public init(dictionaryMainListFactory: DictionaryMainListFactory, searchFactory: DictionarySearchFactory, notificationFactory: DictionaryNotificationFactory, loginFactory: LoginFactory, fetchProfileUseCase: FetchProfileUseCase) { + public init(dictionaryMainListFactory: DictionaryMainListFactory, searchFactory: DictionarySearchFactory, notificationFactory: DictionaryNotificationFactory, loginFactory: LoginFactory, userDefaultsRepository: DictionaryUserDefaultsRepository, fetchProfileUseCase: FetchProfileUseCase) { self.dictionaryMainListFactory = dictionaryMainListFactory self.searchFactory = searchFactory self.notificationFactory = notificationFactory self.loginFactory = loginFactory + self.userDefaultsRepository = userDefaultsRepository self.fetchProfileUseCase = fetchProfileUseCase } public func make() -> BaseViewController { - let reactor = DictionaryMainReactor(fetchProfileUseCase: fetchProfileUseCase) + let reactor = DictionaryMainReactor(userDefaultsRepository: userDefaultsRepository, fetchProfileUseCase: fetchProfileUseCase) let viewController = DictionaryMainViewController(dictionaryMainListFactory: dictionaryMainListFactory, searchFactory: searchFactory, notificationFactory: notificationFactory, loginFactory: loginFactory, reactor: reactor, ) viewController.isBottomTabbarHidden = false viewController.reactor = reactor diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/UserDefaultsRepository.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/DictionaryUserDefaultsRepository.swift similarity index 51% rename from MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/UserDefaultsRepository.swift rename to MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/DictionaryUserDefaultsRepository.swift index 33ef87bb..7cb77e21 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/UserDefaultsRepository.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/DictionaryUserDefaultsRepository.swift @@ -1,6 +1,7 @@ import RxSwift -public protocol UserDefaultsRepository { +public protocol DictionaryUserDefaultsRepository { func fetchDictionaryDetail() -> Observable func saveDictionaryDetail() -> Completable + func checkFirstLauchRecommendation() -> Observable } diff --git a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Data/Repositories/RecommendationUserDefaultsRepositoryImpl.swift b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Data/Repositories/RecommendationUserDefaultsRepositoryImpl.swift new file mode 100644 index 00000000..8733fe87 --- /dev/null +++ b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Data/Repositories/RecommendationUserDefaultsRepositoryImpl.swift @@ -0,0 +1,16 @@ +import Foundation + +import MLSRecommendationFeatureInterface + +import RxSwift + +public final class RecommendationUserDefaultsRepositoryImpl: RecommendationUserDefaultsRepository { + private let recommendationkey = "recommendationkey" + + public init() {} + + public func saveFirstLaunch() -> Completable { + UserDefaults.standard.set(true, forKey: recommendationkey) + return .empty() + } +} diff --git a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainFactoryImpl.swift b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainFactoryImpl.swift index 60cd147f..0efaf1cf 100644 --- a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainFactoryImpl.swift +++ b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainFactoryImpl.swift @@ -7,6 +7,7 @@ import MLSRecommendationFeatureInterface public struct RecommendationMainFactoryImpl: RecommendationMainFactory { private let repository: RecommendationRepository private let bookmarkRepository: BookmarkRepository + private let recommendationUserDefaultsRepository: RecommendationUserDefaultsRepository private let makeLoginVC: (() -> UIViewController)? private let makeCharacterSettingVC: (() -> UIViewController)? private let makeSearchVC: (() -> UIViewController)? @@ -17,6 +18,7 @@ public struct RecommendationMainFactoryImpl: RecommendationMainFactory { public init( repository: RecommendationRepository, bookmarkRepository: BookmarkRepository, + recommendationUserDefaultsRepository: RecommendationUserDefaultsRepository, makeLoginVC: (() -> UIViewController)? = nil, makeCharacterSettingVC: (() -> UIViewController)? = nil, makeSearchVC: (() -> UIViewController)? = nil, @@ -26,6 +28,7 @@ public struct RecommendationMainFactoryImpl: RecommendationMainFactory { ) { self.repository = repository self.bookmarkRepository = bookmarkRepository + self.recommendationUserDefaultsRepository = recommendationUserDefaultsRepository self.makeLoginVC = makeLoginVC self.makeCharacterSettingVC = makeCharacterSettingVC self.makeSearchVC = makeSearchVC @@ -36,7 +39,7 @@ public struct RecommendationMainFactoryImpl: RecommendationMainFactory { public func make() -> BaseViewController { let vc = RecommendationMainViewController() - vc.reactor = RecommendationMainReactor(repository: repository, bookmarkRepository: bookmarkRepository) + vc.reactor = RecommendationMainReactor(repository: repository, bookmarkRepository: bookmarkRepository, userDefaultsRepository: recommendationUserDefaultsRepository) vc.onLoginTapped = makeLoginVC vc.onEditTapped = makeCharacterSettingVC vc.onSearchTapped = makeSearchVC diff --git a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainReactor.swift b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainReactor.swift index efa2a80b..e34cad2c 100644 --- a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainReactor.swift +++ b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainReactor.swift @@ -53,11 +53,13 @@ final class RecommendationMainReactor: Reactor { private let repository: RecommendationRepository private let bookmarkRepository: BookmarkRepository + private let userDefaultsRepository: RecommendationUserDefaultsRepository // MARK: - Init - init(repository: RecommendationRepository, bookmarkRepository: BookmarkRepository) { + init(repository: RecommendationRepository, bookmarkRepository: BookmarkRepository, userDefaultsRepository: RecommendationUserDefaultsRepository) { self.repository = repository self.bookmarkRepository = bookmarkRepository + self.userDefaultsRepository = userDefaultsRepository self.initialState = State() } @@ -65,13 +67,15 @@ final class RecommendationMainReactor: Reactor { func mutate(action: Action) -> Observable { switch action { case .viewWillAppear: + let saveFirstRecommendationLaunch = userDefaultsRepository + .saveFirstLaunch() + .andThen(Observable.empty()) + let fetchAll = repository.fetchProfile() .flatMap { [weak self] profile -> Observable in guard let self else { return .empty() } let setLogin = Observable.just(Mutation.setLogin(true)) - - // 프로필 저장 let setProfile = Observable.just(Mutation.setProfile(profile)) // 프로필 미입력 상태 @@ -121,7 +125,10 @@ final class RecommendationMainReactor: Reactor { return Observable.concat([ .just(.setLoading(true)), - fetchAll, + Observable.merge([ + fetchAll, + saveFirstRecommendationLaunch + ]), .just(.setLoading(false)) ]) diff --git a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainViewController.swift b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainViewController.swift index 1b18a431..1f76138f 100644 --- a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainViewController.swift +++ b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Presentation/RecommendationMain/RecommendationMainViewController.swift @@ -149,7 +149,7 @@ extension RecommendationMainViewController { owner.navigationController?.pushViewController(vc, animated: true) }, cancelAction: { if let tabBarController = owner.tabBarController as? BottomTabBarController { - tabBarController.selectTab(index: 1) + tabBarController.selectTab(index: 0) } }, ctaRatio: 0.5) } diff --git a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeatureInterface/Repositories/RecommendationRepository 2.swift b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeatureInterface/Repositories/RecommendationRepository 2.swift new file mode 100644 index 00000000..cdf87b34 --- /dev/null +++ b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeatureInterface/Repositories/RecommendationRepository 2.swift @@ -0,0 +1,5 @@ +import RxSwift + +public protocol RecommendationUserDefaultsRepository { + func saveFirstLaunch() -> Completable +} From 2d67290376d656a8297beb8945cc583d64d2571e Mon Sep 17 00:00:00 2001 From: p2glet Date: Sun, 12 Jul 2026 22:57:43 +0900 Subject: [PATCH 6/8] =?UTF-8?q?fix/#341:=20=EC=98=A4=ED=83=80=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Tabbar/BottomTabBarController.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Tabbar/BottomTabBarController.swift b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Tabbar/BottomTabBarController.swift index 42b3244b..06be2ab5 100644 --- a/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Tabbar/BottomTabBarController.swift +++ b/MLS/MLSDesignSystem/Sources/MLSDesignSystem/Components/Tabbar/BottomTabBarController.swift @@ -24,7 +24,7 @@ public final class BottomTabBarController: UITabBarController { view.backgroundColor = .systemBackground return view }() - private let recoomendationCommentView: UIImageView = { + private let recommendationCommentView: UIImageView = { let view = UIImageView() view.image = DesignSystemAsset.image(named: "recommendationComment") view.isHidden = true @@ -63,7 +63,7 @@ private extension BottomTabBarController { view.addSubview(tabBarBackground) view.addSubview(customTabBar) view.addSubview(divider) - view.addSubview(recoomendationCommentView) + view.addSubview(recommendationCommentView) } func setupConstraints() { @@ -82,7 +82,7 @@ private extension BottomTabBarController { make.top.equalTo(divider.snp.top) } - recoomendationCommentView.snp.makeConstraints { make in + recommendationCommentView.snp.makeConstraints { make in make.bottom.equalTo(customTabBar.snp.top).offset(Constant.commentBottomMargin) make.leading.equalTo(view.snp.leading).offset(Constant.commentLeadingOffset) make.width.equalTo(Constant.commentWidth) @@ -155,6 +155,6 @@ public extension BottomTabBarController { } func checkRecommendationFirstLaunch(isFirst: Bool) { - recoomendationCommentView.isHidden = isFirst + recommendationCommentView.isHidden = isFirst } } From 5806655c5117deae6f61cc1624734dcf61f0c72c Mon Sep 17 00:00:00 2001 From: p2glet Date: Sun, 12 Jul 2026 23:02:55 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix/#341:=20userDefaults=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DictionaryMain/DictionaryMainViewController.swift | 1 + .../RecommendationUserDefaultsRepositoryImpl.swift | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewController.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewController.swift index 663068cf..b4644698 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewController.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainViewController.swift @@ -197,6 +197,7 @@ public extension DictionaryMainViewController { reactor.state .observe(on: MainScheduler.instance) .map { $0.isFirstRecommendationLaunch } + .distinctUntilChanged() .withUnretained(self) .subscribe { owner, isFirst in (owner.tabBarController as? BottomTabBarController)? diff --git a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Data/Repositories/RecommendationUserDefaultsRepositoryImpl.swift b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Data/Repositories/RecommendationUserDefaultsRepositoryImpl.swift index 8733fe87..95b70313 100644 --- a/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Data/Repositories/RecommendationUserDefaultsRepositoryImpl.swift +++ b/MLS/MLSRecommendationFeature/Sources/MLSRecommendationFeature/Data/Repositories/RecommendationUserDefaultsRepositoryImpl.swift @@ -10,7 +10,9 @@ public final class RecommendationUserDefaultsRepositoryImpl: RecommendationUserD public init() {} public func saveFirstLaunch() -> Completable { - UserDefaults.standard.set(true, forKey: recommendationkey) + if !UserDefaults.standard.bool(forKey: recommendationkey) { + UserDefaults.standard.set(true, forKey: recommendationkey) + } return .empty() } } From c73c89e969c306f6acf99cfd7089ea390719209c Mon Sep 17 00:00:00 2001 From: p2glet Date: Sun, 12 Jul 2026 23:03:46 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix/#341:=20=EC=98=A4=ED=83=80=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Repositories/UserDefaultsRepositoryImpl.swift | 2 +- .../Presentation/DictionaryMain/DictionaryMainReactor.swift | 2 +- .../Repositories/DictionaryUserDefaultsRepository.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Data/Repositories/UserDefaultsRepositoryImpl.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Data/Repositories/UserDefaultsRepositoryImpl.swift index ab4c16e2..ef50a367 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Data/Repositories/UserDefaultsRepositoryImpl.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Data/Repositories/UserDefaultsRepositoryImpl.swift @@ -27,7 +27,7 @@ public final class UserDefaultsRepositoryImpl: DictionaryUserDefaultsRepository } } - public func checkFirstLauchRecommendation() -> Observable { + public func checkFirstLaunchRecommendation() -> Observable { let hasVisited = UserDefaults.standard.bool(forKey: recommendationkey) return .just(hasVisited) } diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainReactor.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainReactor.swift index aafa9752..09c2c0cb 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainReactor.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeature/Presentation/DictionaryMain/DictionaryMainReactor.swift @@ -58,7 +58,7 @@ public final class DictionaryMainReactor: Reactor { switch action { case .viewWillAppear: let firstRecommendation = userDefaultsRepository - .checkFirstLauchRecommendation() + .checkFirstLaunchRecommendation() .map(Mutation.setIsFirstRecommendationLaunch) let fetchProfile = fetchProfileUseCase.execute() diff --git a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/DictionaryUserDefaultsRepository.swift b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/DictionaryUserDefaultsRepository.swift index 7cb77e21..21e559dc 100644 --- a/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/DictionaryUserDefaultsRepository.swift +++ b/MLS/MLSDictionaryFeature/Sources/MLSDictionaryFeatureInterface/Repositories/DictionaryUserDefaultsRepository.swift @@ -3,5 +3,5 @@ import RxSwift public protocol DictionaryUserDefaultsRepository { func fetchDictionaryDetail() -> Observable func saveDictionaryDetail() -> Completable - func checkFirstLauchRecommendation() -> Observable + func checkFirstLaunchRecommendation() -> Observable }