From 48ea4e78df8be6712f543604124dfc9cc77ba3b4 Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Fri, 27 Mar 2026 00:39:58 +0900 Subject: [PATCH 01/10] =?UTF-8?q?[FEAT]=20=EC=95=B1=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=20=EC=B2=B4=ED=81=AC=20=EB=B0=8F=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=9C=A0=EB=8F=84=20=ED=8C=9D=EC=97=85=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Atcha-iOS/Base.lproj/LaunchScreen.storyboard | 4 +- .../Presentation/Popup/AtchaPopupInfo.swift | 7 +++ .../Popup/AtchaPopupViewController.swift | 4 +- .../Splash/SplashViewController.swift | 51 +++++++++++++++---- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/Atcha-iOS/Base.lproj/LaunchScreen.storyboard b/Atcha-iOS/Base.lproj/LaunchScreen.storyboard index 82c0f0b6..cb2b988e 100644 --- a/Atcha-iOS/Base.lproj/LaunchScreen.storyboard +++ b/Atcha-iOS/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + diff --git a/Atcha-iOS/Presentation/Popup/AtchaPopupInfo.swift b/Atcha-iOS/Presentation/Popup/AtchaPopupInfo.swift index 14f80ec8..75c1a8e8 100644 --- a/Atcha-iOS/Presentation/Popup/AtchaPopupInfo.swift +++ b/Atcha-iOS/Presentation/Popup/AtchaPopupInfo.swift @@ -18,6 +18,8 @@ enum AtcahPopuInfo { case arrive case scheduledArrive case serverError + case update_essential + case update_recommended var title: String { switch self { @@ -31,6 +33,8 @@ enum AtcahPopuInfo { case .arrive: return "목적지 부근에 도착해\n안내를 종료합니다" case .scheduledArrive: return "예정된 도착 시간이 지나\n알람이 자동으로 종료됐어요" case .serverError: return "잠시 후 다시 시도해주세요\n앗차팀에서 확인 및 대응 중입니다" + case .update_essential: return "더 좋아진 앗차를 사용하기 위해\n업데이트가 필요해요" + case .update_recommended: return "더 좋아진 앗차를 사용하기 위해\n업데이트를 권장해요" } } @@ -46,6 +50,8 @@ enum AtcahPopuInfo { case .arrive: return "확인" case .scheduledArrive: return "닫기" case .serverError: return "확인" + case .update_essential: return "업데이트" + case .update_recommended: return "업데이트" } } @@ -68,6 +74,7 @@ enum AtcahPopuInfo { switch self { case .alarm, .re_register: return "돌아가기" case .course: return "돌아가기" + case .update_recommended: return "나중에" default: return "취소" } } diff --git a/Atcha-iOS/Presentation/Popup/AtchaPopupViewController.swift b/Atcha-iOS/Presentation/Popup/AtchaPopupViewController.swift index ed0b20d2..10d0cd7d 100644 --- a/Atcha-iOS/Presentation/Popup/AtchaPopupViewController.swift +++ b/Atcha-iOS/Presentation/Popup/AtchaPopupViewController.swift @@ -80,7 +80,7 @@ final class AtchaPopupViewController: BaseViewController { confirmButton.setAttributedTitle(confirmAttr, for: .normal) confirmButton.backgroundColor = info.confrimBackgroundColor - if info != .alarmTimeout || info != .arrive || info != .serverError || info != .scheduledArrive { + if info != .alarmTimeout || info != .arrive || info != .serverError || info != .scheduledArrive || info != .update_essential { let cancelAttr = AtchaFont.B5_SB_14(info.cancelTitle, color: info.cancelForegroundColor) cancelButton.setAttributedTitle(cancelAttr, for: .normal) cancelButton.backgroundColor = info.cancelBackgroundColor @@ -93,7 +93,7 @@ final class AtchaPopupViewController: BaseViewController { $0.removeFromSuperview() } - if info == .alarmTimeout || info == .arrive || info == .serverError || info == .scheduledArrive { + if info == .alarmTimeout || info == .arrive || info == .serverError || info == .scheduledArrive || info == .update_essential { buttonStackView.addArrangedSubview(confirmButton) } else { buttonStackView.addArrangedSubview(cancelButton) diff --git a/Atcha-iOS/Presentation/Splash/SplashViewController.swift b/Atcha-iOS/Presentation/Splash/SplashViewController.swift index 412c0578..d249af19 100644 --- a/Atcha-iOS/Presentation/Splash/SplashViewController.swift +++ b/Atcha-iOS/Presentation/Splash/SplashViewController.swift @@ -26,7 +26,6 @@ final class SplashViewController: BaseViewController { setupUI() setupBindings() - viewModel.makeInitialFlow() } private func setupUI() { @@ -41,8 +40,8 @@ final class SplashViewController: BaseViewController { make.edges.equalToSuperview() } logoStackView.snp.makeConstraints { make in - make.centerX.equalTo(view.safeAreaLayoutGuide) - make.centerY.equalTo(view.safeAreaLayoutGuide) + make.centerX.equalToSuperview() + make.centerY.equalToSuperview() } } @@ -75,15 +74,16 @@ final class SplashViewController: BaseViewController { } private func updateAppVersion(_ version: String) { - let severVersion: String = version - let appVersion: String = AppInfoProvider.versionWithV - - if isVersion(severVersion, lessThan: appVersion) { - viewModel.updateAppVersion(version: appVersion) - } else { - print("강제 업데이트 표출해주세요!!") + let serverVersion = version + let appVersion = AppInfoProvider.versionWithV + + // 서버 버전이 앱 버전보다 높다면 업데이트가 필요한 상황 + if isVersion(appVersion, lessThan: serverVersion) { + showUpdatePopup(isEssential: true) + } else { + viewModel.makeInitialFlow() + } } - } /// lhs < rhs 인지 비교 (서버 버전 < 앱 버전?) private func isVersion(_ lhs: String, lessThan rhs: String) -> Bool { @@ -99,4 +99,33 @@ final class SplashViewController: BaseViewController { } return false } + + private func showUpdatePopup(isEssential: Bool) { + // 1. 팝업 뷰모델 생성 (info 타입은 프로젝트 정의에 맞게 조절하세요) + let popupVM = AtchaPopupViewModel(info: isEssential ? .update_essential : .update_recommended) + let popupVC = AtchaPopupViewController(viewModel: popupVM) + + // 2. [업데이트하기] 버튼 로직 + popupVC.confirmButton.addAction(UIAction { _ in + let appID = "6747877903" + if let url = URL(string: "itms-apps://itunes.apple.com/app/id\(appID)"), + UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + }, for: .touchUpInside) + + // 3. [닫기/취소] 버튼 로직 + popupVC.cancelButton.addAction(UIAction { [weak self, weak popupVC] _ in + popupVC?.dismiss(animated: false) + + if isEssential { + print("필수 업데이트입니다. 진행할 수 없습니다.") + } else { + self?.viewModel.makeInitialFlow() + } + }, for: .touchUpInside) + + popupVC.modalPresentationStyle = .overFullScreen + present(popupVC, animated: false) + } } From bdc5f9c27ca79771dcc12d949c2d0299365602fa Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Sat, 28 Mar 2026 22:24:04 +0900 Subject: [PATCH 02/10] =?UTF-8?q?[FEAT]=20LaunchType=20=EB=8F=84=EC=9E=85?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A7=84=EC=9E=85=20=EA=B2=BD=EB=A1=9C=EB=B3=84?= =?UTF-8?q?=20=EC=8A=A4=ED=94=8C=EB=9E=98=EC=8B=9C=20=EB=8C=80=EA=B8=B0=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EC=B0=A8=EB=B3=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Atcha-iOS.xcodeproj/project.pbxproj | 12 +++---- Atcha-iOS/App/AppFlowCoordinator.swift | 11 +++--- .../App/DIContainer/AppCompositionRoot.swift | 4 +-- .../App/DIContainer/CoordinatorFactory.swift | 6 ++-- .../Splash/SplashDIContainer.swift | 10 +++--- Atcha-iOS/App/SceneDelegate.swift | 3 +- .../Network/Token/SessionController.swift | 2 ++ .../Intro/IntroViewController.swift | 5 --- .../Coordinator/SplashCoordinator.swift | 6 ++-- .../Splash/SplashViewController.swift | 17 ---------- .../Presentation/Splash/SplashViewModel.swift | 34 +++++++++++++++---- .../User/Withdraw/WithdrawViewModel.swift | 1 + 12 files changed, 61 insertions(+), 50 deletions(-) diff --git a/Atcha-iOS.xcodeproj/project.pbxproj b/Atcha-iOS.xcodeproj/project.pbxproj index 5f03f507..e257cc61 100644 --- a/Atcha-iOS.xcodeproj/project.pbxproj +++ b/Atcha-iOS.xcodeproj/project.pbxproj @@ -2516,7 +2516,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482; FRAMEWORK_SEARCH_PATHS = ( @@ -2539,7 +2539,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.9.5; + MARKETING_VERSION = 1.9.6; PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2563,7 +2563,7 @@ CODE_SIGN_ENTITLEMENTS = "Atcha-iOS/Atcha-iOS.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = 23SCTLK482; EXCLUDED_ARCHS = ""; FRAMEWORK_SEARCH_PATHS = ( @@ -2586,7 +2586,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.9.5; + MARKETING_VERSION = 1.9.6; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2611,7 +2611,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482; FRAMEWORK_SEARCH_PATHS = ( @@ -2634,7 +2634,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.9.5; + MARKETING_VERSION = 1.9.6; PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Atcha-iOS/App/AppFlowCoordinator.swift b/Atcha-iOS/App/AppFlowCoordinator.swift index dd2d147a..613ccccc 100644 --- a/Atcha-iOS/App/AppFlowCoordinator.swift +++ b/Atcha-iOS/App/AppFlowCoordinator.swift @@ -25,7 +25,7 @@ class AppFlowCoordinator { self.container = container } - func startApp() { + func startApp(launchType: LaunchType = .main) { let navigationController = UINavigationController() window.rootViewController = navigationController window.makeKeyAndVisible() @@ -35,11 +35,14 @@ class AppFlowCoordinator { DispatchQueue.main.async { AppDIContainer.shared.tokenStorage.clearAllTokens() UserDefaultsWrapper.shared.set(false, forKey: UserDefaultsWrapper.Key.hasSeenIntro.rawValue) - self?.startApp() + self?.startApp(launchType: .fast) } } - let splashCoordinator = container.makeSplashCoordinator(navigationController: navigationController) + let splashCoordinator = container.makeSplashCoordinator( + navigationController: navigationController, + launchType: launchType + ) splashCoordinator.routerHandler = { [weak self] router in guard let self = self else { return } switch router { @@ -114,7 +117,7 @@ class AppFlowCoordinator { mainCoordinator.withdrawFinish = { [weak self] in DispatchQueue.main.async { // 앱 데이터를 다 지웠으니, 스플래시부터 앱을 아예 새로 시작(리부팅)합니다! - self?.startApp() + self?.startApp(launchType: .fast) } } diff --git a/Atcha-iOS/App/DIContainer/AppCompositionRoot.swift b/Atcha-iOS/App/DIContainer/AppCompositionRoot.swift index adc27d04..fb4299fd 100644 --- a/Atcha-iOS/App/DIContainer/AppCompositionRoot.swift +++ b/Atcha-iOS/App/DIContainer/AppCompositionRoot.swift @@ -57,8 +57,8 @@ extension AppCompositionRoot: SplashCoordinatorFactory, MainCoordinatorFactory, LockScreenCoordinatorFactory, IntroCoordinatorFactory { - func makeSplashCoordinator(navigationController: UINavigationController) -> SplashCoordinator { - return splashDIContainer.makeSplashCoordinator(navigationController: navigationController) + func makeSplashCoordinator(navigationController: UINavigationController, launchType: LaunchType) -> SplashCoordinator { + return splashDIContainer.makeSplashCoordinator(navigationController: navigationController, launchType: launchType) } func makeLoginCoordinator(navigationController: UINavigationController) -> LoginCoordinator { diff --git a/Atcha-iOS/App/DIContainer/CoordinatorFactory.swift b/Atcha-iOS/App/DIContainer/CoordinatorFactory.swift index 43ed90b9..2573e102 100644 --- a/Atcha-iOS/App/DIContainer/CoordinatorFactory.swift +++ b/Atcha-iOS/App/DIContainer/CoordinatorFactory.swift @@ -13,7 +13,7 @@ import Foundation /// to avoid service locator style lookups and to enable constructor injection of dependencies. protocol SplashCoordinatorFactory { - func makeSplashCoordinator(navigationController: UINavigationController) -> SplashCoordinator + func makeSplashCoordinator(navigationController: UINavigationController, launchType: LaunchType) -> SplashCoordinator } protocol LoginCoordinatorFactory { @@ -45,8 +45,8 @@ extension AppDIContainer: SplashCoordinatorFactory, MainCoordinatorFactory, LockScreenCoordinatorFactory, IntroCoordinatorFactory { - func makeSplashCoordinator(navigationController: UINavigationController) -> SplashCoordinator { - return splashDIContainer.makeSplashCoordinator(navigationController: navigationController) + func makeSplashCoordinator(navigationController: UINavigationController, launchType: LaunchType) -> SplashCoordinator { + return splashDIContainer.makeSplashCoordinator(navigationController: navigationController, launchType: launchType) } func makeLoginCoordinator(navigationController: UINavigationController) -> LoginCoordinator { diff --git a/Atcha-iOS/App/DIContainer/Splash/SplashDIContainer.swift b/Atcha-iOS/App/DIContainer/Splash/SplashDIContainer.swift index c523b766..2132b7b2 100644 --- a/Atcha-iOS/App/DIContainer/Splash/SplashDIContainer.swift +++ b/Atcha-iOS/App/DIContainer/Splash/SplashDIContainer.swift @@ -32,12 +32,13 @@ final class SplashDIContainer { return UpdateAppVersionUseCaseImpl(repository: repository) } - func makeSplashViewModel() -> SplashViewModel { + func makeSplashViewModel(launchType: LaunchType) -> SplashViewModel { return SplashViewModel( fetchUserUseCase: makeFetchUserUseCase(), checkAppVersionUseCase: makeCheckAppVersionUseCase(), updateAppVersionUseCase: makeUpdateAppVersionUseCase(), - tokenStorage: tokenStorage + tokenStorage: tokenStorage, + launchType: launchType ) } @@ -45,8 +46,9 @@ final class SplashDIContainer { return SplashViewController(viewModel: viewModel) } - func makeSplashCoordinator(navigationController: UINavigationController) -> SplashCoordinator { + func makeSplashCoordinator(navigationController: UINavigationController, launchType: LaunchType) -> SplashCoordinator { SplashCoordinator(navigationController: navigationController, - diContainer: self) + diContainer: self, + launchType: launchType) } } diff --git a/Atcha-iOS/App/SceneDelegate.swift b/Atcha-iOS/App/SceneDelegate.swift index 6c61134c..939cc36b 100644 --- a/Atcha-iOS/App/SceneDelegate.swift +++ b/Atcha-iOS/App/SceneDelegate.swift @@ -17,9 +17,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) + var launchType: LaunchType = .main appFlowCoordinator = AppFlowCoordinator(window: window, container: diContainer) - appFlowCoordinator?.startApp() + appFlowCoordinator?.startApp(launchType: launchType) self.window = window } diff --git a/Atcha-iOS/Core/Network/Token/SessionController.swift b/Atcha-iOS/Core/Network/Token/SessionController.swift index 3d82a51e..1b306b7d 100644 --- a/Atcha-iOS/Core/Network/Token/SessionController.swift +++ b/Atcha-iOS/Core/Network/Token/SessionController.swift @@ -26,6 +26,8 @@ final class SessionController { storage.clearRefreshToken() UserDefaultsWrapper.shared.removeAll() AppDIContainer.shared.locationStateHolder.clear() + UserDefaultsWrapper.shared.set(true, forKey: UserDefaultsWrapper.Key.isGuest.rawValue) + UserDefaultsWrapper.shared.set(false, forKey: UserDefaultsWrapper.Key.hasSeenIntro.rawValue) // 로그인 화면으로 DispatchQueue.main.async { [weak self] in diff --git a/Atcha-iOS/Presentation/Intro/IntroViewController.swift b/Atcha-iOS/Presentation/Intro/IntroViewController.swift index 77855fc2..2f823535 100644 --- a/Atcha-iOS/Presentation/Intro/IntroViewController.swift +++ b/Atcha-iOS/Presentation/Intro/IntroViewController.swift @@ -178,11 +178,6 @@ final class IntroViewController: BaseViewController { label.snp.makeConstraints { make in make.center.equalToSuperview() } - - button.snp.makeConstraints { make in - make.horizontalEdges.equalToSuperview().inset(15) - make.height.equalTo(56) - } } } diff --git a/Atcha-iOS/Presentation/Splash/Coordinator/SplashCoordinator.swift b/Atcha-iOS/Presentation/Splash/Coordinator/SplashCoordinator.swift index 2d496307..bc8dee6c 100644 --- a/Atcha-iOS/Presentation/Splash/Coordinator/SplashCoordinator.swift +++ b/Atcha-iOS/Presentation/Splash/Coordinator/SplashCoordinator.swift @@ -11,16 +11,18 @@ import Foundation final class SplashCoordinator { private let navigationController: UINavigationController private let diContainer: SplashDIContainer + private let launchType: LaunchType var routerHandler: ((SplashRouter) -> Void)? - init(navigationController: UINavigationController, diContainer: SplashDIContainer) { + init(navigationController: UINavigationController, diContainer: SplashDIContainer, launchType: LaunchType) { self.navigationController = navigationController self.diContainer = diContainer + self.launchType = launchType } func start() { - let viewModel = diContainer.makeSplashViewModel() + let viewModel = diContainer.makeSplashViewModel(launchType: launchType) let viewController = diContainer.makeSplashViewController(viewModel: viewModel) viewModel.routerHandler = { [weak self] router in self?.routerHandler?(router) diff --git a/Atcha-iOS/Presentation/Splash/SplashViewController.swift b/Atcha-iOS/Presentation/Splash/SplashViewController.swift index d249af19..2ebb5155 100644 --- a/Atcha-iOS/Presentation/Splash/SplashViewController.swift +++ b/Atcha-iOS/Presentation/Splash/SplashViewController.swift @@ -46,23 +46,6 @@ final class SplashViewController: BaseViewController { } private func setupBindings() { - viewModel.$isLoading - .receive(on: DispatchQueue.main) - .sink { isLoading in - // 로딩 UI 표시/숨김 - print("isLoading: \(isLoading)") - } - .store(in: &cancellables) - - viewModel.$errorMessage - .compactMap { $0 } - .receive(on: DispatchQueue.main) - .sink { message in - // Alert 띄우기 - print("Error: \(message)") - } - .store(in: &cancellables) - viewModel.$appVersionInfo .compactMap { $0 } .receive(on: DispatchQueue.main) diff --git a/Atcha-iOS/Presentation/Splash/SplashViewModel.swift b/Atcha-iOS/Presentation/Splash/SplashViewModel.swift index a346a080..e9d1cfd4 100644 --- a/Atcha-iOS/Presentation/Splash/SplashViewModel.swift +++ b/Atcha-iOS/Presentation/Splash/SplashViewModel.swift @@ -7,9 +7,21 @@ import Foundation +enum LaunchType { + case main // 일반 진입 (1.5초 대기) + case fast // 빠른 진입 (0초 대기) - 푸시 알림 등 + + var minimumDelay: Double { + switch self { + case .main: return 1.2 + case .fast: return 0.8 + } + } +} + final class SplashViewModel: BaseViewModel { @Published private(set) var appVersionInfo: String? - + private let launchType: LaunchType private let fetchUserUseCase: FetchUserUseCase private let checkAppVersionUseCase: CheckAppVersionUseCase private let updateAppVersionUseCase: UpdateAppVersionUseCase @@ -20,24 +32,34 @@ final class SplashViewModel: BaseViewModel { init(fetchUserUseCase: FetchUserUseCase, checkAppVersionUseCase: CheckAppVersionUseCase, updateAppVersionUseCase: UpdateAppVersionUseCase, - tokenStorage: TokenStorage) { + tokenStorage: TokenStorage, + launchType: LaunchType) { self.fetchUserUseCase = fetchUserUseCase self.checkAppVersionUseCase = checkAppVersionUseCase self.updateAppVersionUseCase = updateAppVersionUseCase self.tokenStorage = tokenStorage + self.launchType = launchType super.init() self.checkAppVersion() } func checkAppVersion() { - print(#function) Task { - setLoading(true) - defer { self.setLoading(false) } + let startTime = Date() + let minDelay = launchType.minimumDelay + do { let versionInfo = try await checkAppVersionUseCase.execute() - appVersionInfo = versionInfo + let elapsed = Date().timeIntervalSince(startTime) + if elapsed < minDelay { + let remaining = minDelay - elapsed + try? await Task.sleep(nanoseconds: UInt64(remaining * 1_000_000_000)) + } + + await MainActor.run { + self.appVersionInfo = versionInfo + } } catch { handleError(error) } diff --git a/Atcha-iOS/Presentation/User/Withdraw/WithdrawViewModel.swift b/Atcha-iOS/Presentation/User/Withdraw/WithdrawViewModel.swift index 9ea0f983..d91516f5 100644 --- a/Atcha-iOS/Presentation/User/Withdraw/WithdrawViewModel.swift +++ b/Atcha-iOS/Presentation/User/Withdraw/WithdrawViewModel.swift @@ -37,6 +37,7 @@ final class WithdrawViewModel: BaseViewModel { tokenStorage.clearAllTokens() UserDefaultsWrapper.shared.removeAll() locationStateHolder.clear() + UserDefaultsWrapper.shared.set(true, forKey: UserDefaultsWrapper.Key.isGuest.rawValue) UserDefaultsWrapper.shared.set(false, forKey: UserDefaultsWrapper.Key.hasSeenIntro.rawValue) signOutFinish?() } catch { From 4370b4a8ac225a63082765079eac3ac2b141437d Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Tue, 31 Mar 2026 12:56:43 +0900 Subject: [PATCH 03/10] =?UTF-8?q?[FEAT]=20=EC=95=8C=EB=9E=8C=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=A0=84=EC=97=90=EB=8F=84=20=EC=BA=90=EB=A6=AD?= =?UTF-8?q?=ED=84=B0=20=ED=84=B0=EC=B9=98=20=EC=8B=9C=20=EC=A0=90=ED=94=84?= =?UTF-8?q?=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Atcha-iOS/Presentation/Location/MainViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Atcha-iOS/Presentation/Location/MainViewController.swift b/Atcha-iOS/Presentation/Location/MainViewController.swift index 87ceab62..17db4dda 100644 --- a/Atcha-iOS/Presentation/Location/MainViewController.swift +++ b/Atcha-iOS/Presentation/Location/MainViewController.swift @@ -956,6 +956,8 @@ extension MainViewController { } @objc private func handleBallonTap() { + safeStartJump() + // 알람 등록 후(departure 상태)일 때만 반응 guard viewModel.bottomType == .departure else { return } From 7c1b02b5435d2e6430599b230a0297f23ff3bd69 Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Tue, 31 Mar 2026 12:57:56 +0900 Subject: [PATCH 04/10] =?UTF-8?q?[BUGFIX]=20=EA=B0=95=EC=A0=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Atcha-iOS/Presentation/Splash/SplashViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Atcha-iOS/Presentation/Splash/SplashViewController.swift b/Atcha-iOS/Presentation/Splash/SplashViewController.swift index 2ebb5155..f91f003b 100644 --- a/Atcha-iOS/Presentation/Splash/SplashViewController.swift +++ b/Atcha-iOS/Presentation/Splash/SplashViewController.swift @@ -62,7 +62,7 @@ final class SplashViewController: BaseViewController { // 서버 버전이 앱 버전보다 높다면 업데이트가 필요한 상황 if isVersion(appVersion, lessThan: serverVersion) { - showUpdatePopup(isEssential: true) + showUpdatePopup(isEssential: false) } else { viewModel.makeInitialFlow() } From afecbebf0d8d90b6518ff24dc7705c359aff6f0f Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Tue, 31 Mar 2026 12:59:53 +0900 Subject: [PATCH 05/10] =?UTF-8?q?[BUGFIX]=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EC=84=B1=EA=B3=B5=20=EC=8B=9C=20=EB=94=94?= =?UTF-8?q?=EC=8A=A4=EC=BD=94=EB=93=9C=20=EC=95=8C=EB=9E=8C=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/Network/Token/TokenInterceptor.swift | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/Atcha-iOS/Core/Network/Token/TokenInterceptor.swift b/Atcha-iOS/Core/Network/Token/TokenInterceptor.swift index 97361f57..4d8c69f0 100644 --- a/Atcha-iOS/Core/Network/Token/TokenInterceptor.swift +++ b/Atcha-iOS/Core/Network/Token/TokenInterceptor.swift @@ -77,8 +77,6 @@ final class TokenInterceptor: RequestInterceptor, @unchecked Sendable { return } - let actualHeaders = request.request?.allHTTPHeaderFields ?? [:] - guard let refreshToken = tokenStorage.refreshToken else { SessionController.shared.expireAndRouteToLogin() completion(.doNotRetry) @@ -109,24 +107,7 @@ final class TokenInterceptor: RequestInterceptor, @unchecked Sendable { waiters.forEach { $0(.doNotRetry) } return } - - let successBody = [ - "newAccessToken": p.accessToken, - "newRefreshToken": p.refreshToken - ] - - DiscordWebhookManager.shared.sendErrorLog( - baseURL: NetworkConstant.baseURL, - statusCode: 200, - method: "GET", - path: "/auth/reissue", - responseCode: "REISSUE_SUCCESS", - message: "토큰 재발급에 성공하여 새로운 토큰을 수신했습니다.", - requestHeaders: actualHeaders, - requestBody: successBody, // 여기서 받은 토큰 정보를 보냅니다. - requestParameters: nil - ) - + self.tokenStorage.accessToken = p.accessToken self.tokenStorage.refreshToken = p.refreshToken From 8c8a4f92943c7a94097769c00f2e2b3e05516312 Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Tue, 31 Mar 2026 13:05:19 +0900 Subject: [PATCH 06/10] =?UTF-8?q?[BUGFIX]=20=EC=95=8C=EB=9E=8C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=A7=84=EB=8F=99=20=EC=98=B5=EC=85=98?= =?UTF-8?q?=20=EC=9E=AC=EC=84=A0=ED=83=9D=20=EC=8B=9C=20=ED=94=BC=EB=93=9C?= =?UTF-8?q?=EB=B0=B1=EC=9D=B4=20=EC=98=A4=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PushAlarmSheet/PushAlarmSheetViewController.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Atcha-iOS/Presentation/Setting/PushAlarmSheet/PushAlarmSheetViewController.swift b/Atcha-iOS/Presentation/Setting/PushAlarmSheet/PushAlarmSheetViewController.swift index 92d09f0e..77a50c59 100644 --- a/Atcha-iOS/Presentation/Setting/PushAlarmSheet/PushAlarmSheetViewController.swift +++ b/Atcha-iOS/Presentation/Setting/PushAlarmSheet/PushAlarmSheetViewController.swift @@ -203,13 +203,10 @@ final class PushAlarmSheetViewController: BaseViewController Date: Tue, 31 Mar 2026 13:11:06 +0900 Subject: [PATCH 07/10] =?UTF-8?q?[BUGFIX]=20=EC=A7=91=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=8B=9C=20=EB=A7=88=EC=BB=A4=20=EB=9D=BC=EC=9D=B4=ED=8C=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting_home_mark.imageset/Contents.json | 23 ++++++++++++++++++ .../setting_home_mark.png | Bin 0 -> 1631 bytes .../setting_home_mark@2x.png | Bin 0 -> 3011 bytes .../setting_home_mark@3x.png | Bin 0 -> 4417 bytes .../User/Home/HomeFindViewController.swift | 2 +- 5 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/Contents.json create mode 100644 Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark.png create mode 100644 Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark@2x.png create mode 100644 Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark@3x.png diff --git a/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/Contents.json b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/Contents.json new file mode 100644 index 00000000..e334e205 --- /dev/null +++ b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "setting_home_mark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "setting_home_mark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "setting_home_mark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark.png b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark.png new file mode 100644 index 0000000000000000000000000000000000000000..91b6e6759a6bb6f433c90f9a3fd9439f0f73d104 GIT binary patch literal 1631 zcmV-l2B7(gP)~iJC&VRZTG-)OcvQ7`#CY#*48;y%A`m#)Fk^qcI2=S`MfbaJI1xqzT=X zK!DO6e&07c!|rr;cV;@>?k4>tvu|hS&CKun-tYJ3{TV?%peRa&DA<|Fh!!Cfb6Y5?RfE0qUTh)}ze+6a`& zpeW7Vnif1;M&spUHEr%JKfAM3#$eIAC{2CbY8>RhbyR-v%s0Z}D5cZ0qlGdR6p%H( zpOzW4a>Yd03P9Dd@n2Gf01?sXS~ZoL9ba(HdO#0Y`25nfG#*KDU~jbs_JSa0<436S zj0X`-yK~%nFwceH_gpC@BY*i24~4_)L^^#fVIDF5WL@W9s(jd$<{MR2O<~7$p-d*& zN7Vs$Woq^rJF*62MYl$G1p@6dN|>)n`! zXE#XEzkYt+O-UCdlgYFq23p7iPra(>X;)097lxnpUa*If@I@kxV zeAY%CpLjBX0s&G2Jd{)1TvWFvL@ys`lQrQv(-muR|OlFKFw2 zRNe<0?t|0|YUFe-@Y|U-+y?9JQ686BFp8<`7}Q?C5?dL0O{fOpJa-2Ah%_LtM#0CNs2>_!F7K8 z_%y9r9g@Kv&T%U*0&3jkMr(7l=XYrgeAh}W>mqQc)PH)*1cR}E`pAUo_y7Dn&4n>6 z^W&ZnVQr3`0s$yCSKVhJ?zcjAptXSj1hyd>z?D!VC;;Pnh=4HEsv&yG1>iyFjwNl+ z!AJXM5ru4jBz|GZmwFjx|Um_XJiv;SM7H0fIvs7 zf3cj*gLeHp6rU5vE>Y*)^ML9$<2``e&dH-;L>!0@wSE#h%-uBcdJ-7&rNaz zPlAmL9>DKr0bjeh5JC6O$c~cvtlb2>g^hPWE~rTaxx3PrvA9QIu9L?Cv*Z%^bSVn? zv(Z`s7?0XP>J}d4H)aPk%_Ce3ajGh`v1Ib{*6fQ+j8c`MPs^KQ;=7nWOi@)4O5j%t zz}V7IR1I+O4j2<$IenpOs)dlbeqaiymRjKPd)_L}g1OOHtf|~<0d7!}$xDs72Nrav zg~)VKWx5h3ciXK87fi3G7@o?$%6}_rj+6s;m#R*jv>vxzAK-cTv1oK1+$&BM0Q{a^ z$9BD<;0jGDbuCc|B4GUFZ>)fvsdml1NM1(UsW#+6wBmdnN==)C{z}( zf?wO^WSP~~Lbswk($9VHrJRPB?;=$#>@rrod}^R@=&%s#CjZF3MDHFn7A~B!n2$Xa zeO^`ca;jmz&MmL#^0C?;B2v}}b1S~f}S^~JwgpZWagH^B}iL8xBqwB}Hi++WC#+Ns7F8Vii zG+=j*yT`k^dwhg^#8&|hy4`tSn)rdwF(CUxN|6i^EKZY;_$)vurSrI3OeuxW0yNd1 zJkIf1!2BQ;pHoteNm;+}wHf6prCY`3l=MORM1p+8_xFH4r?h^EjeVT-fW|=PByW4T d2^}P#xd&ADZHbuuyyyS`002ovPDHLkV1l~${4xLl literal 0 HcmV?d00001 diff --git a/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark@2x.png b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0d03202ea19bc5c40ac9adbffd5d476953164ea4 GIT binary patch literal 3011 zcmV;!3q16RP)LyZJ>W0on zO+{3RtXm~&YkQ~XyEAt^yF0r(fA7rN_b08qJKo)~&v)-R=bpLujzKxaG|dvx@Hjsm zZ#1Z47`91-hr!25KD^Z3hlQWEZCHf@)%f`W(aM5lS#`=G2IU0D;6q3F=>glONgSd? zu@_jB1r}wVXk^Z^-m)kMWGaB$%1_v~eVAWPl3L~rgHB~FMkWH7=FUkr`eUTt=-rrs z7?yQqh7_Yr08eXwm=bp{iD9u2(=0@l6rn@_)4c0p_P>u)XW+~$%7jQ&0N2$#NBM2> z{YYJiR0LpmW7_u6Q~dM@tphkKs}>3)(`f|jAxaq2+;QBt3kYW8v`*k&!2e6*NVz<^ zRIlGypae;*2O!8c8imIU!}ukwW7wrWT_`lBlN#71B7p01&e7od`mk_p7@11KB^4+p zrnzgJjsH4r9ODCSaLecLzf(+N3E=qq28=%_>1C3b_}^gzib3Qc?B@1=A`J)UGJ^9f z6oE(rxW<2)G%m*dQAG$Xfa7*_%kKmtj9M(h8=*cG=FLvP6+U0n1;Z#zSk_|Tu|nV= z?4+8FGo)Hd@_<@3mra$ggL0^0Ya00J(Jqg0m^e*4)B)%@FQrYn?X#tXk z1M=$j_OU@n574bYHN>=>_JbV)Xy2b6?wqI=S_I(Mk68>RY0|>PR2}bu76G`-9P8?i zra$hK%qGns?2dZhB~9vZFfPkr?CAkRlcZ6^S}^vyAnkq>;ZDaF2p5!h_fz@C9rXPt z_cvdkoO+4UK-GDGbVI|=OSSIj*y#gw=hyEL&;R-R%i__G;?~=`{%+G3NBHqMibDOp zn}N@hEF;J`t3rTAgAN$ES*#SE&G(e(M}Iy*QFvnPdCGGop{*^)KL3Rg>c!HvTfS~=?*nGjrCfdW1FD_> zfN}&vZ)wKqDv~3>^*8H*VvSmc#}NaR zw*LLccecES7|1?quO{C*H+y;-gzfk}AM-W_zxSjJ1i<2FHK6tHIcjzsN9yV$l^0z?&Q}nHqIeA^qYOnY%B{J4cjbm{U=<1j|l#G7*V#ki!aoKTLAFW z_#FgFioHHU-}m#~lqU)WGKUSr<{(U|0>l2`@G#sR5B9vJ_pvxkH+ynzla-)oTb)kSXgueJDQ_~q+wE>T!ytdKnotsm#Y zIq_WnT|fNex5VF-q|1v)dIG?pUPHqGfBEseZz_n^_w3=bA}#=7uxmFdC@?Als@M29 z|ML(1Tl~c1G@o0F03ZO~@9ixaP#UHbLFL5pU7uA$o?3da7|A1>3uU+)vI z;n!b$^fliegkvYa{gMdM$cPXh!t}H>NL>IgV+Xf#5d0d&rM-YAdm4gzkU%PfZabfR zv@Cj?N+1CG>Ayj=e z49JS$ZgzcVOG}LK1Fme5d(U}J9K5k3W$H$yvh#Hw66CrZjf&|*=f2x~9i)bj>?Uf4 z6Z9fce2h8t0E-kA zIt{oAv4m1Y%~n*6#y_i~__hDUTE!J0AGo;xCa=P z%Ig6V;dB3%&d}dmhQ7q9W>7HJkOIiwk!G4&C>Ric65anMD9AuweDngP0kRsI>5Opz z4qDtpu$G{KC>SFw3}aK%6!vWjM0?FX484pBcOqi|Dxetr{paj85ewQ!<7nw31{__h zpn^+20|ShF7`sL_TLFH5;hE) zPqQqY1sIs2qUz0VB)trepnXw6>o*KTxPEtLoSFrB2x_Hm@h3jPjf5F2;k;kjT@(Y8v`m?lly zRJ|`cCZ+2|+NomIYFD;<4|J>(<^jSpq)7$r#NKhN{eU|rKrh&co3W-zN9zY2J-`XZ zIu<02U+WjgyL~{%f<*Cc{h;f9LEH--Buyf8YyF_B0NCM)KR830Sg;MNy>5ZhXWPov zN64ABqKSsZqu!qJwCAO;I#Vu>UZ-_(FBsKu>wm-2HZP(^>K3M4{3s76$-*;eF-FhGd5VAA{`Hc;ND***iC-)_Vo#q+V=GT zUyN&+r18bM&bt_Do>k}xK~7Nq5*BdXPnsq?aOI5WO6WU_M4FLRumArQEy5a(@!yGb zPo2n9;v5l1vA}9Wj}hGIC6Vu`6ML5+H!$PYCMInxI5R9vh4uOE=vx^^BR{t?1f27V zd!}%AT=!SgFv4~250lKTZ4=+9%$?@A9&h|mcWOJy-rPCydFf7?TAXG7i2BjG*a{70 zd3HHR`xVwAeK)1ZI_M1+E=+a1`$ZBc*(-4Xg%H=pqvrNuP`X*=00?0@({fg-N!>BK zADQeH>THv59Jg&d7lSx9`W*k5PSe$NJvU_t#t+V}y+VvhQiyBxS(UBb_wS&buqFmJ zG&T06u$QUjPClOJ3rsUv8r@571ZW2{^qf6Sk;-CcN3ppeHkmcanYGtGa}NeZ@WfAx zKT~toKw}y1%0gZY-cD`>=trfpqh)nDUw<9Q|0R)9geE##fQ-=sWQ-OdW3&JnqXozq zEkMR-0WwAlkTF_-jL`yQj20kcv;Y~S1;`jJK*nUVUu6t%OYl;uj0^iMSImmVQL|hg zHR|;nZ_xlTFc&f0`KQ=eaI()^6PvfGf`Jggoq>(Xzd<(;No3}-3UV)i+w!q4E$r?s zEW*e{-g`psgRnd8J<8TOd;xzs^}SHzl4}8+30$#e(M=*~Q;x~C0F4G6rr6lWDaYhm zfF$npE$>{NS8^T$5}6%mQjXCh;f%?<02al@&h=Oz=P>{iY2$u}p#_MJQKk6gdQ@?Q}hh&0sMr)YJ8 z0%S#4dgK)0@?QxB1GlKbtq&b(TgK!rKQc~bvCu9Kp8>D&rmBqJk81z`002ovPDHLk FV1iw(uB-q6 literal 0 HcmV?d00001 diff --git a/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark@3x.png b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/setting_home_mark.imageset/setting_home_mark@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..24f4a8f8bf1bd2f9892f26dc815c2aff1b694fc3 GIT binary patch literal 4417 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, backButton) mapContainerView.delegate = self - flagImageView.image = UIImage.settingLocationMark + flagImageView.image = UIImage.settingHomeMark backButton.setImage(UIImage.chevronLeft, for: .normal) backButton.tintColor = .white backButton.backgroundColor = .black From 4ab7eec239005f33f5992530286a85bde33ee163 Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Tue, 31 Mar 2026 13:50:12 +0900 Subject: [PATCH 08/10] =?UTF-8?q?[FEAT]=20=EA=B2=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=20=EB=A7=90=ED=92=8D=EC=84=A0=20=ED=84=B0?= =?UTF-8?q?=EC=B9=98=20=EC=9D=B8=ED=84=B0=EB=9E=99=EC=85=98=20=EB=B0=8F=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Location/MainViewController.swift | 65 ++++++++++++++++++- .../Login/LoginViewController.swift | 1 + 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Atcha-iOS/Presentation/Location/MainViewController.swift b/Atcha-iOS/Presentation/Location/MainViewController.swift index 17db4dda..202fc622 100644 --- a/Atcha-iOS/Presentation/Location/MainViewController.swift +++ b/Atcha-iOS/Presentation/Location/MainViewController.swift @@ -78,6 +78,7 @@ final class MainViewController: BaseViewController, if viewModel.isGuest { return false } return viewModel.isGuideActiveInSession } + private var guestTapCount = 0 // MARK: - Life Cycle @@ -152,6 +153,12 @@ final class MainViewController: BaseViewController, } } } + + self.guestTapCount = 0 + if viewModel.isGuest { + ballonView.isHidden = true + ballonView.alpha = 0 + } } override func viewDidAppear(_ animated: Bool) { @@ -309,6 +316,7 @@ extension MainViewController { observeArrival() observeScheduledArrivalTimeout() observeAlarmTimeout() + observeLoginDismissal() } private func bindPermissionAlert() { @@ -958,6 +966,11 @@ extension MainViewController { @objc private func handleBallonTap() { safeStartJump() + if viewModel.isGuest { + handleGuestBallonTap() + return + } + // 알람 등록 후(departure 상태)일 때만 반응 guard viewModel.bottomType == .departure else { return } @@ -1016,6 +1029,31 @@ extension MainViewController { postAlarmTapIndex += 1 } } + + private func handleGuestBallonTap() { + if guestTapCount == 0 { + // 첫 번째 터치: "궁금하면 로그인 해봐요!" + guestTapCount = 1 + + ballonView.layer.removeAllAnimations() + ballonView.isHidden = false + ballonView.alpha = 1 + + ballonView.setupTitle(topMessage: nil, bottomMessage: "궁금하면 로그인 해봐요!") + ballonView.animateStaggered(secondaryDelay: 0, fade: 0.25) + + } else { + // 두 번째 터치: 로그인 시트 노출 + guestTapCount = 0 // 카운트 리셋 + + // [중요] 말풍선을 즉시 숨김 상태로 만들어야 시트가 내려간 뒤 다시 Persistent(???원) 메시지가 나타납니다. + ballonView.layer.removeAllAnimations() + ballonView.isHidden = true + ballonView.alpha = 0 + + presentLoginAlert() + } + } } // MARK: - Map Delegate & Gesture @@ -1235,6 +1273,10 @@ extension MainViewController { private func showOrUpdatePersistentBalloon(isFirstVisit: Bool, isServiceRegion: Bool?, fareStr: String?) { guard !isShowingToast else { return } + if viewModel.isGuest && guestTapCount == 1 { + return + } + // [수정] 우리가 정의한 로그인 기반 가이드 로직 적용 let showGuideLine = shouldShowMapGuide let topText: String? = showGuideLine ? "지도를 움직여 출발지를 설정해요" : nil @@ -1244,7 +1286,7 @@ extension MainViewController { } else { if viewModel.isGuest { // 비로그인: 가이드 없이 ???원만 노출 - ballonView.separationTitle(grayMessage: "여기서 막차 놓치면 택시비 ", whiteMessage: "약 ???원", showTopLine: false) + ballonView.separationTitle(grayMessage: "여기서 막차 놓치면 택시비 ", whiteMessage: "???원", showTopLine: false) } else { // 로그인 상태 if let fare = fareStr { @@ -1317,4 +1359,25 @@ extension MainViewController { balloonHideWorkItem = workItem DispatchQueue.main.asyncAfter(deadline: .now() + 2.0, execute: workItem) } + + private func observeLoginDismissal() { + NotificationCenter.default.publisher(for: NSNotification.Name("LoginSheetDismissed")) + .receive(on: RunLoop.main) + .sink { [weak self] _ in + guard let self = self else { return } + + // 로그인 시트가 내려갔으니 guestTapCount도 초기화해주는 게 자연스러워요 + self.guestTapCount = 0 + + // 현재 검색 모드라면 다시 고정 말풍선 노출 + if self.viewModel.bottomType == .search || self.viewModel.bottomType == nil { + self.showOrUpdatePersistentBalloon( + isFirstVisit: self.isFirstVisit, + isServiceRegion: self.latestIsServiceRegion, + fareStr: self.latestFareString + ) + } + } + .store(in: &cancellables) + } } diff --git a/Atcha-iOS/Presentation/Login/LoginViewController.swift b/Atcha-iOS/Presentation/Login/LoginViewController.swift index a4c63712..a1b4c52f 100644 --- a/Atcha-iOS/Presentation/Login/LoginViewController.swift +++ b/Atcha-iOS/Presentation/Login/LoginViewController.swift @@ -245,6 +245,7 @@ extension LoginViewController { self.containerView.transform = CGAffineTransform(translationX: 0, y: self.sheetHeight) }) { _ in self.dismiss(animated: false) { + NotificationCenter.default.post(name: NSNotification.Name("LoginSheetDismissed"), object: nil) self.viewModel.loginCancelled?() } } From c4ae611ec21156b3bffb25f894af55c6d494c1b9 Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Tue, 31 Mar 2026 13:53:55 +0900 Subject: [PATCH 09/10] =?UTF-8?q?[FEAT]=20=EB=A7=90=ED=92=8D=EC=84=A0=20?= =?UTF-8?q?=ED=84=B0=EC=B9=98=20=EC=9D=B8=ED=84=B0=EB=9E=99=EC=85=98=20?= =?UTF-8?q?=EB=B0=8F=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Atcha-iOS/Presentation/Location/MainViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Atcha-iOS/Presentation/Location/MainViewController.swift b/Atcha-iOS/Presentation/Location/MainViewController.swift index 202fc622..c5a7d096 100644 --- a/Atcha-iOS/Presentation/Location/MainViewController.swift +++ b/Atcha-iOS/Presentation/Location/MainViewController.swift @@ -236,6 +236,8 @@ final class MainViewController: BaseViewController, flagImageView.image = UIImage.settingLocationMark atchaImageView.isUserInteractionEnabled = true atchaImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleBallonTap))) + ballonView.isUserInteractionEnabled = true + ballonView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleBallonTap))) ballonView.isHidden = true ballonView.alpha = 0 From b058c858d39e6691a6d3bf6f80b96b3c8de15410 Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Tue, 31 Mar 2026 13:57:15 +0900 Subject: [PATCH 10/10] =?UTF-8?q?[SETTING]=20=EB=B9=8C=EB=93=9C=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B4=88=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Atcha-iOS.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Atcha-iOS.xcodeproj/project.pbxproj b/Atcha-iOS.xcodeproj/project.pbxproj index e257cc61..862e7052 100644 --- a/Atcha-iOS.xcodeproj/project.pbxproj +++ b/Atcha-iOS.xcodeproj/project.pbxproj @@ -2516,7 +2516,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482; FRAMEWORK_SEARCH_PATHS = ( @@ -2563,7 +2563,7 @@ CODE_SIGN_ENTITLEMENTS = "Atcha-iOS/Atcha-iOS.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 23SCTLK482; EXCLUDED_ARCHS = ""; FRAMEWORK_SEARCH_PATHS = ( @@ -2611,7 +2611,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482; FRAMEWORK_SEARCH_PATHS = (