From b8f8eec991c09355a8b29370912a723c48db74c9 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 6 Jan 2026 16:45:16 +0900 Subject: [PATCH 01/25] =?UTF-8?q?refactor:=20#99=20=ED=99=94=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=B0=EA=B2=BD=20=EC=82=BD=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Feature/Setting/View/Main/SettingView.swift | 1 + .../Feature/Setting/View/ModifyName/ModifyNameView.swift | 1 + .../Presentation/Feature/Setting/View/Profile/ProfileView.swift | 1 + 3 files changed, 3 insertions(+) diff --git a/BeforeGoing/Presentation/Feature/Setting/View/Main/SettingView.swift b/BeforeGoing/Presentation/Feature/Setting/View/Main/SettingView.swift index f64aff90..2b151f5b 100644 --- a/BeforeGoing/Presentation/Feature/Setting/View/Main/SettingView.swift +++ b/BeforeGoing/Presentation/Feature/Setting/View/Main/SettingView.swift @@ -18,6 +18,7 @@ final class SettingView: BaseView { private(set) var policyView = PolicyView() override func setStyle() { + backgroundColor = .white titleLabel.do { $0.text = "설정" $0.textColor = .gray900 diff --git a/BeforeGoing/Presentation/Feature/Setting/View/ModifyName/ModifyNameView.swift b/BeforeGoing/Presentation/Feature/Setting/View/ModifyName/ModifyNameView.swift index acd16b6a..83777926 100644 --- a/BeforeGoing/Presentation/Feature/Setting/View/ModifyName/ModifyNameView.swift +++ b/BeforeGoing/Presentation/Feature/Setting/View/ModifyName/ModifyNameView.swift @@ -19,6 +19,7 @@ final class ModifyNameView: BaseView { private(set) var confirmButton = CustomButton(state: .enableLongButton, title: "확인") override func setStyle() { + backgroundColor = .white nameLabel.do { $0.text = "이름" $0.textColor = .gray600 diff --git a/BeforeGoing/Presentation/Feature/Setting/View/Profile/ProfileView.swift b/BeforeGoing/Presentation/Feature/Setting/View/Profile/ProfileView.swift index 7b1c0c6a..31646c2f 100644 --- a/BeforeGoing/Presentation/Feature/Setting/View/Profile/ProfileView.swift +++ b/BeforeGoing/Presentation/Feature/Setting/View/Profile/ProfileView.swift @@ -18,6 +18,7 @@ final class ProfileView: BaseView { private(set) var withdrawView = ProfileFeatureView(title: "회원탈퇴") override func setStyle() { + backgroundColor = .white worryImageView.do { $0.image = .profile } From 7837cd337d7450e64b73d9dee125573a8488e4b2 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 6 Jan 2026 16:53:44 +0900 Subject: [PATCH 02/25] =?UTF-8?q?setting:=20#99=20=EB=B0=B1=EA=B7=B8?= =?UTF-8?q?=EB=9D=BC=EC=9A=B4=EB=93=9C=20=EA=B6=8C=ED=95=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BeforeGoing.xcodeproj/project.pbxproj | 12 ++++++++---- BeforeGoing/Resource/Info.plist | 20 ++++---------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/BeforeGoing.xcodeproj/project.pbxproj b/BeforeGoing.xcodeproj/project.pbxproj index 200b2056..ae56052b 100644 --- a/BeforeGoing.xcodeproj/project.pbxproj +++ b/BeforeGoing.xcodeproj/project.pbxproj @@ -267,12 +267,14 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = BeforeGoing/Resource/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; - INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "\"현재 위치 사용을 허용하시겠습니까?\""; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "\"앱을 사용하는 동안 위치 사용을 허용하시겠습니까?\""; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "앱 사용 여부와 관계없이 현재 위치의 날씨를 확인하고 정확한 알림을 제공하는 데 사용됩니다."; + INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "앱이 닫혀 있을 때에도 날씨 정보를 미리 업데이트하여 정확한 정보를 제공하기 위해 위치를 사용합니다."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "현재 위치의 날씨 정보를 제공하고, 사용자가 직접 미션을 추가할 때 지리적 태그를 지정하기 위해 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -307,12 +309,14 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = BeforeGoing/Resource/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; - INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "\"현재 위치 사용을 허용하시겠습니까?\""; - INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "\"앱을 사용하는 동안 위치 사용을 허용하시겠습니까?\""; + INFOPLIST_KEY_NSLocationAlwaysAndWhenInUseUsageDescription = "앱 사용 여부와 관계없이 현재 위치의 날씨를 확인하고 정확한 알림을 제공하는 데 사용됩니다."; + INFOPLIST_KEY_NSLocationAlwaysUsageDescription = "앱이 닫혀 있을 때에도 날씨 정보를 미리 업데이트하여 정확한 정보를 제공하기 위해 위치를 사용합니다."; + INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "현재 위치의 날씨 정보를 제공하고, 사용자가 직접 미션을 추가할 때 지리적 태그를 지정하기 위해 필요합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UIUserInterfaceStyle = Light; IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/BeforeGoing/Resource/Info.plist b/BeforeGoing/Resource/Info.plist index 5698295d..9fb075d0 100644 --- a/BeforeGoing/Resource/Info.plist +++ b/BeforeGoing/Resource/Info.plist @@ -2,18 +2,6 @@ - LSApplicationQueriesSchemes - - kakaokompassauth - - UIUserInterfaceStyle - Light - NSLocationAlwaysUsageDescription - 앱이 닫혀 있을 때에도 날씨 정보를 미리 업데이트하여 정확한 정보를 제공하기 위해 위치를 사용합니다. - NSLocationWhenInUseUsageDescription - 현재 위치의 날씨 정보를 제공하고, 사용자가 직접 미션을 추가할 때 지리적 태그를 지정하기 위해 필요합니다. - NSLocationAlwaysAndWhenInUseUsageDescription - 앱 사용 여부와 관계없이 현재 위치의 날씨를 확인하고 정확한 알림을 제공하는 데 사용됩니다. BASE_URL $(BASE_URL) CFBundleURLTypes @@ -29,6 +17,10 @@ KAKAO_NATIVE_APP_KEY $(KAKAO_NATIVE_APP_KEY) + LSApplicationQueriesSchemes + + kakaokompassauth + UIAppFonts Pretendard-SemiBold.otf @@ -56,9 +48,5 @@ - UIBackgroundModes - - location - From ff242b5946eaee5eb162fea03ae752f9a395cd90 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 6 Jan 2026 17:22:12 +0900 Subject: [PATCH 03/25] =?UTF-8?q?setting:=20#99=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=84=A4=EC=A0=95=20=EB=AC=B8=EA=B5=AC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BeforeGoing/Resource/Info.plist | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BeforeGoing/Resource/Info.plist b/BeforeGoing/Resource/Info.plist index 9fb075d0..49dce960 100644 --- a/BeforeGoing/Resource/Info.plist +++ b/BeforeGoing/Resource/Info.plist @@ -2,6 +2,12 @@ + NSLocationWhenInUseUsageDescription + 사용자의 현재 위치를 기반으로 기상정보와 준비물을 추천해 드리기 위해 위치 권한을 사용합니다 + NSLocationAlwaysAndWhenInUseUsageDescription + 앱 사용 여부와 관계없이 현재 위치의 날씨를 확인하고 정확한 알림을 제공하는 데 사용됩니다. + NSLocationAlwaysUsageDescription + 앱이 닫혀 있을 때에도 날씨 정보를 미리 업데이트하여 정확한 정보를 제공하기 위해 위치를 사용합니다. BASE_URL $(BASE_URL) CFBundleURLTypes From 3a91055920fb1a8571040ca85880d50ce5e6f6fb Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 6 Jan 2026 20:48:20 +0900 Subject: [PATCH 04/25] =?UTF-8?q?feat:=20#99=20=EC=97=90=ED=94=8C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EB=B7=B0=20=EA=B1=B4=EB=84=88=EB=9B=B0?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BeforeGoing/Core/BeforeGoingError.swift | 1 + .../Data/Repository/MemberRepository.swift | 11 +++++++ .../Domain/DomainDependencyAssembler.swift | 3 ++ .../Domain/Interface/MemberInterface.swift | 2 ++ .../Member/IsAppleLoginedUseCase.swift | 24 +++++++++++++++ .../AgreeTermsViewController.swift | 22 ++++++++++++-- .../ViewModel/AgreeItemViewModel.swift | 30 ++++++++++++++++--- .../PresentationDependencyAssembler.swift | 8 ++++- 8 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 BeforeGoing/Domain/UseCase/Member/IsAppleLoginedUseCase.swift diff --git a/BeforeGoing/Core/BeforeGoingError.swift b/BeforeGoing/Core/BeforeGoingError.swift index ca034553..11a3670f 100644 --- a/BeforeGoing/Core/BeforeGoingError.swift +++ b/BeforeGoing/Core/BeforeGoingError.swift @@ -29,4 +29,5 @@ enum BeforeGoingError: Error, Equatable { case missionLimitError case tooManyRequset case withdrawFailed + case notFoundProvider } diff --git a/BeforeGoing/Data/Repository/MemberRepository.swift b/BeforeGoing/Data/Repository/MemberRepository.swift index d2c06c8d..44d406c6 100644 --- a/BeforeGoing/Data/Repository/MemberRepository.swift +++ b/BeforeGoing/Data/Repository/MemberRepository.swift @@ -24,6 +24,17 @@ struct MemberRepository: MemberInterface { self.updateNicknameRequestMapper = updateNicknameRequestMapper } + var isAppleLogined: Bool? { + guard let provider: String = userDefaultsService.load(key: .provider) else { + return nil + } + + if provider == Provider.apple.rawValue { + return true + } + return false + } + func getMemberName() -> String? { guard let provider: String = userDefaultsService.load(key: .provider) else { return nil diff --git a/BeforeGoing/Domain/DomainDependencyAssembler.swift b/BeforeGoing/Domain/DomainDependencyAssembler.swift index 1223cca8..19a2721c 100644 --- a/BeforeGoing/Domain/DomainDependencyAssembler.swift +++ b/BeforeGoing/Domain/DomainDependencyAssembler.swift @@ -94,6 +94,9 @@ final class DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: SendAgreeTermsType.self) { _ in return SendAgreeTermsUseCase(repository: termsRepository) } + DIContainer.shared.register(type: IsAppleLoginType.self) { _ in + return IsAppleLoginedUseCase(repository: memberRepository) + } DIContainer.shared.register(type: UpdatePushNoticeType.self) { _ in return UpdatePushNoticeUseCase(repository: termsRepository) } diff --git a/BeforeGoing/Domain/Interface/MemberInterface.swift b/BeforeGoing/Domain/Interface/MemberInterface.swift index 9de2929b..2ecedc82 100644 --- a/BeforeGoing/Domain/Interface/MemberInterface.swift +++ b/BeforeGoing/Domain/Interface/MemberInterface.swift @@ -7,6 +7,8 @@ protocol MemberInterface { + var isAppleLogined: Bool? { get } + func updateNickname(nickname: String) async throws func withdrawMember() async throws func getMemberName() -> String? diff --git a/BeforeGoing/Domain/UseCase/Member/IsAppleLoginedUseCase.swift b/BeforeGoing/Domain/UseCase/Member/IsAppleLoginedUseCase.swift new file mode 100644 index 00000000..511be4b5 --- /dev/null +++ b/BeforeGoing/Domain/UseCase/Member/IsAppleLoginedUseCase.swift @@ -0,0 +1,24 @@ +// +// IsAppleLoginedUseCase.swift +// BeforeGoing +// +// Created by APPLE on 1/6/26. +// + +protocol IsAppleLoginType { + + func execute() -> Bool? +} + +struct IsAppleLoginedUseCase: IsAppleLoginType { + + private let repository: MemberInterface + + init(repository: MemberInterface) { + self.repository = repository + } + + func execute() -> Bool? { + repository.isAppleLogined + } +} diff --git a/BeforeGoing/Presentation/Feature/Approach/ViewController/AgreeTermsViewController.swift b/BeforeGoing/Presentation/Feature/Approach/ViewController/AgreeTermsViewController.swift index f03ed8d6..547d58a7 100644 --- a/BeforeGoing/Presentation/Feature/Approach/ViewController/AgreeTermsViewController.swift +++ b/BeforeGoing/Presentation/Feature/Approach/ViewController/AgreeTermsViewController.swift @@ -88,8 +88,26 @@ extension AgreeTermsViewController { } if output.agreeTermsResult { - let nicknameViewController = ViewControllerFactory.shared.makeNicknameViewController() - self.navigationController?.pushViewController(nicknameViewController, animated: false) + guard let isAppleLoginedOutput = try await viewModel.action( + input: .checkLoginMethod + ) as? AgreeItemViewModel.IsAppleLoginedOutput else { + return + } + + switch isAppleLoginedOutput.isAppleLogined { + case .success(let isAppleLogined): + if isAppleLogined { + let onboardoingViewController = ViewControllerFactory.shared.makeOnboardingViewController() + onboardoingViewController.navigationItem.hidesBackButton = true + self.navigationController?.pushViewController(onboardoingViewController, animated: false) + return + } + let nicknameViewController = ViewControllerFactory.shared.makeNicknameViewController() + self.navigationController?.pushViewController(nicknameViewController, animated: false) + + case .failure(let error): + BeforeGoingLogger.error(error) + } return } BeforeGoingLogger.error(BeforeGoingError.agreeTermsFailed) diff --git a/BeforeGoing/Presentation/Feature/Approach/ViewModel/AgreeItemViewModel.swift b/BeforeGoing/Presentation/Feature/Approach/ViewModel/AgreeItemViewModel.swift index 028622dc..1692d147 100644 --- a/BeforeGoing/Presentation/Feature/Approach/ViewModel/AgreeItemViewModel.swift +++ b/BeforeGoing/Presentation/Feature/Approach/ViewModel/AgreeItemViewModel.swift @@ -11,11 +11,13 @@ final class AgreeItemViewModel: ViewModeling { private var agreeItems = AgreeItem.allCases private var checkBoxStates: [AgreeItem : CheckBoxState] = [:] - private let useCase: SendAgreeTermsType + private let sendAgreeUseCase: SendAgreeTermsType + private let isAppleLoginedUseCase: IsAppleLoginType enum Input { case nextButtonDidTap case initTerms + case checkLoginMethod } typealias Output = AgreeItemOutput @@ -26,8 +28,17 @@ final class AgreeItemViewModel: ViewModeling { struct EmptyOutput: AgreeItemOutput {} - init(useCase: SendAgreeTermsType) { - self.useCase = useCase + struct IsAppleLoginedOutput: AgreeItemOutput { + let isAppleLogined: Result + } + + init( + sendAgreeUseCase: SendAgreeTermsType, + isAppleLoginedUseCase: IsAppleLoginType + ) { + self.sendAgreeUseCase = sendAgreeUseCase + self.isAppleLoginedUseCase = isAppleLoginedUseCase + agreeItems.forEach { checkBoxStates[$0] = .unchecked } } @@ -35,7 +46,7 @@ final class AgreeItemViewModel: ViewModeling { switch input { case .nextButtonDidTap: do { - try await useCase.execute( + try await sendAgreeUseCase.execute( termsOfServiceAgreed: matchState(item: .isTermsOfServiceAgreed), privacyPolicyAgreed: matchState(item: .isPrivacyPolicyAgreed), isOver14: matchState(item: .isOverFourteen), @@ -47,9 +58,20 @@ final class AgreeItemViewModel: ViewModeling { BeforeGoingLogger.error(error) return TermsOutput(agreeTermsResult: false) } + case .initTerms: agreeItems.forEach { checkBoxStates[$0] = .unchecked } return EmptyOutput() + + case .checkLoginMethod: + let result = isAppleLoginedUseCase.execute() + + switch result { + case .some(let isAppleLogined): + return IsAppleLoginedOutput(isAppleLogined: .success(isAppleLogined)) + case .none: + return IsAppleLoginedOutput(isAppleLogined: .failure(.notFoundProvider)) + } } } } diff --git a/BeforeGoing/Presentation/PresentationDependencyAssembler.swift b/BeforeGoing/Presentation/PresentationDependencyAssembler.swift index 06dace06..55d16c67 100644 --- a/BeforeGoing/Presentation/PresentationDependencyAssembler.swift +++ b/BeforeGoing/Presentation/PresentationDependencyAssembler.swift @@ -41,6 +41,11 @@ struct PresentationDependencyAssembler: DependencyAssembler { fatalError() } + guard let isAppleLoginedUseCase = DIContainer.shared.resolve(type: IsAppleLoginType.self) else { + BeforeGoingLogger.error(BeforeGoingError.diContainerError) + fatalError() + } + guard let updatePushNoticeUseCase = DIContainer.shared.resolve(type: UpdatePushNoticeType.self) else { BeforeGoingLogger.error(BeforeGoingError.diContainerError) fatalError() @@ -133,7 +138,8 @@ struct PresentationDependencyAssembler: DependencyAssembler { DIContainer.shared.register( AgreeItemViewModel( - useCase: agreeTermsUseCase + sendAgreeUseCase: agreeTermsUseCase, + isAppleLoginedUseCase: isAppleLoginedUseCase ) ) DIContainer.shared.register( From 86aa93bfc6c7ee9fe706abfab9c1a1aa83586a35 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Wed, 7 Jan 2026 09:58:35 +0900 Subject: [PATCH 05/25] =?UTF-8?q?refactor:=20#99=20=EC=95=A0=ED=94=8C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EC=9D=B4=EB=A6=84=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Persistence/Service/UserDefaultsKey.swift | 1 + .../Data/Repository/AuthRepository.swift | 20 ++++++++++- .../Data/Repository/MemberRepository.swift | 34 +++++++++++-------- .../Domain/Interface/AuthInterface.swift | 2 +- .../Domain/UseCase/Auth/LoginUseCase.swift | 12 ++++--- .../Approach/ViewModel/LoginViewModel.swift | 15 +++++++- 6 files changed, 63 insertions(+), 21 deletions(-) diff --git a/BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift b/BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift index 81e1b8c8..5a2a4a8d 100644 --- a/BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift +++ b/BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift @@ -12,6 +12,7 @@ enum UserDefaultsKey: String, CaseIterable { case isAppleCompletedOnboarding case kakaoMemberName case appleMemberName + case appleCrendentialName case provider case lastProvider } diff --git a/BeforeGoing/Data/Repository/AuthRepository.swift b/BeforeGoing/Data/Repository/AuthRepository.swift index 413dcbd0..7207bf62 100644 --- a/BeforeGoing/Data/Repository/AuthRepository.swift +++ b/BeforeGoing/Data/Repository/AuthRepository.swift @@ -51,7 +51,7 @@ struct AuthRepository: AuthInterface { return try await requestLogin(provider: provider, idToken: idToken) } - func requestLogin(provider: Provider, idToken: String) async throws -> Bool { + private func requestLogin(provider: Provider, idToken: String) async throws -> Bool { let requestDTO = loginRequestMapper.map((provider.rawValue, idToken)) let response = try await networkService.request( endPoint: AuthAPI.login(dto: requestDTO), @@ -63,6 +63,12 @@ struct AuthRepository: AuthInterface { return isCompletedOnboarding(provider: provider) } + func requestLogin(provider: Provider, idToken: String, name: String?) async throws -> Bool { + let isCompletedOnboarding = try await requestLogin(provider: provider, idToken: idToken) + saveMemberName(name) + return isCompletedOnboarding + } + func autoLogin() async throws -> Bool { guard let providerString: String = userDefaultsService.load(key: .provider), let provider = Provider(rawValue: providerString) else { @@ -138,6 +144,18 @@ struct AuthRepository: AuthInterface { let _ = userDefaultsService.save(lastLogin, key: .lastProvider) } + private func saveMemberName(_ name: String?) { + if let name, !name.isBlank { + let _ = userDefaultsService.save(name, key: .appleCrendentialName) + let _ = userDefaultsService.save(name, key: .appleMemberName) + return + } + + if let credentialName: String = userDefaultsService.load(key: .appleCrendentialName) { + let _ = userDefaultsService.save(credentialName, key: .appleMemberName) + } + } + private var isTokenExists: Bool { if let accessToken = keyChainService.load(key: .accessToken), let refreshToken = keyChainService.load(key: .refreshToken), diff --git a/BeforeGoing/Data/Repository/MemberRepository.swift b/BeforeGoing/Data/Repository/MemberRepository.swift index 44d406c6..fbf2be2e 100644 --- a/BeforeGoing/Data/Repository/MemberRepository.swift +++ b/BeforeGoing/Data/Repository/MemberRepository.swift @@ -75,7 +75,7 @@ struct MemberRepository: MemberInterface { func withdrawMember() async throws { guard let accessToken = keyChainService.load(key: .accessToken), - let provider: String = userDefaultsService.load(key: .provider) else { + let provider: String = userDefaultsService.load(key: .provider) else { BeforeGoingLogger.error(BeforeGoingError.accessTokenMissing) return } @@ -84,6 +84,7 @@ struct MemberRepository: MemberInterface { try await networkService.request(endPoint: MemberAPI.withdraw(accessToken: accessToken)) removeMemberInfo(provider: provider) removeNotifications() + removeNotifications() } catch { throw BeforeGoingError.withdrawFailed } @@ -95,7 +96,7 @@ struct MemberRepository: MemberInterface { return false } -let key: UserDefaultsKey = (provider == .apple) ? .isAppleCompletedOnboarding : .isKakaoCompletedOnboarding + let key: UserDefaultsKey = (provider == .apple) ? .isAppleCompletedOnboarding : .isKakaoCompletedOnboarding let isSaved = userDefaultsService.save(true, key: key) return isSaved } @@ -118,20 +119,25 @@ let key: UserDefaultsKey = (provider == .apple) ? .isAppleCompletedOnboarding : private func removeUserDefaultsInfo(provider: String) { let excludedKeys: [UserDefaultsKey?] = { switch provider { - case Provider.kakao.rawValue: return [ - .appleMemberName, - .isAppleCompletedOnboarding, - .isAppleCompletedAgreeTerms - ] - case Provider.apple.rawValue: return [ - .kakaoMemberName, - .isKakaoCompletedOnboarding, - .isKakaoCompletedAgreeTerms - ] -default: return [] + case Provider.kakao.rawValue: + return [ + .appleCrendentialName, + .appleMemberName, + .isAppleCompletedOnboarding, + .isAppleCompletedAgreeTerms + ] + case Provider.apple.rawValue: + return [ + .appleCrendentialName, + .kakaoMemberName, + .isKakaoCompletedOnboarding, + .isKakaoCompletedAgreeTerms + ] + default: + return [.appleCrendentialName] } }() - + UserDefaultsKey.allCases .filter { !excludedKeys.contains($0) } .forEach { let _ = userDefaultsService.delete(key: $0) } diff --git a/BeforeGoing/Domain/Interface/AuthInterface.swift b/BeforeGoing/Domain/Interface/AuthInterface.swift index 6f2c5b6a..cf6f128c 100644 --- a/BeforeGoing/Domain/Interface/AuthInterface.swift +++ b/BeforeGoing/Domain/Interface/AuthInterface.swift @@ -9,7 +9,7 @@ protocol AuthInterface { func requestNonce(provider: Provider) async throws -> NonceEntity func requestLogin(provider: Provider) async throws -> Bool - func requestLogin(provider: Provider, idToken: String) async throws -> Bool + func requestLogin(provider: Provider, idToken: String, name: String?) async throws -> Bool func autoLogin() async throws -> Bool func getLastLogin() -> Provider? func logout() async throws diff --git a/BeforeGoing/Domain/UseCase/Auth/LoginUseCase.swift b/BeforeGoing/Domain/UseCase/Auth/LoginUseCase.swift index 028ca6b7..a6d6dbf2 100644 --- a/BeforeGoing/Domain/UseCase/Auth/LoginUseCase.swift +++ b/BeforeGoing/Domain/UseCase/Auth/LoginUseCase.swift @@ -8,7 +8,7 @@ protocol LoginType { func login(provider: Provider) async throws -> Bool - func login(provider: Provider, idToken: String) async throws -> Bool + func login(provider: Provider, idToken: String, name: String?) async throws -> Bool func requestNonce(provider: Provider) async throws -> String } @@ -24,8 +24,12 @@ struct LoginUseCase: LoginType { try await repository.requestLogin(provider: provider) } - func login(provider: Provider, idToken: String) async throws -> Bool { - return try await repository.requestLogin(provider: provider, idToken: idToken) + func login(provider: Provider, idToken: String, name: String?) async throws -> Bool { + return try await repository.requestLogin( + provider: provider, + idToken: idToken, + name: name + ) } func requestNonce(provider: Provider) async throws -> String { @@ -39,7 +43,7 @@ struct MockLoginUseCase: LoginType { return true } - func login(provider: Provider, idToken: String) -> Bool { + func login(provider: Provider, idToken: String, name: String?) -> Bool { return true } diff --git a/BeforeGoing/Presentation/Feature/Approach/ViewModel/LoginViewModel.swift b/BeforeGoing/Presentation/Feature/Approach/ViewModel/LoginViewModel.swift index 96d4531d..efba1916 100644 --- a/BeforeGoing/Presentation/Feature/Approach/ViewModel/LoginViewModel.swift +++ b/BeforeGoing/Presentation/Feature/Approach/ViewModel/LoginViewModel.swift @@ -11,6 +11,12 @@ import AuthenticationServices final class LoginViewModel: NSObject, ViewModeling { + private let personNameFormatter: PersonNameComponentsFormatter = { + let formatter = PersonNameComponentsFormatter() + formatter.style = .default + return formatter + }() + private let autoLoginUseCase: AutoLoginType private let loginUseCase: LoginType private let getLastLoginUseCase: GetLastLoginType @@ -67,6 +73,7 @@ final class LoginViewModel: NSObject, ViewModeling { case .appleLoginDidTap: let provider = ASAuthorizationAppleIDProvider() let request = provider.createRequest() + request.requestedScopes = [.fullName] let nonce = try await loginUseCase.requestNonce(provider: .apple) request.nonce = nonce @@ -94,10 +101,16 @@ extension LoginViewModel: ASAuthorizationControllerDelegate { let idToken = String(data: identityTokenData, encoding: .utf8) else { return } + + let name = credential.fullName.flatMap { personNameFormatter.string(from: $0) } Task { do { - let isMemberRegistered = try await loginUseCase.login(provider: .apple, idToken: idToken) + let isMemberRegistered = try await loginUseCase.login( + provider: .apple, + idToken: idToken, + name: name + ) onAppleLoginPerformed?(isMemberRegistered) } catch (let error) { BeforeGoingLogger.error(error) From 79925172b1b19fb4181e0370d6ff44226a4fed38 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Wed, 7 Jan 2026 14:15:12 +0900 Subject: [PATCH 06/25] =?UTF-8?q?refactor:=20#99=20scenarioNames=EA=B0=80?= =?UTF-8?q?=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=EC=97=90=EB=8F=84=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=B2=98=EB=A6=AC=EA=B0=80=20=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/SettingScenarioViewController.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift index a9a15b1b..f26b66cb 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift @@ -300,8 +300,7 @@ extension SettingScenarioViewController: ToastPresentable { Task { do { - guard let scenarioNames, - !scenarioNames.contains(scenarioName) else { + if let scenarioNames, scenarioNames.contains(scenarioName) { self.presentToastMessage(type: .duplicateScenario) return } From b7ecbc93a8fd703f6db45492e543f99bcf27e7a0 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Wed, 7 Jan 2026 14:15:32 +0900 Subject: [PATCH 07/25] =?UTF-8?q?feat:=20#99=20weatherKit=20=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=20=EB=B0=8F=20=EB=A7=81=ED=81=AC=20=EC=82=BD=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/ScenarioEmptyView.swift | 13 +++++-- .../Common/WeatherKitButtonView.swift | 34 +++++++++++++++++++ .../Presentation/Enum/ExternalLink.swift | 1 + .../Home/View/UserScenarioModalView.swift | 9 +++-- .../ViewController/HomeViewController.swift | 17 +++++++++- 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 BeforeGoing/Presentation/Common/WeatherKitButtonView.swift diff --git a/BeforeGoing/Presentation/Common/ScenarioEmptyView.swift b/BeforeGoing/Presentation/Common/ScenarioEmptyView.swift index c0468437..2bea842b 100644 --- a/BeforeGoing/Presentation/Common/ScenarioEmptyView.swift +++ b/BeforeGoing/Presentation/Common/ScenarioEmptyView.swift @@ -18,6 +18,9 @@ final class ScenarioEmptyView: BaseView { state: .addScenarioButton, title: "+ 시나리오 추가" ) + private(set) var weatherKitButtonView = WeatherKitButtonView( + frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 52.adjustedH) + ) init(type: ScenarioEmptyViewType) { self.type = type @@ -55,7 +58,10 @@ final class ScenarioEmptyView: BaseView { subtitleLabel ) if type.isHome { - addSubview(moveButton) + addSubviews( + moveButton, + weatherKitButtonView + ) } } @@ -81,7 +87,10 @@ final class ScenarioEmptyView: BaseView { $0.centerX.equalToSuperview() $0.width.equalTo(139.adjustedW) $0.height.equalTo(48.adjustedH) - $0.bottom.equalToSuperview() + } + weatherKitButtonView.snp.makeConstraints { + $0.horizontalEdges.equalToSuperview() + $0.bottom.equalTo(safeAreaLayoutGuide.snp.bottom) } } } diff --git a/BeforeGoing/Presentation/Common/WeatherKitButtonView.swift b/BeforeGoing/Presentation/Common/WeatherKitButtonView.swift new file mode 100644 index 00000000..447bb3c5 --- /dev/null +++ b/BeforeGoing/Presentation/Common/WeatherKitButtonView.swift @@ -0,0 +1,34 @@ +// +// WeatherKitButtonView.swift +// BeforeGoing +// +// Created by APPLE on 1/7/26. +// + +import UIKit + +final class WeatherKitButtonView: BaseView { + + private(set) var weatherKitButton = UIButton() + + override func setStyle() { + weatherKitButton.do { + $0.setTitle(" Weather", for: .normal) + $0.setTitleColor(.gray400, for: .normal) + $0.titleLabel?.font = .custom(.bodySMMedium) + $0.titleLabel?.textAlignment = .center + } + } + + override func setUI() { + addSubview(weatherKitButton) + } + + override func setLayout() { + weatherKitButton.snp.makeConstraints { + $0.verticalEdges.equalToSuperview().inset(20.adjustedH) + $0.centerX.equalToSuperview() + $0.width.equalTo(80.adjustedW) + } + } +} diff --git a/BeforeGoing/Presentation/Enum/ExternalLink.swift b/BeforeGoing/Presentation/Enum/ExternalLink.swift index 0f77c5f2..9738fbb6 100644 --- a/BeforeGoing/Presentation/Enum/ExternalLink.swift +++ b/BeforeGoing/Presentation/Enum/ExternalLink.swift @@ -14,6 +14,7 @@ enum ExternalLink: String { case privacy = "https://fluffy-nectarine-129.notion.site/2824ff02f66080b290c6ce933b8759d7?source=copy_link" case term = "https://fluffy-nectarine-129.notion.site/2824ff02f6608029a52ed13a25059f97?source=copy_link" case notice = "https://fluffy-nectarine-129.notion.site/2a04ff02f6608083af96e52f216f1c88" + case weatherLegal = "https://developer.apple.com/weatherkit/data-source-attribution/" func openURL(for rootViewController: UIViewController) { guard let url = URL(string: self.rawValue) else { diff --git a/BeforeGoing/Presentation/Feature/Home/View/UserScenarioModalView.swift b/BeforeGoing/Presentation/Feature/Home/View/UserScenarioModalView.swift index 924fdd51..acd4c64b 100644 --- a/BeforeGoing/Presentation/Feature/Home/View/UserScenarioModalView.swift +++ b/BeforeGoing/Presentation/Feature/Home/View/UserScenarioModalView.swift @@ -24,6 +24,9 @@ final class UserScenarioModalView: BaseView { private(set) var addTaskButton = UIButton() private(set) var deleteTaskButton = UIButton() private(set) var listTableView = UITableView() + private(set) lazy var weatherKitButtonView = WeatherKitButtonView( + frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 52.adjustedH) + ) init() { super.init(frame: .zero) @@ -57,6 +60,7 @@ final class UserScenarioModalView: BaseView { } listTableView.do { $0.separatorStyle = .none + $0.tableFooterView = weatherKitButtonView } } @@ -188,13 +192,14 @@ extension UserScenarioModalView { listTableView, taskTextField, addTaskButton, - deleteTaskButton + deleteTaskButton, + weatherKitButtonView ].forEach { $0.removeFromSuperview() } addSubview(emptyView) emptyView.snp.makeConstraints { $0.top.equalToSuperview().inset(80.adjustedH) $0.horizontalEdges.equalToSuperview().inset(20.adjustedW) - $0.height.equalTo(285.adjustedH) + $0.bottom.equalToSuperview().inset(20.adjustedH) } } diff --git a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift index dae8243e..dbce7851 100644 --- a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -50,7 +50,7 @@ final class HomeViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - + setLocationManager() requestDate() updateWeatherInformation(date: DateUtil.getCurrentDate()) @@ -94,6 +94,16 @@ final class HomeViewController: BaseViewController { action: #selector(moveButtonDidTap), for: .touchUpInside ) + [ + rootView.modalView.weatherKitButtonView.weatherKitButton, + rootView.modalView.emptyView.weatherKitButtonView.weatherKitButton + ].forEach { + $0.addTarget( + self, + action: #selector(weatherKitButtonDidTap), + for: .touchUpInside + ) + } } override func setDelegate() { @@ -410,6 +420,11 @@ extension HomeViewController: ToastPresentable { pushManageScenario(navigationController: navigationController) } + @objc + private func weatherKitButtonDidTap() { + ExternalLink.weatherLegal.openURL(for: self) + } + private func initSelectedDate(calendarViewController: CalendarViewController) { if let homeDateString = self.homeDate, let date = DateUtil.toDate(dateString: homeDateString) { From 1c64b46bbc2faf35d3e9c41822e55d53e2e19794 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Wed, 7 Jan 2026 14:48:01 +0900 Subject: [PATCH 08/25] =?UTF-8?q?refactor:=20#99=20=ED=99=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=EB=90=9C=20=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EC=9D=84=20=EB=95=8C=20=EB=B7=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Common/ScenarioEmptyView.swift | 14 ++++++++---- .../Common/WeatherKitButtonView.swift | 3 ++- .../Home/View/UserScenarioModalView.swift | 19 +++++++++++----- .../Icon/exclamation.imageset/Contents.json | 21 ++++++++++++++++++ .../Icon/exclamation.imageset/exclamation.png | Bin 0 -> 1419 bytes 5 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 BeforeGoing/Resource/Assets.xcassets/Icon/exclamation.imageset/Contents.json create mode 100644 BeforeGoing/Resource/Assets.xcassets/Icon/exclamation.imageset/exclamation.png diff --git a/BeforeGoing/Presentation/Common/ScenarioEmptyView.swift b/BeforeGoing/Presentation/Common/ScenarioEmptyView.swift index 2bea842b..eee513bf 100644 --- a/BeforeGoing/Presentation/Common/ScenarioEmptyView.swift +++ b/BeforeGoing/Presentation/Common/ScenarioEmptyView.swift @@ -36,7 +36,7 @@ final class ScenarioEmptyView: BaseView { override func setStyle() { worryImageView.do { - $0.image = .starWorry + $0.image = type.isHome ? .exclamation : .starWorry $0.contentMode = .scaleAspectFill } titleLabel.do { @@ -67,12 +67,17 @@ final class ScenarioEmptyView: BaseView { override func setLayout() { worryImageView.snp.makeConstraints { - $0.top.equalToSuperview() + let topInset = type.isHome ? 20.adjustedH : 0 + $0.top.equalToSuperview().inset(topInset) + $0.centerX.equalToSuperview() - $0.size.equalTo(200.adjustedW) + + let imageSize = type.isHome ? 48.adjustedW : 200.adjustedW + $0.size.equalTo(imageSize) } titleLabel.snp.makeConstraints { - $0.top.equalTo(worryImageView.snp.bottom).offset(10.adjustedH) + let topOffset = type.isHome ? 20.adjustedH : 10.adjustedH + $0.top.equalTo(worryImageView.snp.bottom).offset(topOffset) $0.centerX.equalToSuperview() $0.height.equalTo(26.adjustedH) } @@ -89,6 +94,7 @@ final class ScenarioEmptyView: BaseView { $0.height.equalTo(48.adjustedH) } weatherKitButtonView.snp.makeConstraints { + $0.top.equalTo(moveButton.snp.bottom).offset(110.adjustedH) $0.horizontalEdges.equalToSuperview() $0.bottom.equalTo(safeAreaLayoutGuide.snp.bottom) } diff --git a/BeforeGoing/Presentation/Common/WeatherKitButtonView.swift b/BeforeGoing/Presentation/Common/WeatherKitButtonView.swift index 447bb3c5..ca661c9f 100644 --- a/BeforeGoing/Presentation/Common/WeatherKitButtonView.swift +++ b/BeforeGoing/Presentation/Common/WeatherKitButtonView.swift @@ -26,7 +26,8 @@ final class WeatherKitButtonView: BaseView { override func setLayout() { weatherKitButton.snp.makeConstraints { - $0.verticalEdges.equalToSuperview().inset(20.adjustedH) + $0.top.equalToSuperview().inset(20.adjustedH) + $0.bottom.equalToSuperview().inset(30.adjustedH) $0.centerX.equalToSuperview() $0.width.equalTo(80.adjustedW) } diff --git a/BeforeGoing/Presentation/Feature/Home/View/UserScenarioModalView.swift b/BeforeGoing/Presentation/Feature/Home/View/UserScenarioModalView.swift index acd4c64b..89e53c4e 100644 --- a/BeforeGoing/Presentation/Feature/Home/View/UserScenarioModalView.swift +++ b/BeforeGoing/Presentation/Feature/Home/View/UserScenarioModalView.swift @@ -25,7 +25,7 @@ final class UserScenarioModalView: BaseView { private(set) var deleteTaskButton = UIButton() private(set) var listTableView = UITableView() private(set) lazy var weatherKitButtonView = WeatherKitButtonView( - frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 52.adjustedH) + frame: CGRect(x: 0, y: 0, width: 80.adjustedW, height: 52.adjustedH) ) init() { @@ -194,18 +194,25 @@ extension UserScenarioModalView { addTaskButton, deleteTaskButton, weatherKitButtonView - ].forEach { $0.removeFromSuperview() } + ].forEach { $0.isHidden = true } addSubview(emptyView) + emptyView.isHidden = false emptyView.snp.makeConstraints { $0.top.equalToSuperview().inset(80.adjustedH) $0.horizontalEdges.equalToSuperview().inset(20.adjustedW) - $0.bottom.equalToSuperview().inset(20.adjustedH) + $0.bottom.equalToSuperview() } } func replaceModalView() { - emptyView.removeFromSuperview() - setUI() - setLayout() + emptyView.isHidden = true + [ + headerView, + listTableView, + taskTextField, + addTaskButton, + deleteTaskButton, + weatherKitButtonView + ].forEach { $0.isHidden = false } } } diff --git a/BeforeGoing/Resource/Assets.xcassets/Icon/exclamation.imageset/Contents.json b/BeforeGoing/Resource/Assets.xcassets/Icon/exclamation.imageset/Contents.json new file mode 100644 index 00000000..129e2a45 --- /dev/null +++ b/BeforeGoing/Resource/Assets.xcassets/Icon/exclamation.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "exclamation.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeforeGoing/Resource/Assets.xcassets/Icon/exclamation.imageset/exclamation.png b/BeforeGoing/Resource/Assets.xcassets/Icon/exclamation.imageset/exclamation.png new file mode 100644 index 0000000000000000000000000000000000000000..65a965ff67df8a968523f624cf507d96f7739e37 GIT binary patch literal 1419 zcmV;61$6p}P)<6~Oe7S6Vntcl zNW_>BTR6VVjP1GfOm}r1PED8vf)%NjY$=~*$?hIa*Qs;+>oo9&{{b`MRItoi7OVbo zjpdq06!O6Buv`30dlIYAga03MI537N^XUQq1}qPDm}(bL2n(iGRJ~g8Q|psj4Iv)Y zXa8Qjx99qgMYq5wFVBQY`J&Y{jg+gTafunfX{(&sV%r04@8?1*PtMCwAy z16&@Iv@*6^a~(i_q~cJCINLE=Cy25PcPN1;{2Oprnw63CRiOf_?<7|T;bIjgBdyw~ zPk1Y#jvNdii-|LR76TY|QFh;jilkz|s;)r8>bL zaNLH~Vc3(-dri{ydr9U$6zp6Y&=FSl3-Cxj2CS|I982)P#*)NVo-%2fUPQ)LuynS2 zva68e`t`J9;&D>pjv}teYNvFdx01l@%UvEcJ5S_MM&&fNEM>CX;w+i6&=F_+B(vr+ zQ1vDuhWIa|Zg-z@5$(1iP1|fSCg;eiQR==}7vh`2GgEjXpAfnV(fwWGRJ}#Yq#`3m zb&}pfV*21pXw50#L#>=bND?{RH9bxOm#;=; z9TBBHOu^eZU`T$0t)QMFp|;Gi;=t$;M507D;Y~se$;SX!O4rsh%${i^T<+>3Z6YnJ zxw-AgXA^VeRfU8_jlK4mq;uAHQkvV1?T_br@JxONRIi8>YNXZB^biLzifQKaATuOC z13srdM&C}_dubmdnRMP_h#&mtK)$STgu4#{JmbP(tnQNg)Vv!QF4mpC9`Miz?;g)@ zH{AOGVR8(mM!kWGDwJmrcA6VMFQkLXg(q^6(|2X3tKIhaSbS7%)ekO(I(T1oj?KpT zg#aJQ=n?+H$gc-nh`2+0aBZYGS{OIIbcPkbgVgp!P8_I+8cE8#xG`FXC;WRLUeagH zSjuQeOy&Z!y(~u5L{fiBDVxD)(dC0Da!{j%gwGo*Mb^Uo5kC1^R}Q Z#9t!*ThOlQ?(YBq002ovPDHLkV1oAcoL2w< literal 0 HcmV?d00001 From df7214394746de8195361aee20f8cfd1d0e984b3 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Wed, 7 Jan 2026 14:48:18 +0900 Subject: [PATCH 09/25] =?UTF-8?q?refactor:=20#99=20weatherKitButtonView?= =?UTF-8?q?=EC=97=90=20=ED=83=AD=20=EC=A0=9C=EC=8A=A4=EC=B2=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Home/ViewController/HomeViewController.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift index dbce7851..fce56a71 100644 --- a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -104,6 +104,17 @@ final class HomeViewController: BaseViewController { for: .touchUpInside ) } + [ + rootView.modalView.weatherKitButtonView, + rootView.modalView.emptyView.weatherKitButtonView + ].forEach { + let tapGesture = UITapGestureRecognizer( + target: self, + action: #selector(weatherKitButtonDidTap) + ) + $0.isUserInteractionEnabled = true + $0.addGestureRecognizer(tapGesture) + } } override func setDelegate() { From 1c36676a93ca0d78794d52d02453920f9036bd63 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Sun, 11 Jan 2026 11:07:41 +0900 Subject: [PATCH 10/25] =?UTF-8?q?refactor:=20#99=20LoginResponseDTO=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Model/Auth/Response/LoginResponseDTO.swift | 4 +++- BeforeGoing/Data/Repository/AuthRepository.swift | 7 ++++--- BeforeGoing/Data/Repository/MemberRepository.swift | 1 - BeforeGoing/Domain/Entity/LoginEntity.swift | 1 + 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/BeforeGoing/Data/Model/Auth/Response/LoginResponseDTO.swift b/BeforeGoing/Data/Model/Auth/Response/LoginResponseDTO.swift index 4cecfa49..5b384678 100644 --- a/BeforeGoing/Data/Model/Auth/Response/LoginResponseDTO.swift +++ b/BeforeGoing/Data/Model/Auth/Response/LoginResponseDTO.swift @@ -4,6 +4,7 @@ struct LoginResponseDTO: Decodable { let accessTokenExpiresIn: Int let refreshToken: String let refreshTokenExpiresIn: Int + let isNewMember: Bool } extension LoginResponseDTO { @@ -13,7 +14,8 @@ extension LoginResponseDTO { accessToken: accessToken, accessTokenExpiresIn: accessTokenExpiresIn, refreshToken: refreshToken, - refreshTokenExpiresIn: refreshTokenExpiresIn + refreshTokenExpiresIn: refreshTokenExpiresIn, + isNewMember: isNewMember ) } } diff --git a/BeforeGoing/Data/Repository/AuthRepository.swift b/BeforeGoing/Data/Repository/AuthRepository.swift index 7207bf62..a75e867b 100644 --- a/BeforeGoing/Data/Repository/AuthRepository.swift +++ b/BeforeGoing/Data/Repository/AuthRepository.swift @@ -60,13 +60,14 @@ struct AuthRepository: AuthInterface { saveKeyChain(response: response) saveProvider(provider) - return isCompletedOnboarding(provider: provider) + let isCompletedJoin = !response.isNewMember && isCompletedOnboarding(provider: provider) + return isCompletedJoin } func requestLogin(provider: Provider, idToken: String, name: String?) async throws -> Bool { - let isCompletedOnboarding = try await requestLogin(provider: provider, idToken: idToken) + let isRegisterdMember = try await requestLogin(provider: provider, idToken: idToken) saveMemberName(name) - return isCompletedOnboarding + return isRegisterdMember } func autoLogin() async throws -> Bool { diff --git a/BeforeGoing/Data/Repository/MemberRepository.swift b/BeforeGoing/Data/Repository/MemberRepository.swift index fbf2be2e..0fdff674 100644 --- a/BeforeGoing/Data/Repository/MemberRepository.swift +++ b/BeforeGoing/Data/Repository/MemberRepository.swift @@ -84,7 +84,6 @@ struct MemberRepository: MemberInterface { try await networkService.request(endPoint: MemberAPI.withdraw(accessToken: accessToken)) removeMemberInfo(provider: provider) removeNotifications() - removeNotifications() } catch { throw BeforeGoingError.withdrawFailed } diff --git a/BeforeGoing/Domain/Entity/LoginEntity.swift b/BeforeGoing/Domain/Entity/LoginEntity.swift index 02350b92..5c0cc153 100644 --- a/BeforeGoing/Domain/Entity/LoginEntity.swift +++ b/BeforeGoing/Domain/Entity/LoginEntity.swift @@ -11,4 +11,5 @@ struct LoginEntity { let accessTokenExpiresIn: Int let refreshToken: String let refreshTokenExpiresIn: Int + let isNewMember: Bool } From 908f1418e88ee59e069fddddb70e503bf7dce9a7 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 12 Jan 2026 11:37:53 +0900 Subject: [PATCH 11/25] =?UTF-8?q?refactor:=20#99=20=EC=B9=B4=EC=B9=B4?= =?UTF-8?q?=EC=98=A4=20SDK=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=ED=95=A8=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BeforeGoing/Application/AppDelegate.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/BeforeGoing/Application/AppDelegate.swift b/BeforeGoing/Application/AppDelegate.swift index c4b0ffb1..f6ec062c 100644 --- a/BeforeGoing/Application/AppDelegate.swift +++ b/BeforeGoing/Application/AppDelegate.swift @@ -16,12 +16,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { DIContainer.shared.injectDependency() + initKakaoSDK() - if let appKey = Bundle.main.object(forInfoDictionaryKey: "KAKAO_NATIVE_APP_KEY") as? String { - KakaoSDK.initSDK(appKey: appKey) - } else { - fatalError("카카오 네이티브 앱 키 없음") - } return true } @@ -49,3 +45,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) {} } +extension AppDelegate { + + func initKakaoSDK() { + if let appKey = Bundle.main.object(forInfoDictionaryKey: "KAKAO_NATIVE_APP_KEY") as? String { + KakaoSDK.initSDK(appKey: appKey) + } else { + fatalError("카카오 네이티브 앱 키 없음") + } + } +} From f2ddf4b8166264efb5bc416b665056ae6fc9bb16 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Mon, 12 Jan 2026 18:02:24 +0900 Subject: [PATCH 12/25] =?UTF-8?q?refactor:=20#99=20=EB=8F=99=EC=8B=9C=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9=20=EC=A0=81=EC=9A=A9=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20=EB=82=A0=EC=94=A8=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EC=86=8D=EB=8F=84=20=EC=A6=9D=EC=A7=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift index fce56a71..d7236524 100644 --- a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -18,7 +18,7 @@ final class HomeViewController: BaseViewController { private let locationManager = CLLocationManager() private var homeDate: String? - private var memberName: String? + private var memberName: String = "워리" private var scenarioTitle: String? init( @@ -52,8 +52,14 @@ final class HomeViewController: BaseViewController { super.viewDidLoad() setLocationManager() - requestDate() - updateWeatherInformation(date: DateUtil.getCurrentDate()) + + Task { + async let dateResult: () = requestDate() + async let updateWeatherResult: () = updateWeatherInformation(date: DateUtil.getCurrentDate()) + + let _ = try await dateResult + let _ = try await updateWeatherResult + } } override func touchesBegan(_ touches: Set, with event: UIEvent?) { @@ -126,26 +132,24 @@ final class HomeViewController: BaseViewController { } } - private func requestDate() { - Task { - do { - guard let result = try await homeViewModel.action( - input: .requestDate - ) as? HomeViewModel.DateOutput, - let monthAndDay = DateUtil.toMonthAndDay(date: result.date) - else { - return - } - self.homeDate = result.date - rootView.headerView.updateDateUI(date: result.date) - rootView.modalView.updateTaskField( - isEnabled: true, - text: "\(monthAndDay)의 미션을 추가해요" - ) - } catch (let error) { - self.handleError(error) - BeforeGoingLogger.error(error) + private func requestDate() async throws { + do { + guard let result = try await homeViewModel.action( + input: .requestDate + ) as? HomeViewModel.DateOutput, + let monthAndDay = DateUtil.toMonthAndDay(date: result.date) + else { + return } + self.homeDate = result.date + rootView.headerView.updateDateUI(date: result.date) + rootView.modalView.updateTaskField( + isEnabled: true, + text: "\(monthAndDay)의 미션을 추가해요" + ) + } catch (let error) { + self.handleError(error) + BeforeGoingLogger.error(error) } } @@ -215,32 +219,26 @@ final class HomeViewController: BaseViewController { } } - private func updateWeatherInformation(date: Date) { + private func updateWeatherInformation(date: Date) async throws { guard let latitude = locationManager.location?.coordinate.latitude, let longitude = locationManager.location?.coordinate.longitude else { return } - Task { - try await getMemberName() - - guard let memberName else { - return - } - - guard let result = try await homeViewModel.action( - input: .requestWeather(date: date, memberName: memberName, latitude: latitude, longitude: longitude) - ) as? HomeViewModel.WeatherOutput else { - return - } - - switch result.weatherResult { - case .success(let weatherInformation): - self.rootView.headerView.updateWeatherUI(information: weatherInformation) - case .failure(let error): - self.handleError(error) - BeforeGoingLogger.error(error) - } + try await getMemberName() + + guard let result = try await homeViewModel.action( + input: .requestWeather(date: date, memberName: memberName, latitude: latitude, longitude: longitude) + ) as? HomeViewModel.WeatherOutput else { + return + } + + switch result.weatherResult { + case .success(let weatherInformation): + self.rootView.headerView.updateWeatherUI(information: weatherInformation) + case .failure(let error): + self.handleError(error) + BeforeGoingLogger.error(error) } } @@ -312,7 +310,9 @@ extension HomeViewController: ToastPresentable { monthAndDay: monthAndDay ) - updateWeatherInformation(date: date) + Task { + try await self.updateWeatherInformation(date: date) + } } calendarViewController.onDismiss = { [weak self] in guard let homeDate = self?.homeDate, From 0dd1c5ba6453662b9281b6b97e3c8b1a1bf4f126 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 13:53:24 +0900 Subject: [PATCH 13/25] =?UTF-8?q?refactor:=20#99=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/LoginViewController.swift | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/BeforeGoing/Presentation/Feature/Approach/ViewController/LoginViewController.swift b/BeforeGoing/Presentation/Feature/Approach/ViewController/LoginViewController.swift index 2e32f163..9e3e61aa 100644 --- a/BeforeGoing/Presentation/Feature/Approach/ViewController/LoginViewController.swift +++ b/BeforeGoing/Presentation/Feature/Approach/ViewController/LoginViewController.swift @@ -33,7 +33,7 @@ final class LoginViewController: BaseViewController { do { if let output = try await viewModel.action(input: .viewDidLoad) as? LoginViewModel.AutoLoginOutput, output.isSucceed { - moveHome() + moveByNotification() } } catch { BeforeGoingLogger.error(BeforeGoingError.autoLoginFailed) @@ -93,7 +93,7 @@ extension LoginViewController: NetworkRequestable, NetworkRequestErrorHandler { ) as? LoginViewModel.SocialLoginOutput else { return } - output.isRegisteredMember ? moveHome() : moveTerms() + output.isRegisteredMember ? moveByNotification() : moveTerms() } catch (let error) { self.handleError(error) BeforeGoingLogger.error(BeforeGoingError.loginFailed) @@ -107,7 +107,7 @@ extension LoginViewController: NetworkRequestable, NetworkRequestErrorHandler { do { let _ = try await viewModel.action(input: .appleLoginDidTap) viewModel.onAppleLoginPerformed = { [weak self] isMemberRegistered in - isMemberRegistered ? self?.moveHome() : self?.moveTerms() + isMemberRegistered ? self?.moveByNotification() : self?.moveTerms() } } catch (let error) { self.handleError(error) @@ -116,7 +116,7 @@ extension LoginViewController: NetworkRequestable, NetworkRequestErrorHandler { } } - private func moveHome() { + private func moveByNotification() { if let request = AuthManager.shared.pendingNotificationRequest { AuthManager.shared.pendingNotificationRequest = nil @@ -126,24 +126,26 @@ extension LoginViewController: NetworkRequestable, NetworkRequestErrorHandler { switch notificationIdentifier { case .pushNotice : - ViewControllerUtil.replaceRootViewController( - to: BottomNavigationViewController(scenarioTitle: request.content.title) + replaceViewController( + to: BottomNavigationViewController( + scenarioTitle: request.content.title + ) ) - case .callNotice(let sequence): - ViewControllerUtil.replaceRootViewController( + replaceViewController( to: NotificationViewController( notificationViewType: .init(sequence: sequence), content: request.content, identifier: notificationIdentifier.identifier ) ) - case .terminate: - ViewControllerUtil.replaceRootViewController(to: BottomNavigationViewController()) + replaceViewController(to: BottomNavigationViewController()) } + return } + let viewController = BottomNavigationViewController() ViewControllerUtil.replaceRootViewController(to: viewController) } @@ -153,4 +155,8 @@ extension LoginViewController: NetworkRequestable, NetworkRequestErrorHandler { viewController.navigationItem.hidesBackButton = true self.navigationController?.pushViewController(viewController, animated: true) } + + private func replaceViewController(to viewController: UIViewController) { + ViewControllerUtil.replaceRootViewController(to: viewController) + } } From b340462903e40bafc1b4703d5fe288615bc4d52b Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 13:59:45 +0900 Subject: [PATCH 14/25] =?UTF-8?q?refactor:=20#99=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EC=A0=81=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=A9=EB=B2=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit async let -> TaskGroup --- .../ViewController/HomeViewController.swift | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift index d7236524..b289c418 100644 --- a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -52,14 +52,7 @@ final class HomeViewController: BaseViewController { super.viewDidLoad() setLocationManager() - - Task { - async let dateResult: () = requestDate() - async let updateWeatherResult: () = updateWeatherInformation(date: DateUtil.getCurrentDate()) - - let _ = try await dateResult - let _ = try await updateWeatherResult - } + updateDateAndWeather() } override func touchesBegan(_ touches: Set, with event: UIEvent?) { @@ -132,6 +125,34 @@ final class HomeViewController: BaseViewController { } } + private func updateDateAndWeather() { + Task { + if #available(iOS 17.0, *) { + try await withThrowingDiscardingTaskGroup { group in + group.addTask { [weak self] in + try await self?.requestDate() + } + + group.addTask { [weak self] in + try await self?.updateWeatherInformation(date: DateUtil.getCurrentDate()) + } + } + } else { + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { [weak self] in + try await self?.requestDate() + } + + group.addTask { [weak self] in + try await self?.updateWeatherInformation(date: DateUtil.getCurrentDate()) + } + + try await group.waitForAll() + } + } + } + } + private func requestDate() async throws { do { guard let result = try await homeViewModel.action( From 72ef72c0f9fe094d46e5900f9ab39492ab481f2a Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 14:00:28 +0900 Subject: [PATCH 15/25] =?UTF-8?q?refactor:=20#99=20WeatherKit=20=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=20=ED=83=AD=20=EC=A0=9C=EC=8A=A4=EC=B2=98=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EB=A1=9C=EC=A7=81=EC=9D=84=20setGesture=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/HomeViewController.swift | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift index b289c418..3a19b827 100644 --- a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -103,17 +103,6 @@ final class HomeViewController: BaseViewController { for: .touchUpInside ) } - [ - rootView.modalView.weatherKitButtonView, - rootView.modalView.emptyView.weatherKitButtonView - ].forEach { - let tapGesture = UITapGestureRecognizer( - target: self, - action: #selector(weatherKitButtonDidTap) - ) - $0.isUserInteractionEnabled = true - $0.addGestureRecognizer(tapGesture) - } } override func setDelegate() { @@ -278,11 +267,23 @@ final class HomeViewController: BaseViewController { } private func setGesture() { - let tapGesture = UITapGestureRecognizer( + let calendarTapGesture = UITapGestureRecognizer( target: self, action: #selector(viewCalendarButtonDidTap) ) - rootView.headerView.dateStackView.addGestureRecognizer(tapGesture) + rootView.headerView.dateStackView.addGestureRecognizer(calendarTapGesture) + + [ + rootView.modalView.weatherKitButtonView, + rootView.modalView.emptyView.weatherKitButtonView + ].forEach { + let weatherKitLogoTapGesture = UITapGestureRecognizer( + target: self, + action: #selector(weatherKitButtonDidTap) + ) + $0.isUserInteractionEnabled = true + $0.addGestureRecognizer(weatherKitLogoTapGesture) + } } private func setGesture(scenarios: [ScenarioEntity]) { From 4c6c6c66c9cd2e991f2f073074c9986ebfdc4da2 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 14:05:53 +0900 Subject: [PATCH 16/25] =?UTF-8?q?refactor:=20#99=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Home/ViewController/HomeViewController.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift index 3a19b827..37a1d75d 100644 --- a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -51,7 +51,7 @@ final class HomeViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - setLocationManager() + locationManager.delegate = self updateDateAndWeather() } @@ -300,12 +300,6 @@ final class HomeViewController: BaseViewController { ) } } - - private func setLocationManager() { - locationManager.do { - $0.delegate = self - } - } } extension HomeViewController: ToastPresentable { From 8ea0a9774361d10d940e7bf556ab46e5371fac7e Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 14:06:24 +0900 Subject: [PATCH 17/25] =?UTF-8?q?refactor:=20UISwipeActionsConfiguration?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=A9=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Feature/Home/ViewController/HomeViewController.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift index 37a1d75d..91cbea54 100644 --- a/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift +++ b/BeforeGoing/Presentation/Feature/Home/ViewController/HomeViewController.swift @@ -700,8 +700,11 @@ extension HomeViewController: UITableViewDataSource { } private func createSwipeAction(deleteAction: UIContextualAction) -> UISwipeActionsConfiguration { - let config = UISwipeActionsConfiguration(actions: [deleteAction]) - config.performsFirstActionWithFullSwipe = false - return config + let swipeActionsConfig: UISwipeActionsConfiguration = { + let config = UISwipeActionsConfiguration(actions: [deleteAction]) + config.performsFirstActionWithFullSwipe = false + return config + }() + return swipeActionsConfig } } From 3164fa280c3b3adb5effe1414fc89ddf15bf5462 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 14:59:14 +0900 Subject: [PATCH 18/25] =?UTF-8?q?refactor:=20#99=20=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EA=B7=B8=20=EB=8F=99=EC=9E=91=20=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageScenarioViewController.swift | 4 ++-- .../MyScenarioViewController.swift | 4 ++-- .../SettingScenarioViewController.swift | 4 ++-- .../Presentation/Protocol/DragAction.swift | 18 ++++++++++++++++++ 4 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 BeforeGoing/Presentation/Protocol/DragAction.swift diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift index 405e0e72..b56db033 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift @@ -197,14 +197,14 @@ extension ManageScenarioViewController: UITableViewDataSource { } } -extension ManageScenarioViewController: UITableViewDragDelegate { +extension ManageScenarioViewController: UITableViewDragDelegate, DragAction { func tableView( _ tableView: UITableView, itemsForBeginning session: any UIDragSession, at indexPath: IndexPath ) -> [UIDragItem] { - return [UIDragItem(itemProvider: NSItemProvider())] + return makeDragItems() } } diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift index 13b5ac7d..68c26eaa 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift @@ -278,14 +278,14 @@ extension MyScenarioViewController: UITableViewDataSource { } } -extension MyScenarioViewController: UITableViewDragDelegate { +extension MyScenarioViewController: UITableViewDragDelegate, DragAction { func tableView( _ tableView: UITableView, itemsForBeginning session: any UIDragSession, at indexPath: IndexPath ) -> [UIDragItem] { - return [UIDragItem(itemProvider: NSItemProvider())] + return makeDragItems() } } diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift index f26b66cb..f9c91d27 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift @@ -469,10 +469,10 @@ extension SettingScenarioViewController: UITableViewDataSource { } } -extension SettingScenarioViewController: UITableViewDragDelegate { +extension SettingScenarioViewController: UITableViewDragDelegate, DragAction { func tableView(_ tableView: UITableView, itemsForBeginning session: any UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - return [UIDragItem(itemProvider: NSItemProvider())] + return makeDragItems() } } diff --git a/BeforeGoing/Presentation/Protocol/DragAction.swift b/BeforeGoing/Presentation/Protocol/DragAction.swift new file mode 100644 index 00000000..f1aadb1d --- /dev/null +++ b/BeforeGoing/Presentation/Protocol/DragAction.swift @@ -0,0 +1,18 @@ +// +// DragAction.swift +// BeforeGoing +// +// Created by APPLE on 1/13/26. +// + +import UIKit + +protocol DragAction { + func makeDragItems() -> [UIDragItem] +} + +extension DragAction { + func makeDragItems() -> [UIDragItem] { + return [UIDragItem(itemProvider: NSItemProvider())] + } +} From aaab10b41c1aaada30b3d1b0a2f1fcfdeff55575 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 15:15:14 +0900 Subject: [PATCH 19/25] =?UTF-8?q?refactor:=20#99=20=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EA=B7=B8=20=EB=8F=99=EC=9E=91=20=EC=8B=B1=EA=B8=80=ED=84=B4=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageScenarioViewController.swift | 13 +---------- .../MyScenarioViewController.swift | 13 +---------- .../SettingScenarioViewController.swift | 9 +------- .../Presentation/Protocol/DragAction.swift | 18 --------------- .../Protocol/TableViewDragHandler.swift | 23 +++++++++++++++++++ 5 files changed, 26 insertions(+), 50 deletions(-) delete mode 100644 BeforeGoing/Presentation/Protocol/DragAction.swift create mode 100644 BeforeGoing/Presentation/Protocol/TableViewDragHandler.swift diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift index b56db033..9e15c6dc 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift @@ -50,7 +50,7 @@ final class ManageScenarioViewController: BaseViewController { rootView.templateTableView.do { $0.delegate = self $0.dataSource = self - $0.dragDelegate = self + $0.dragDelegate = TableViewDragHandler.shared $0.dropDelegate = self $0.register(ManageScenarioCell.self, forCellReuseIdentifier: ManageScenarioCell.identifier) } @@ -197,17 +197,6 @@ extension ManageScenarioViewController: UITableViewDataSource { } } -extension ManageScenarioViewController: UITableViewDragDelegate, DragAction { - - func tableView( - _ tableView: UITableView, - itemsForBeginning session: any UIDragSession, - at indexPath: IndexPath - ) -> [UIDragItem] { - return makeDragItems() - } -} - extension ManageScenarioViewController: UITableViewDropDelegate { func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift index 68c26eaa..18ab2c73 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift @@ -98,7 +98,7 @@ final class MyScenarioViewController: BaseViewController { rootView.scenarioListTableView.do { $0.delegate = self $0.dataSource = self - $0.dragDelegate = self + $0.dragDelegate = TableViewDragHandler.shared $0.dropDelegate = self $0.register(ScenarioListItemCell.self, forCellReuseIdentifier: ScenarioListItemCell.identifier) } @@ -278,17 +278,6 @@ extension MyScenarioViewController: UITableViewDataSource { } } -extension MyScenarioViewController: UITableViewDragDelegate, DragAction { - - func tableView( - _ tableView: UITableView, - itemsForBeginning session: any UIDragSession, - at indexPath: IndexPath - ) -> [UIDragItem] { - return makeDragItems() - } -} - extension MyScenarioViewController: UITableViewDropDelegate { func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift index f9c91d27..2bd6b101 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift @@ -104,7 +104,7 @@ final class SettingScenarioViewController: BaseViewController { $0.register(MissionItemCell.self, forCellReuseIdentifier: MissionItemCell.identifier) $0.delegate = self $0.dataSource = self - $0.dragDelegate = self + $0.dragDelegate = TableViewDragHandler.shared $0.dropDelegate = self } } @@ -469,13 +469,6 @@ extension SettingScenarioViewController: UITableViewDataSource { } } -extension SettingScenarioViewController: UITableViewDragDelegate, DragAction { - - func tableView(_ tableView: UITableView, itemsForBeginning session: any UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - return makeDragItems() - } -} - extension SettingScenarioViewController: UITableViewDropDelegate { func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { diff --git a/BeforeGoing/Presentation/Protocol/DragAction.swift b/BeforeGoing/Presentation/Protocol/DragAction.swift deleted file mode 100644 index f1aadb1d..00000000 --- a/BeforeGoing/Presentation/Protocol/DragAction.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// DragAction.swift -// BeforeGoing -// -// Created by APPLE on 1/13/26. -// - -import UIKit - -protocol DragAction { - func makeDragItems() -> [UIDragItem] -} - -extension DragAction { - func makeDragItems() -> [UIDragItem] { - return [UIDragItem(itemProvider: NSItemProvider())] - } -} diff --git a/BeforeGoing/Presentation/Protocol/TableViewDragHandler.swift b/BeforeGoing/Presentation/Protocol/TableViewDragHandler.swift new file mode 100644 index 00000000..ac2cfa9f --- /dev/null +++ b/BeforeGoing/Presentation/Protocol/TableViewDragHandler.swift @@ -0,0 +1,23 @@ +// +// TableViewDragHandler.swift +// BeforeGoing +// +// Created by APPLE on 1/13/26. +// + +import UIKit + +final class TableViewDragHandler: NSObject, UITableViewDragDelegate { + + static let shared = TableViewDragHandler() + + private override init() {} + + func tableView( + _ tableView: UITableView, + itemsForBeginning session: any UIDragSession, + at indexPath: IndexPath + ) -> [UIDragItem] { + return [UIDragItem(itemProvider: NSItemProvider())] + } +} From df0e6337d28a24296c708a946d518848cd136d8f Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 15:17:46 +0900 Subject: [PATCH 20/25] =?UTF-8?q?refactor:=20#99=20=EB=93=9C=EB=9E=98?= =?UTF-8?q?=EA=B7=B8=20=EB=8F=99=EC=9E=91=20=ED=95=B8=EB=93=A4=EB=9F=AC=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B0=8F=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TableViewHandler}/TableViewDragHandler.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename BeforeGoing/Presentation/{Protocol => Common/TableViewHandler}/TableViewDragHandler.swift (100%) diff --git a/BeforeGoing/Presentation/Protocol/TableViewDragHandler.swift b/BeforeGoing/Presentation/Common/TableViewHandler/TableViewDragHandler.swift similarity index 100% rename from BeforeGoing/Presentation/Protocol/TableViewDragHandler.swift rename to BeforeGoing/Presentation/Common/TableViewHandler/TableViewDragHandler.swift From 506efde913d4837cf179583b115516844747f5b5 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 16:26:53 +0900 Subject: [PATCH 21/25] =?UTF-8?q?refactor:=20#99=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=EB=B7=B0=20Drop=20=EB=8F=99=EC=9E=91=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Extensions/UITableViewDropDelegate+.swift | 37 +++++++++++++++++++ .../ManageScenarioViewController.swift | 19 ++++------ .../MyScenarioViewController.swift | 19 ++++------ .../SettingScenarioViewController.swift | 19 ++++------ 4 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 BeforeGoing/Presentation/Extensions/UITableViewDropDelegate+.swift diff --git a/BeforeGoing/Presentation/Extensions/UITableViewDropDelegate+.swift b/BeforeGoing/Presentation/Extensions/UITableViewDropDelegate+.swift new file mode 100644 index 00000000..bc167c57 --- /dev/null +++ b/BeforeGoing/Presentation/Extensions/UITableViewDropDelegate+.swift @@ -0,0 +1,37 @@ +// +// UITableViewDropDelegate+.swift +// BeforeGoing +// +// Created by APPLE on 1/13/26. +// + +import UIKit + +extension UITableViewDropDelegate { + + func handleDrop( + with coordinator: UITableViewDropCoordinator, + action: (_ sourceSection: Int, _ destinationSection: Int) -> Void + ) { + guard let destinationIndexPath = coordinator.destinationIndexPath else { + return + } + + let destinationSection = destinationIndexPath.section + + for item in coordinator.items { + guard let sourceIndexPath = item.sourceIndexPath else { + return + } + + action(sourceIndexPath.section, destinationSection) + } + } + + func handleDropProposal(dropSessionDidUpdate session: UIDropSession) -> UITableViewDropProposal { + if session.localDragSession != nil { + return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) + } + return UITableViewDropProposal(operation: .cancel, intent: .unspecified) + } +} diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift index 9e15c6dc..675ec332 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift @@ -199,17 +199,17 @@ extension ManageScenarioViewController: UITableViewDataSource { extension ManageScenarioViewController: UITableViewDropDelegate { - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - guard let destinationIndexPath = coordinator.destinationIndexPath else { return } - let destinationSection = destinationIndexPath.section - - for item in coordinator.items { - guard let sourceIndexPath = item.sourceIndexPath, - let movedSection = viewModel.removeScenarioType(at: sourceIndexPath.section) else { + func tableView( + _ tableView: UITableView, + performDropWith coordinator: UITableViewDropCoordinator + ) { + handleDrop(with: coordinator) { sourceSection, destinationSection in + guard let movedSection = viewModel.removeScenarioType(at: sourceSection) else { return } viewModel.addScenarioType(movedSection, at: destinationSection) } + tableView.reloadData() } @@ -218,9 +218,6 @@ extension ManageScenarioViewController: UITableViewDropDelegate { dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath? ) -> UITableViewDropProposal { - if session.localDragSession != nil { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - return UITableViewDropProposal(operation: .cancel, intent: .unspecified) + handleDropProposal(dropSessionDidUpdate: session) } } diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift index 18ab2c73..a63e6369 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift @@ -280,17 +280,15 @@ extension MyScenarioViewController: UITableViewDataSource { extension MyScenarioViewController: UITableViewDropDelegate { - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - guard let destinationIndexPath = coordinator.destinationIndexPath else { return } - let destinationSection = destinationIndexPath.section - - for item in coordinator.items { - guard let sourceIndexPath = item.sourceIndexPath else { continue } - let sourceSection = sourceIndexPath.section - + func tableView( + _ tableView: UITableView, + performDropWith coordinator: UITableViewDropCoordinator + ) { + handleDrop(with: coordinator) { sourceSection, destinationSection in moveScenario(originalAt: sourceSection, destinationAt: destinationSection) updateScenarioOrder(originalAt: sourceSection, destinationAt: destinationSection) } + tableView.reloadData() } @@ -299,10 +297,7 @@ extension MyScenarioViewController: UITableViewDropDelegate { dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath? ) -> UITableViewDropProposal { - if session.localDragSession != nil { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - return UITableViewDropProposal(operation: .cancel, intent: .unspecified) + handleDropProposal(dropSessionDidUpdate: session) } private func moveScenario(originalAt: Int, destinationAt: Int) { diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift index 2bd6b101..17397ac5 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift @@ -471,17 +471,15 @@ extension SettingScenarioViewController: UITableViewDataSource { extension SettingScenarioViewController: UITableViewDropDelegate { - func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) { - guard let destinationIndexPath = coordinator.destinationIndexPath else { return } - let destinationSection = destinationIndexPath.section - - for item in coordinator.items { - guard let sourceIndexPath = item.sourceIndexPath else { continue } - let sourceSection = sourceIndexPath.section - + func tableView( + _ tableView: UITableView, + performDropWith coordinator: UITableViewDropCoordinator + ) { + handleDrop(with: coordinator) { sourceSection, destinationSection in let movedSection = addScenarioViewModel.removeMission(at: sourceSection) addScenarioViewModel.addMission(movedSection, at: destinationSection) } + tableView.reloadData() } @@ -490,9 +488,6 @@ extension SettingScenarioViewController: UITableViewDropDelegate { dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath? ) -> UITableViewDropProposal { - if session.localDragSession != nil { - return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) - } - return UITableViewDropProposal(operation: .cancel, intent: .unspecified) + handleDropProposal(dropSessionDidUpdate: session) } } From 1cd46ec1144191aee4bf9d850703884505882a0d Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 17:31:30 +0900 Subject: [PATCH 22/25] =?UTF-8?q?refactor:=20#99=20UITableViewDragDelegate?= =?UTF-8?q?=20=ED=99=95=EC=9E=A5=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TableViewDragHandler.swift | 23 ------------------- .../Extensions/UITableViewDragDelegate+.swift | 15 ++++++++++++ .../ManageScenarioViewController.swift | 2 +- .../MyScenarioViewController.swift | 2 +- .../SettingScenarioViewController.swift | 2 +- 5 files changed, 18 insertions(+), 26 deletions(-) delete mode 100644 BeforeGoing/Presentation/Common/TableViewHandler/TableViewDragHandler.swift create mode 100644 BeforeGoing/Presentation/Extensions/UITableViewDragDelegate+.swift diff --git a/BeforeGoing/Presentation/Common/TableViewHandler/TableViewDragHandler.swift b/BeforeGoing/Presentation/Common/TableViewHandler/TableViewDragHandler.swift deleted file mode 100644 index ac2cfa9f..00000000 --- a/BeforeGoing/Presentation/Common/TableViewHandler/TableViewDragHandler.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// TableViewDragHandler.swift -// BeforeGoing -// -// Created by APPLE on 1/13/26. -// - -import UIKit - -final class TableViewDragHandler: NSObject, UITableViewDragDelegate { - - static let shared = TableViewDragHandler() - - private override init() {} - - func tableView( - _ tableView: UITableView, - itemsForBeginning session: any UIDragSession, - at indexPath: IndexPath - ) -> [UIDragItem] { - return [UIDragItem(itemProvider: NSItemProvider())] - } -} diff --git a/BeforeGoing/Presentation/Extensions/UITableViewDragDelegate+.swift b/BeforeGoing/Presentation/Extensions/UITableViewDragDelegate+.swift new file mode 100644 index 00000000..82491eac --- /dev/null +++ b/BeforeGoing/Presentation/Extensions/UITableViewDragDelegate+.swift @@ -0,0 +1,15 @@ +// +// UITableViewDragDelegate+.swift +// BeforeGoing +// +// Created by APPLE on 1/13/26. +// + +import UIKit + +extension UITableViewDragDelegate { + + func provideDragItem() -> [UIDragItem] { + return [UIDragItem(itemProvider: NSItemProvider())] + } +} diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift index 675ec332..5a3b6849 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift @@ -50,7 +50,7 @@ final class ManageScenarioViewController: BaseViewController { rootView.templateTableView.do { $0.delegate = self $0.dataSource = self - $0.dragDelegate = TableViewDragHandler.shared + $0.dragDelegate = self $0.dropDelegate = self $0.register(ManageScenarioCell.self, forCellReuseIdentifier: ManageScenarioCell.identifier) } diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift index a63e6369..96bcdbe3 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift @@ -98,7 +98,7 @@ final class MyScenarioViewController: BaseViewController { rootView.scenarioListTableView.do { $0.delegate = self $0.dataSource = self - $0.dragDelegate = TableViewDragHandler.shared + $0.dragDelegate = self $0.dropDelegate = self $0.register(ScenarioListItemCell.self, forCellReuseIdentifier: ScenarioListItemCell.identifier) } diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift index 17397ac5..876b0962 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift @@ -104,7 +104,7 @@ final class SettingScenarioViewController: BaseViewController { $0.register(MissionItemCell.self, forCellReuseIdentifier: MissionItemCell.identifier) $0.delegate = self $0.dataSource = self - $0.dragDelegate = TableViewDragHandler.shared + $0.dragDelegate = self $0.dropDelegate = self } } From 912a99651601897a5c91f13a2ce3b0935c8c3db0 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Tue, 13 Jan 2026 17:32:09 +0900 Subject: [PATCH 23/25] =?UTF-8?q?refactor:=20#99=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=EB=B7=B0=EC=9D=98=20=EC=8A=A4=EC=99=80=EC=9D=B4?= =?UTF-8?q?=ED=94=84=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=86=A0=EC=BD=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ManageScenarioViewController.swift | 51 +++---------- .../MyScenarioViewController.swift | 57 ++++----------- .../SettingScenarioViewController.swift | 49 +++---------- .../Protocol/TableViewSwipeAction.swift | 73 +++++++++++++++++++ 4 files changed, 112 insertions(+), 118 deletions(-) create mode 100644 BeforeGoing/Presentation/Protocol/TableViewSwipeAction.swift diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift index 5a3b6849..b472532f 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/ManageScenarioViewController.swift @@ -123,7 +123,7 @@ extension ManageScenarioViewController: UITableViewDelegate { } } -extension ManageScenarioViewController: UITableViewDataSource { +extension ManageScenarioViewController: UITableViewDataSource, TableViewSwipeAction { func numberOfSections(in tableView: UITableView) -> Int { return viewModel.templateCount @@ -152,48 +152,21 @@ extension ManageScenarioViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - - let deleteAction = createDeleteAction(tableView: tableView, indexPath: indexPath) - let largeConfig = createLargeConfig() - setDeleteActionStyle(deleteAction: deleteAction, largeConfig: largeConfig) - - let config = createSwipeAction(deleteAction: deleteAction) - - return config - } - - private func createDeleteAction(tableView: UITableView, indexPath: IndexPath) -> UIContextualAction { - return UIContextualAction( - style: .normal, - title: nil - ) { [weak self] (_, view, completion) in + return createSwipeActionConfig(tableView: tableView, indexPath: indexPath) { [weak self] in let _ = self?.viewModel.removeScenarioType(at: indexPath.section) tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic) - completion(true) - } - } - - private func createLargeConfig() -> UIImage.SymbolConfiguration { - return UIImage.SymbolConfiguration(pointSize: 12.0, weight: .bold, scale: .large) - } - - private func setDeleteActionStyle( - deleteAction: UIContextualAction, - largeConfig: UIImage.SymbolConfiguration - ) { - deleteAction.do { - $0.backgroundColor = .white - $0.image = UIImage( - systemName: "trash", - withConfiguration: largeConfig - )?.withTintColor(.white, renderingMode: .alwaysTemplate).addBackgroundCircle(.warning500) - } + } } +} + +extension ManageScenarioViewController: UITableViewDragDelegate { - private func createSwipeAction(deleteAction: UIContextualAction) -> UISwipeActionsConfiguration { - let config = UISwipeActionsConfiguration(actions: [deleteAction]) - config.performsFirstActionWithFullSwipe = false - return config + func tableView( + _ tableView: UITableView, + itemsForBeginning session: any UIDragSession, + at indexPath: IndexPath + ) -> [UIDragItem] { + provideDragItem() } } diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift index 96bcdbe3..8ef9a24d 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/MyScenarioViewController.swift @@ -182,7 +182,7 @@ extension MyScenarioViewController: UITableViewDelegate { } } -extension MyScenarioViewController: UITableViewDataSource { +extension MyScenarioViewController: UITableViewDataSource, TableViewSwipeAction { func numberOfSections(in tableView: UITableView) -> Int { return getScenariosViewModel.scenariosCount @@ -209,26 +209,7 @@ extension MyScenarioViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - - let deleteAction = createDeleteAction(tableView: tableView, indexPath: indexPath) - let largeConfig = createLargeConfig() - setDeleteActionStyle(deleteAction: deleteAction, largeConfig: largeConfig) - - let config = createSwipeAction(deleteAction: deleteAction) - return config - } - - private func bindCell(to cell: ScenarioListItemCell, section: Int) { - let name = getScenariosViewModel.getScenarioName(section: section) - let noticeInformation = getScenariosViewModel.getNotificationInformation(section: section) - cell.bind(name: name, noticeInformation: noticeInformation) - } - - private func createDeleteAction(tableView: UITableView, indexPath: IndexPath) -> UIContextualAction { - return UIContextualAction( - style: .normal, - title: nil - ) { [weak self] (_, view, completion) in + return createSwipeActionConfig(tableView: tableView, indexPath: indexPath) { [weak self] in Task { guard let self = self else { return } @@ -248,33 +229,25 @@ extension MyScenarioViewController: UITableViewDataSource { self.handleError(error) BeforeGoingLogger.error(error) } - completion(true) } } } - - private func createLargeConfig() -> UIImage.SymbolConfiguration { - return UIImage.SymbolConfiguration(pointSize: 12.0, weight: .bold, scale: .large) - } - - private func setDeleteActionStyle( - deleteAction: UIContextualAction, - largeConfig: UIImage.SymbolConfiguration - ) { - deleteAction.do { - $0.backgroundColor = .white - $0.image = UIImage( - systemName: "trash", - withConfiguration: largeConfig - )?.withTintColor(.white, renderingMode: .alwaysTemplate).addBackgroundCircle(.warning500) - } + private func bindCell(to cell: ScenarioListItemCell, section: Int) { + let name = getScenariosViewModel.getScenarioName(section: section) + let noticeInformation = getScenariosViewModel.getNotificationInformation(section: section) + cell.bind(name: name, noticeInformation: noticeInformation) } +} + +extension MyScenarioViewController: UITableViewDragDelegate { - private func createSwipeAction(deleteAction: UIContextualAction) -> UISwipeActionsConfiguration { - let config = UISwipeActionsConfiguration(actions: [deleteAction]) - config.performsFirstActionWithFullSwipe = false - return config + func tableView( + _ tableView: UITableView, + itemsForBeginning session: any UIDragSession, + at indexPath: IndexPath + ) -> [UIDragItem] { + provideDragItem() } } diff --git a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift index 876b0962..03e088ff 100644 --- a/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift +++ b/BeforeGoing/Presentation/Feature/Scenario/ViewController/SettingScenarioViewController.swift @@ -393,7 +393,7 @@ extension SettingScenarioViewController: UITableViewDelegate { } } -extension SettingScenarioViewController: UITableViewDataSource { +extension SettingScenarioViewController: UITableViewDataSource, TableViewSwipeAction { func numberOfSections(in tableView: UITableView) -> Int { return addScenarioViewModel.missionsCount @@ -422,50 +422,25 @@ extension SettingScenarioViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - - let deleteAction = createDeleteAction(tableView: tableView, indexPath: indexPath) - let largeConfig = createLargeConfig() - setDeleteActionStyle(deleteAction: deleteAction, largeConfig: largeConfig) - let config = createSwipeAction(deleteAction: deleteAction) - return config - } - - private func createDeleteAction(tableView: UITableView, indexPath: IndexPath) -> UIContextualAction { - return UIContextualAction( - style: .normal, - title: nil - ) { [weak self] (_, view, completion) in + return createSwipeActionConfig(tableView: tableView, indexPath: indexPath) { [weak self] in guard let self = self else { return } let _ = self.addScenarioViewModel.removeMission(at: indexPath.section) tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic) self.checkNextButtonState() self.rootView.settingMissionView.updateMissionCount(self.addScenarioViewModel.missionsCount) - completion(true) - } - } - - private func createLargeConfig() -> UIImage.SymbolConfiguration { - return UIImage.SymbolConfiguration(pointSize: 12.0, weight: .bold, scale: .large) - } - - private func setDeleteActionStyle( - deleteAction: UIContextualAction, - largeConfig: UIImage.SymbolConfiguration - ) { - deleteAction.do { - $0.backgroundColor = .white - $0.image = UIImage( - systemName: "trash", - withConfiguration: largeConfig - )?.withTintColor(.white, renderingMode: .alwaysTemplate).addBackgroundCircle(.warning500) - } + } } +} + +extension SettingScenarioViewController: UITableViewDragDelegate { - private func createSwipeAction(deleteAction: UIContextualAction) -> UISwipeActionsConfiguration { - let config = UISwipeActionsConfiguration(actions: [deleteAction]) - config.performsFirstActionWithFullSwipe = false - return config + func tableView( + _ tableView: UITableView, + itemsForBeginning session: any UIDragSession, + at indexPath: IndexPath + ) -> [UIDragItem] { + provideDragItem() } } diff --git a/BeforeGoing/Presentation/Protocol/TableViewSwipeAction.swift b/BeforeGoing/Presentation/Protocol/TableViewSwipeAction.swift new file mode 100644 index 00000000..03e31b53 --- /dev/null +++ b/BeforeGoing/Presentation/Protocol/TableViewSwipeAction.swift @@ -0,0 +1,73 @@ +// +// TableViewSwipeAction.swift +// BeforeGoing +// +// Created by APPLE on 1/13/26. +// + +import UIKit + +protocol TableViewSwipeAction: AnyObject { + func createSwipeActionConfig( + tableView: UITableView, + indexPath: IndexPath, + action: @escaping () -> Void + ) -> UISwipeActionsConfiguration +} + +extension TableViewSwipeAction where Self: BaseViewController { + + func createSwipeActionConfig( + tableView: UITableView, + indexPath: IndexPath, + action: @escaping () -> Void + ) -> UISwipeActionsConfiguration { + let deleteAction = createDeleteAction( + tableView: tableView, + indexPath: indexPath) { + action() + } + let largeConfig = createLargeConfig() + setDeleteActionStyle(deleteAction: deleteAction, largeConfig: largeConfig) + let config = createSwipeAction(deleteAction: deleteAction) + + return config + } + + private func createSwipeAction(deleteAction: UIContextualAction) -> UISwipeActionsConfiguration { + let config = UISwipeActionsConfiguration(actions: [deleteAction]) + config.performsFirstActionWithFullSwipe = false + return config + } + + private func createDeleteAction( + tableView: UITableView, + indexPath: IndexPath, + action: @escaping () -> Void + ) -> UIContextualAction { + return UIContextualAction( + style: .normal, + title: nil + ) { (_, view, completion) in + action() + completion(true) + } + } + + private func createLargeConfig() -> UIImage.SymbolConfiguration { + return UIImage.SymbolConfiguration(pointSize: 12.0, weight: .bold, scale: .large) + } + + private func setDeleteActionStyle( + deleteAction: UIContextualAction, + largeConfig: UIImage.SymbolConfiguration + ) { + deleteAction.do { + $0.backgroundColor = .white + $0.image = UIImage( + systemName: "trash", + withConfiguration: largeConfig + )?.withTintColor(.white, renderingMode: .alwaysTemplate).addBackgroundCircle(.warning500) + } + } +} From 427fbae3cd4fb20b13dece3b63f707a65198e036 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Fri, 16 Jan 2026 18:14:05 +0900 Subject: [PATCH 24/25] =?UTF-8?q?feat:=20#99=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=A1=B0=ED=9A=8C=20API=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/Service/API/MemberAPI.swift | 11 +-- .../Data/Network/Service/NetworkService.swift | 54 ++++++++------ .../Persistence/Service/UserDefaultsKey.swift | 7 -- .../Data/Repository/AuthRepository.swift | 51 ++++++------- .../Data/Repository/MemberRepository.swift | 72 +++++-------------- .../Domain/Entity/MemberNameEntity.swift | 16 +++++ .../Domain/Interface/MemberInterface.swift | 3 +- .../UseCase/Member/GetMemberNameUseCase.swift | 8 +-- .../Home/ViewModel/HomeViewModel.swift | 2 +- .../Setting/ViewModel/ProfileViewModel.swift | 2 +- 10 files changed, 110 insertions(+), 116 deletions(-) create mode 100644 BeforeGoing/Domain/Entity/MemberNameEntity.swift diff --git a/BeforeGoing/Data/Network/Service/API/MemberAPI.swift b/BeforeGoing/Data/Network/Service/API/MemberAPI.swift index d7dc17a7..bb307341 100644 --- a/BeforeGoing/Data/Network/Service/API/MemberAPI.swift +++ b/BeforeGoing/Data/Network/Service/API/MemberAPI.swift @@ -10,6 +10,7 @@ import Alamofire enum MemberAPI { case updateNickname(accessToken: String, dto: UpdateNicknameRequestDTO) case withdraw(accessToken: String) + case fetchMemberName(accessToken: String) } extension MemberAPI: EndPoint { @@ -22,7 +23,7 @@ extension MemberAPI: EndPoint { let basePath = Environment.baseURL + basePath switch self { - case .updateNickname: + case .updateNickname, .fetchMemberName: return basePath + "/nickname" case .withdraw: return basePath @@ -35,6 +36,8 @@ extension MemberAPI: EndPoint { return .patch case .withdraw: return .delete + case .fetchMemberName: + return .get } } @@ -44,7 +47,7 @@ extension MemberAPI: EndPoint { var headers: HTTPHeaders? { switch self { - case .updateNickname(let accessToken, _), .withdraw(let accessToken): + case .updateNickname(let accessToken, _), .withdraw(let accessToken), .fetchMemberName(let accessToken): return [ "Content-Type": "application/json", "Authorization": "Bearer \(accessToken)" @@ -54,7 +57,7 @@ extension MemberAPI: EndPoint { var parameterEncoding: any ParameterEncoding { switch self { - case .updateNickname: + case .updateNickname, .fetchMemberName: return JSONEncoding.default case .withdraw: return URLEncoding.default @@ -69,7 +72,7 @@ extension MemberAPI: EndPoint { switch self { case .updateNickname(_, let dto): return try? dto.toBodyParameters() - case .withdraw: + case .withdraw, .fetchMemberName: return nil } } diff --git a/BeforeGoing/Data/Network/Service/NetworkService.swift b/BeforeGoing/Data/Network/Service/NetworkService.swift index 7fec1d12..1a57d387 100644 --- a/BeforeGoing/Data/Network/Service/NetworkService.swift +++ b/BeforeGoing/Data/Network/Service/NetworkService.swift @@ -10,6 +10,7 @@ protocol APIManaging { responseType: T.Type ) async throws -> T func request(endPoint: any EndPoint) async throws + func requestString(endPoint: EndPoint) async throws -> String func requestKakaoIDToken(nonce: String?) async throws -> String } @@ -45,6 +46,23 @@ final class NetworkService: APIManaging { let response = try await dataRequest.serializingData().value writeLog(response: response) } catch { + if let afError = error.asAFError { + throw handleError(afError: afError) + } + throw error + } + } + + func requestString(endPoint: EndPoint) async throws -> String { + do { + let dataRequest = createDataRequest(endPoint: endPoint) + let response = try await dataRequest.serializingString().value + writeLog(response: response) + return response + } catch { + if let afError = error.asAFError { + throw handleError(afError: afError) + } throw error } } @@ -73,28 +91,24 @@ final class NetworkService: APIManaging { } } - switch afError { - case .responseValidationFailed(let reason): - if case .unacceptableStatusCode(let statuscode) = reason { - switch statuscode { - case 304: - return .notModifiedError - case 400: - return .badRequestError - case 404: - return .notFoundError - case 429: - return .tooManyRequset - case (500...599): - return .serviceUnavailable - default: - return .unknownError - } + if let statusCode = afError.responseCode { + switch statusCode { + case 304: + return .notModifiedError + case 400: + return .badRequestError + case 404: + return .notFoundError + case 429: + return .tooManyRequset + case 500...599: + return .serviceUnavailable + default: + break } - return .unknownError - default: - return .unknownError } + + return .unknownError } } diff --git a/BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift b/BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift index 5a2a4a8d..5d25d9a7 100644 --- a/BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift +++ b/BeforeGoing/Data/Persistence/Service/UserDefaultsKey.swift @@ -6,13 +6,6 @@ // enum UserDefaultsKey: String, CaseIterable { - case isKakaoCompletedAgreeTerms - case isAppleCompletedAgreeTerms - case isKakaoCompletedOnboarding - case isAppleCompletedOnboarding - case kakaoMemberName - case appleMemberName - case appleCrendentialName case provider case lastProvider } diff --git a/BeforeGoing/Data/Repository/AuthRepository.swift b/BeforeGoing/Data/Repository/AuthRepository.swift index a75e867b..5a00cd37 100644 --- a/BeforeGoing/Data/Repository/AuthRepository.swift +++ b/BeforeGoing/Data/Repository/AuthRepository.swift @@ -60,23 +60,35 @@ struct AuthRepository: AuthInterface { saveKeyChain(response: response) saveProvider(provider) - let isCompletedJoin = !response.isNewMember && isCompletedOnboarding(provider: provider) + let isAgreedTerms = try await isAgreedTerms(accessToken: response.accessToken) + let isCompletedJoin = !response.isNewMember && isAgreedTerms return isCompletedJoin } func requestLogin(provider: Provider, idToken: String, name: String?) async throws -> Bool { - let isRegisterdMember = try await requestLogin(provider: provider, idToken: idToken) - saveMemberName(name) - return isRegisterdMember + let isCompletedJoin = try await requestLogin(provider: provider, idToken: idToken) + + if let name, + !name.isBlank, + let accessToken = keyChainService.load(key: .accessToken) { + try await networkService.request( + endPoint: MemberAPI.updateNickname( + accessToken: accessToken, + dto: .init(nickname: name) + ) + ) + } + + return isCompletedJoin } func autoLogin() async throws -> Bool { - guard let providerString: String = userDefaultsService.load(key: .provider), - let provider = Provider(rawValue: providerString) else { + guard let accessToken = keyChainService.load(key: .accessToken) else { return false } - guard isTokenExists, isCompletedOnboarding(provider: provider) else { + guard isTokenExists, + try await isAgreedTerms(accessToken: accessToken) else { return false } @@ -145,18 +157,6 @@ struct AuthRepository: AuthInterface { let _ = userDefaultsService.save(lastLogin, key: .lastProvider) } - private func saveMemberName(_ name: String?) { - if let name, !name.isBlank { - let _ = userDefaultsService.save(name, key: .appleCrendentialName) - let _ = userDefaultsService.save(name, key: .appleMemberName) - return - } - - if let credentialName: String = userDefaultsService.load(key: .appleCrendentialName) { - let _ = userDefaultsService.save(credentialName, key: .appleMemberName) - } - } - private var isTokenExists: Bool { if let accessToken = keyChainService.load(key: .accessToken), let refreshToken = keyChainService.load(key: .refreshToken), @@ -167,13 +167,16 @@ struct AuthRepository: AuthInterface { return false } - private func isCompletedOnboarding(provider: Provider) -> Bool { - let key: UserDefaultsKey = (provider == .apple) ? .isAppleCompletedOnboarding : .isKakaoCompletedOnboarding - - guard let isCompleted: Bool = userDefaultsService.load(key: key) else { + private func isAgreedTerms(accessToken: String) async throws -> Bool { + do { + let _ = try await networkService.request( + endPoint: TermsAPI.getTerms(accessToken: accessToken), + responseType: TermsResponseDTO.self + ) + return true + } catch { return false } - return isCompleted } private func deleteUserInformation() { diff --git a/BeforeGoing/Data/Repository/MemberRepository.swift b/BeforeGoing/Data/Repository/MemberRepository.swift index 0fdff674..21446d85 100644 --- a/BeforeGoing/Data/Repository/MemberRepository.swift +++ b/BeforeGoing/Data/Repository/MemberRepository.swift @@ -35,18 +35,12 @@ struct MemberRepository: MemberInterface { return false } - func getMemberName() -> String? { - guard let provider: String = userDefaultsService.load(key: .provider) else { - return nil - } - - switch provider { - case Provider.kakao.rawValue: - return userDefaultsService.load(key: .kakaoMemberName) - case Provider.apple.rawValue: - return userDefaultsService.load(key: .appleMemberName) - default: - return nil + func getMemberName() async throws -> MemberNameEntity { + do { + let memberName = try await fetchMemberName() + return memberName + } catch { + return .stub() } } @@ -55,22 +49,12 @@ struct MemberRepository: MemberInterface { BeforeGoingLogger.error(BeforeGoingError.accessTokenMissing) return } - guard let provider: String = userDefaultsService.load(key: .provider) else { - return - } let requestDTO = updateNicknameRequestMapper.map(nickname) - let responseDTO = try await networkService.request( + let _ = try await networkService.request( endPoint: MemberAPI.updateNickname(accessToken: accessToken, dto: requestDTO), responseType: MemberResponseDTO.self ) - - if provider == Provider.apple.rawValue { - let _ = userDefaultsService.save(responseDTO.nickname, key: .appleMemberName) - } - if provider == Provider.kakao.rawValue { - let _ = userDefaultsService.save(responseDTO.nickname, key: .kakaoMemberName) - } } func withdrawMember() async throws { @@ -89,15 +73,17 @@ struct MemberRepository: MemberInterface { } } - func completeOnboarding() -> Bool { - guard let providerString: String = userDefaultsService.load(key: .provider), - let provider = Provider(rawValue: providerString) else { - return false + private func fetchMemberName() async throws -> MemberNameEntity { + guard let accessToken = keyChainService.load(key: .accessToken) else { + BeforeGoingLogger.error(BeforeGoingError.accessTokenMissing) + return .stub() } - let key: UserDefaultsKey = (provider == .apple) ? .isAppleCompletedOnboarding : .isKakaoCompletedOnboarding - let isSaved = userDefaultsService.save(true, key: key) - return isSaved + let fetchedName = try await networkService.requestString( + endPoint: MemberAPI.fetchMemberName(accessToken: accessToken) + ) + + return .init(memberName: fetchedName) } private func removeMemberInfo(provider: String) { @@ -116,29 +102,9 @@ struct MemberRepository: MemberInterface { } private func removeUserDefaultsInfo(provider: String) { - let excludedKeys: [UserDefaultsKey?] = { - switch provider { - case Provider.kakao.rawValue: - return [ - .appleCrendentialName, - .appleMemberName, - .isAppleCompletedOnboarding, - .isAppleCompletedAgreeTerms - ] - case Provider.apple.rawValue: - return [ - .appleCrendentialName, - .kakaoMemberName, - .isKakaoCompletedOnboarding, - .isKakaoCompletedAgreeTerms - ] - default: - return [.appleCrendentialName] - } - }() - UserDefaultsKey.allCases - .filter { !excludedKeys.contains($0) } - .forEach { let _ = userDefaultsService.delete(key: $0) } + .forEach { + let _ = userDefaultsService.delete(key: $0) + } } } diff --git a/BeforeGoing/Domain/Entity/MemberNameEntity.swift b/BeforeGoing/Domain/Entity/MemberNameEntity.swift new file mode 100644 index 00000000..57a354b7 --- /dev/null +++ b/BeforeGoing/Domain/Entity/MemberNameEntity.swift @@ -0,0 +1,16 @@ +// +// MemberNameEntity.swift +// BeforeGoing +// +// Created by APPLE on 1/12/26. +// + +struct MemberNameEntity { + let memberName: String +} + +extension MemberNameEntity { + static func stub() -> Self { + return .init(memberName: "워리") + } +} diff --git a/BeforeGoing/Domain/Interface/MemberInterface.swift b/BeforeGoing/Domain/Interface/MemberInterface.swift index 2ecedc82..188de120 100644 --- a/BeforeGoing/Domain/Interface/MemberInterface.swift +++ b/BeforeGoing/Domain/Interface/MemberInterface.swift @@ -11,6 +11,5 @@ protocol MemberInterface { func updateNickname(nickname: String) async throws func withdrawMember() async throws - func getMemberName() -> String? - func completeOnboarding() -> Bool + func getMemberName() async throws -> MemberNameEntity } diff --git a/BeforeGoing/Domain/UseCase/Member/GetMemberNameUseCase.swift b/BeforeGoing/Domain/UseCase/Member/GetMemberNameUseCase.swift index 014ca07e..f8f30fb4 100644 --- a/BeforeGoing/Domain/UseCase/Member/GetMemberNameUseCase.swift +++ b/BeforeGoing/Domain/UseCase/Member/GetMemberNameUseCase.swift @@ -6,7 +6,7 @@ // protocol GetMemberNameType { - func execute() -> String + func execute() async throws -> String } struct GetMemberNameUseCase: GetMemberNameType { @@ -17,9 +17,9 @@ struct GetMemberNameUseCase: GetMemberNameType { self.repository = repository } - func execute() -> String { - let name = repository.getMemberName() ?? "" - return name + func execute() async throws -> String { + let result = try await repository.getMemberName() + return result.memberName } } diff --git a/BeforeGoing/Presentation/Feature/Home/ViewModel/HomeViewModel.swift b/BeforeGoing/Presentation/Feature/Home/ViewModel/HomeViewModel.swift index bd1e010f..e5b0800e 100644 --- a/BeforeGoing/Presentation/Feature/Home/ViewModel/HomeViewModel.swift +++ b/BeforeGoing/Presentation/Feature/Home/ViewModel/HomeViewModel.swift @@ -104,7 +104,7 @@ final class HomeViewModel: ViewModeling { func action(input: Input) async throws -> Output { switch input { case .requestName: - let memberName = getMemberNameUseCase.execute() + let memberName = try await getMemberNameUseCase.execute() return MemberNameOutput(memberName: memberName) case .requestDate: diff --git a/BeforeGoing/Presentation/Feature/Setting/ViewModel/ProfileViewModel.swift b/BeforeGoing/Presentation/Feature/Setting/ViewModel/ProfileViewModel.swift index 69115ddb..4f68fd46 100644 --- a/BeforeGoing/Presentation/Feature/Setting/ViewModel/ProfileViewModel.swift +++ b/BeforeGoing/Presentation/Feature/Setting/ViewModel/ProfileViewModel.swift @@ -46,7 +46,7 @@ final class ProfileViewModel: ViewModeling { func action(input: Input) async throws -> Output { switch input { case .viewWillAppear: - let name = getMemberNameUseCase.execute() + let name = try await getMemberNameUseCase.execute() return MemberNameOutput(name: name) case .logoutButtonDidTap: do { From 1aa1bda338ce11dacfff6e718929021b68029d70 Mon Sep 17 00:00:00 2001 From: heoseungjun Date: Fri, 16 Jan 2026 18:14:30 +0900 Subject: [PATCH 25/25] =?UTF-8?q?refactor:=20#99=20=EC=95=BD=EA=B4=80?= =?UTF-8?q?=EB=8F=99=EC=9D=98=20=EC=99=84=EB=A3=8C=20=EC=97=AC=EB=B6=80,?= =?UTF-8?q?=20=EC=98=A8=EB=B3=B4=EB=94=A9=20=EC=99=84=EB=A3=8C=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=EB=A5=BC=20=EA=B4=80=EB=A6=AC=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Repository/TermsRepository.swift | 21 +++++++------ .../Domain/DomainDependencyAssembler.swift | 3 -- .../SaveOnboardingCompletedUseCase.swift | 23 -------------- .../OnboardingViewController.swift | 19 ++---------- .../ViewModel/OnboardingViewModel.swift | 31 ------------------- .../PresentationDependencyAssembler.swift | 10 ------ .../Presentation/ViewControllerFactory.swift | 3 +- 7 files changed, 15 insertions(+), 95 deletions(-) delete mode 100644 BeforeGoing/Domain/UseCase/Member/SaveOnboardingCompletedUseCase.swift delete mode 100644 BeforeGoing/Presentation/Feature/Onboarding/ViewModel/OnboardingViewModel.swift diff --git a/BeforeGoing/Data/Repository/TermsRepository.swift b/BeforeGoing/Data/Repository/TermsRepository.swift index 8890e48e..3c9435e1 100644 --- a/BeforeGoing/Data/Repository/TermsRepository.swift +++ b/BeforeGoing/Data/Repository/TermsRepository.swift @@ -51,14 +51,8 @@ struct TermsRepository: TermsInterface { return } - guard let providerString: String = userDefaultsService.load(key: .provider) else { - return - } - - let provider = Provider(rawValue: providerString) - let key: UserDefaultsKey = (provider == .apple) ? .isAppleCompletedAgreeTerms : .isKakaoCompletedAgreeTerms - - if let _: Bool = userDefaultsService.load(key: key) { + let isAgreedTerms = try await isAgreedTerms(accessToken: accessToken) + if isAgreedTerms { try await updateAgreementTerm(eventPushAgreed: eventPushAgreed) return } @@ -75,8 +69,6 @@ struct TermsRepository: TermsInterface { endPoint: TermsAPI.sendTerms(accessToken: accessToken, dto: requestDTO), responseType: TermsResponseDTO.self ) - - let _ = (provider == .apple) ? userDefaultsService.save(true, key: .isAppleCompletedAgreeTerms) : userDefaultsService.save(true, key: .isKakaoCompletedAgreeTerms) } func updateAgreementTerm(eventPushAgreed: Bool) async throws { @@ -91,4 +83,13 @@ struct TermsRepository: TermsInterface { responseType: TermsResponseDTO.self ) } + + private func isAgreedTerms(accessToken: String) async throws -> Bool { + do { + let _ = try await getAgreementTerms() + return true + } catch { + return false + } + } } diff --git a/BeforeGoing/Domain/DomainDependencyAssembler.swift b/BeforeGoing/Domain/DomainDependencyAssembler.swift index 19a2721c..a0a8c174 100644 --- a/BeforeGoing/Domain/DomainDependencyAssembler.swift +++ b/BeforeGoing/Domain/DomainDependencyAssembler.swift @@ -110,9 +110,6 @@ final class DomainDependencyAssembler: DependencyAssembler { DIContainer.shared.register(type: MemberWithdrawType.self) { _ in return MemberWithdrawUseCase(repository: memberRepository) } - DIContainer.shared.register(type: SaveOnboardingCompletedType.self) { _ in - return SaveOnboardingCompletedUseCase(repository: memberRepository) - } DIContainer.shared.register(type: FetchWeatherType.self) { _ in return FetchWeatherUseCase() diff --git a/BeforeGoing/Domain/UseCase/Member/SaveOnboardingCompletedUseCase.swift b/BeforeGoing/Domain/UseCase/Member/SaveOnboardingCompletedUseCase.swift deleted file mode 100644 index 5aa2bcc3..00000000 --- a/BeforeGoing/Domain/UseCase/Member/SaveOnboardingCompletedUseCase.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// SaveIsCompletedOnboardingUseCase.swift -// BeforeGoing -// -// Created by APPLE on 11/13/25. -// - -protocol SaveOnboardingCompletedType { - func saveOnboardingCompleted() -> Bool -} - -struct SaveOnboardingCompletedUseCase: SaveOnboardingCompletedType { - - private let repository: MemberInterface - - init(repository: MemberInterface) { - self.repository = repository - } - - func saveOnboardingCompleted() -> Bool { - repository.completeOnboarding() - } -} diff --git a/BeforeGoing/Presentation/Feature/Onboarding/ViewController/OnboardingViewController.swift b/BeforeGoing/Presentation/Feature/Onboarding/ViewController/OnboardingViewController.swift index 448fdfd9..59b85ad4 100644 --- a/BeforeGoing/Presentation/Feature/Onboarding/ViewController/OnboardingViewController.swift +++ b/BeforeGoing/Presentation/Feature/Onboarding/ViewController/OnboardingViewController.swift @@ -10,16 +10,6 @@ import UIKit final class OnboardingViewController: BaseViewController { private let rootView = OnboardingView(step: .first) - private let viewModel: OnboardingViewModel - - init(viewModel: OnboardingViewModel) { - self.viewModel = viewModel - super.init(nibName: nil, bundle: nil) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } override func loadView() { view = rootView @@ -44,12 +34,9 @@ extension OnboardingViewController { private func bottomButtonDidTap() { let updateResult = rootView.moveFront() if !updateResult { - let result = viewModel.action(input: .startButtonDidTap) - if result.isCompletedOnboarding { - let viewController = ViewControllerFactory.shared.makeAlarmAuthorizationViewController() - viewController.navigationItem.hidesBackButton = true - self.navigationController?.pushViewController(viewController, animated: false) - } + let viewController = ViewControllerFactory.shared.makeAlarmAuthorizationViewController() + viewController.navigationItem.hidesBackButton = true + self.navigationController?.pushViewController(viewController, animated: false) } } } diff --git a/BeforeGoing/Presentation/Feature/Onboarding/ViewModel/OnboardingViewModel.swift b/BeforeGoing/Presentation/Feature/Onboarding/ViewModel/OnboardingViewModel.swift deleted file mode 100644 index 0c4e1abb..00000000 --- a/BeforeGoing/Presentation/Feature/Onboarding/ViewModel/OnboardingViewModel.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// OnboardingViewModel.swift -// BeforeGoing -// -// Created by APPLE on 11/13/25. -// - -final class OnboardingViewModel: ViewModeling { - - private let useCase: SaveOnboardingCompletedType - - init(useCase: SaveOnboardingCompletedType) { - self.useCase = useCase - } - - enum Input { - case startButtonDidTap - } - - struct Output { - let isCompletedOnboarding: Bool - } - - func action(input: Input) -> Output { - switch input { - case .startButtonDidTap: - let isSaved = useCase.saveOnboardingCompleted() - return .init(isCompletedOnboarding: isSaved) - } - } -} diff --git a/BeforeGoing/Presentation/PresentationDependencyAssembler.swift b/BeforeGoing/Presentation/PresentationDependencyAssembler.swift index 55d16c67..f786e0d6 100644 --- a/BeforeGoing/Presentation/PresentationDependencyAssembler.swift +++ b/BeforeGoing/Presentation/PresentationDependencyAssembler.swift @@ -131,11 +131,6 @@ struct PresentationDependencyAssembler: DependencyAssembler { fatalError() } - guard let saveOnboardingCompletedUseCase = DIContainer.shared.resolve(type: SaveOnboardingCompletedType.self) else { - BeforeGoingLogger.error(BeforeGoingError.diContainerError) - fatalError() - } - DIContainer.shared.register( AgreeItemViewModel( sendAgreeUseCase: agreeTermsUseCase, @@ -213,10 +208,5 @@ struct PresentationDependencyAssembler: DependencyAssembler { ) ) DIContainer.shared.register(ManageScenarioViewModel()) - DIContainer.shared.register( - OnboardingViewModel( - useCase: saveOnboardingCompletedUseCase - ) - ) } } diff --git a/BeforeGoing/Presentation/ViewControllerFactory.swift b/BeforeGoing/Presentation/ViewControllerFactory.swift index 1cfbef08..c2534a5b 100644 --- a/BeforeGoing/Presentation/ViewControllerFactory.swift +++ b/BeforeGoing/Presentation/ViewControllerFactory.swift @@ -35,8 +35,7 @@ final class ViewControllerFactory { } func makeOnboardingViewController() -> OnboardingViewController { - let viewModel = resolveViewModel(OnboardingViewModel.self) - return OnboardingViewController(viewModel: viewModel) + return .init() } func makeModifyNicknameViewController() -> ModifyNameViewController {