From 70046026cdbf30dd395ce4a946ab77721df75fec Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Mon, 27 Apr 2026 14:48:15 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[FEAT]=20=EC=9E=A0=EA=B8=88=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=95=8C=EB=9E=8C=20=EC=B7=A8=EC=86=8C=20=ED=8C=9D?= =?UTF-8?q?=EC=97=85=20=EB=B0=8F=20=EC=B7=A8=EC=86=8C=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EB=B2=84=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Lock/alarm_cancel.imageset/Contents.json | 23 ++++++++++++++++++ .../alarm_cancel.imageset/alarm_cancel.png | Bin 0 -> 438 bytes .../alarm_cancel.imageset/alarm_cancel@2x.png | Bin 0 -> 624 bytes .../alarm_cancel.imageset/alarm_cancel@3x.png | Bin 0 -> 835 bytes 4 files changed, 23 insertions(+) create mode 100644 Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/Contents.json create mode 100644 Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel.png create mode 100644 Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel@2x.png create mode 100644 Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel@3x.png diff --git a/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/Contents.json b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/Contents.json new file mode 100644 index 00000000..7ef96f32 --- /dev/null +++ b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "alarm_cancel.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "alarm_cancel@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "alarm_cancel@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel.png b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..2c95d12d340d73d3b2544ee7bc8c50cd083decd9 GIT binary patch literal 438 zcmV;n0ZIOeP)89$FqS(&N_xuyv02d|1x4d}@=&4w~@;(;E zEdQEkKAXMXfl!p=^2un=pi%7#tCcAwCKO9KyO#evGJ=O%DY%!Kgm7XYJ}q2b`@vQ9 z^*tEe{8|ems#|ME!{P0ssy=`C@JZ>%=rD;DT5I=9?_F=S8q=E# zPa4Ebe5K%Rry!wlx+L&64j9TZWjSpzB}cHoi8f08!d*usaJLb|g`Z9zKMpap1Wt1< z-HciScXf%0mcpebx({4p(k=4#KA3aizQWC@)YUbe&TZL?tNXw=29wf1>izp52z)LO gF^8TyHUB)d246!ihm6PilK=n!07*qoM6N<$f)6#a&j0`b literal 0 HcmV?d00001 diff --git a/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel@2x.png b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7549f6b6c0df3b63af77e1283d4d9d9a2cea2dbc GIT binary patch literal 624 zcmV-$0+0QPP)Y@o5QSF~o*{GzYQZJY0VU7^p|i^f-2x@h z0j;P65+^p2tXsjzWA6Xn%EOGl1c7dM-y0Cx)e$W$EIc)Z?SxVvuWftX>Eo(eos`-) zG$Wz)3rld_b>HXVu)clNl;z82E)$meZ$tgd-?gPV#dZ5DSi=c*ViLw6p6j>m*Uq%w zV!(*F35$3q76YqeQBY$zA!foQUTdkU?qrH9&B^wcqG&gBned6%T9kTkOtIE~p)v8H z?)&Q8_r|Xja$)C)=QZRlAZH?1Jg=jmLUQAt9b)DUlcxQsOzDG6*rD52VC%4$8!5;`aJ*4k4cN5CD&f$D$zdoQpt&nm~%@ zd<4X20xez>GYn|)U%)}2y17}$R<6Zjz0000< KMNUMnLSTX%!3=r; literal 0 HcmV?d00001 diff --git a/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel@3x.png b/Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Lock/alarm_cancel.imageset/alarm_cancel@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f73169859b43c2680bd547d3f1f5390b9d8c3809 GIT binary patch literal 835 zcmV-J1HAl+P)Eqh_A6H-)?|&~st}oz)`JbE|e985n zat-Erv-d*q$SMvTCjdryLr!Eh&%gF_F|vZD$$fw0-)1FcLgpk< zQU` z2~E|mq*6J~{a*tC@_kU8KnOP%7O$ilIo^BH+{l8`M65~Tl!JYs5KEG{XCU<_zPQj)YBi~}8zkfb38V?if4C27gQc+e3JNt$vnCUmB! zByBku7Z~Uv$$}h=4NSC1vLXlL10yw(;xAU@;26M6sicQj6LN4I5TH;}7AZM676{Qx zlB66Q4+LqIBrONW1Yv3=X~@BGL4i_9T5=+!q&)|VoPxITXSRgJg33pXdY|(Qm6DS9 zB3&sbAudVuYrauVLR6C2jbbM`2{B3Ri*)&er>zPutDu^va%*L7)^tbhQ@N$$BwNC) zHDzH%5=+XpS3bvrr0z-k-pMX)NnOi6T$w;kN!<#E+-XuRNnJ|MQaNM|NzI!Vq!aAY zlA5-^XjLC1B~@>r(k{(NNvhg{XQgf>A*py1r=_A^niHp_qHVy|our4PO-)w2BgwU?(yx0>oUpt(`=HHU2ys0MSMjRy{^uXqA$EQKgTY`h7z_r|+yVCD%hrAbvx@)# N002ovPDHLkV1f_}U=9EP literal 0 HcmV?d00001 From 19393f160f28de929a6d16d39f1cb5d1392e68af Mon Sep 17 00:00:00 2001 From: JaeWoong Eum Date: Mon, 27 Apr 2026 15:52:20 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[FEAT]=20=EC=95=B1=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EC=96=B4=20API=20=EA=B8=B0=EB=B0=98=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=EB=A1=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=ED=8C=9D=EC=97=85=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + Atcha-iOS.xcodeproj/project.pbxproj | 12 +-- .../Lock/LockViewController.swift | 46 ++++++++- .../Presentation/Main/MainCoordinator.swift | 7 +- .../Presentation/Popup/AtchaPopupInfo.swift | 7 +- .../Splash/SplashViewController.swift | 98 +++++++++++-------- 6 files changed, 121 insertions(+), 51 deletions(-) diff --git a/.gitignore b/.gitignore index 0896441e..cc5cc875 100644 --- a/.gitignore +++ b/.gitignore @@ -114,4 +114,6 @@ StageConfig.xcconfig LiveConfig.xcconfig BaseConfig.xcconfig +.claude + Atcha-iOS/DesignSource/AtchaImage/Icon.xcassets/Onboarding/.DS_Store diff --git a/Atcha-iOS.xcodeproj/project.pbxproj b/Atcha-iOS.xcodeproj/project.pbxproj index 16314675..353cec7a 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 = 11; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482; FRAMEWORK_SEARCH_PATHS = ( @@ -2539,7 +2539,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.9.6; + MARKETING_VERSION = 1.9.7; 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 = 11; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 23SCTLK482; EXCLUDED_ARCHS = ""; FRAMEWORK_SEARCH_PATHS = ( @@ -2586,7 +2586,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.9.6; + MARKETING_VERSION = 1.9.7; 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 = 11; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482; FRAMEWORK_SEARCH_PATHS = ( @@ -2634,7 +2634,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.9.6; + MARKETING_VERSION = 1.9.7; PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Atcha-iOS/Presentation/Lock/LockViewController.swift b/Atcha-iOS/Presentation/Lock/LockViewController.swift index d90fbce8..927cfb46 100644 --- a/Atcha-iOS/Presentation/Lock/LockViewController.swift +++ b/Atcha-iOS/Presentation/Lock/LockViewController.swift @@ -15,6 +15,7 @@ final class LockViewController: BaseViewController { private let titleLabel: UILabel = UILabel() private let taxiFareLabel: UILabel = UILabel() private let startButton: AtchaButton = AtchaButton(text: "출발하기", size: .h52, style: .filled(.primary)) + private let cancelImageView: UIImageView = UIImageView() private let detailRouteButton: AtchaButton = AtchaButton(text: "더 늦은 경로 확인하기", size: .h52, style: .filled(.opacity)) private let bottomStack: UIStackView = UIStackView() private var lottieAnimationView: LottieAnimationView = LottieAnimationView(name: "Alarm") @@ -71,7 +72,7 @@ final class LockViewController: BaseViewController { // MARK: - Lock UI private func setupUI() { - view.addSubViews(backgroundImageView, lottieAnimationView, gradientView, logoImageView, titleLabel, taxiFareLabel, bottomStack) + view.addSubViews(backgroundImageView, lottieAnimationView, gradientView, logoImageView, titleLabel, taxiFareLabel, bottomStack, cancelImageView) backgroundImageView.image = UIImage.lockBackground gradient.colors = [ @@ -96,6 +97,9 @@ final class LockViewController: BaseViewController { startButton.addTarget(self, action: #selector(startTapped), for: .touchUpInside) + cancelImageView.image = UIImage.alarmCancel + cancelImageView.isUserInteractionEnabled = true + cancelImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(cancelAlarmTapped))) detailRouteButton.addTarget(self, action: #selector(detailRouteTapped), @@ -137,6 +141,12 @@ final class LockViewController: BaseViewController { make.leading.equalToSuperview().offset(20) make.trailing.equalToSuperview().inset(20) } + + cancelImageView.snp.makeConstraints { make in + make.top.equalTo(view.safeAreaLayoutGuide.snp.top).inset(18) + make.trailing.equalToSuperview().inset(16) + make.size.equalTo(24) + } } @objc private func startTapped() { @@ -175,6 +185,10 @@ final class LockViewController: BaseViewController { ) } + @objc private func cancelAlarmTapped() { + showAlarmCancelPopup() + } + private func observeAlarmTimeout() { NotificationCenter.default.publisher(for: NSNotification.Name("alarmDidTimeout")) .receive(on: RunLoop.main) @@ -184,4 +198,34 @@ final class LockViewController: BaseViewController { } .store(in: &cancellables) } + + private func showAlarmCancelPopup() { + AlarmManager.shared.stopAlarm() + + let popupVM = AtchaPopupViewModel(info: .alarm_cancel) + let popupVC = AtchaPopupViewController(viewModel: popupVM) + + popupVC.cancelButton.addAction(UIAction { [weak popupVC] _ in + popupVC?.dismiss(animated: false) + }, for: .touchUpInside) + + popupVC.confirmButton.addAction(UIAction { [weak self, weak popupVC] _ in + guard let self else { return } + popupVC?.dismiss(animated: false) + + self.viewModel.cancelLockScreenTimer() + AlarmManager.shared.stopAlarm() + AlarmManager.shared.removeAllAlarmNotificationsExceptAutoStop() + + UserDefaultsWrapper.shared.set( + false, + forKey: UserDefaultsWrapper.Key.departureAlarmDidFire.rawValue + ) + + viewModel.routerHandler?(.dismissLockScreen) + }, for: .touchUpInside) + + popupVC.modalPresentationStyle = .overFullScreen + present(popupVC, animated: false) + } } diff --git a/Atcha-iOS/Presentation/Main/MainCoordinator.swift b/Atcha-iOS/Presentation/Main/MainCoordinator.swift index 8c5e379d..0bca15ec 100644 --- a/Atcha-iOS/Presentation/Main/MainCoordinator.swift +++ b/Atcha-iOS/Presentation/Main/MainCoordinator.swift @@ -320,7 +320,12 @@ final class MainCoordinator: NSObject { context: .afterReigster)) } case .dismissLockScreen: - self?.navigationController.dismiss(animated: true) + self?.mainViewModel?.stopAlarmTimeoutTimer() + self?.navigationController.dismiss(animated: true) { [weak self] in + self?.mainViewModel?.alarmDelete() + self?.mainViewModel?.bottomType = .search + self?.mainViewModel?.removeLegInfoAndAddress() + } default: do {} } } diff --git a/Atcha-iOS/Presentation/Popup/AtchaPopupInfo.swift b/Atcha-iOS/Presentation/Popup/AtchaPopupInfo.swift index 75c1a8e8..3c1c1ff1 100644 --- a/Atcha-iOS/Presentation/Popup/AtchaPopupInfo.swift +++ b/Atcha-iOS/Presentation/Popup/AtchaPopupInfo.swift @@ -20,6 +20,7 @@ enum AtcahPopuInfo { case serverError case update_essential case update_recommended + case alarm_cancel var title: String { switch self { @@ -35,6 +36,7 @@ enum AtcahPopuInfo { case .serverError: return "잠시 후 다시 시도해주세요\n앗차팀에서 확인 및 대응 중입니다" case .update_essential: return "더 좋아진 앗차를 사용하기 위해\n업데이트가 필요해요" case .update_recommended: return "더 좋아진 앗차를 사용하기 위해\n업데이트를 권장해요" + case .alarm_cancel: return "알람을 종료할까요?" } } @@ -52,12 +54,13 @@ enum AtcahPopuInfo { case .serverError: return "확인" case .update_essential: return "업데이트" case .update_recommended: return "업데이트" + case .alarm_cancel: return "종료하기" } } var confrimBackgroundColor: UIColor { switch self { - case .alarm, .re_register, .course, .arrive: return .main + case .alarm, .re_register, .course, .arrive, .alarm_cancel: return .main case .alarmTimeout, .serverError, .scheduledArrive: return .gray910 default: return .white } @@ -73,7 +76,7 @@ enum AtcahPopuInfo { var cancelTitle: String { switch self { case .alarm, .re_register: return "돌아가기" - case .course: return "돌아가기" + case .course, .alarm_cancel: return "돌아가기" case .update_recommended: return "나중에" default: return "취소" } diff --git a/Atcha-iOS/Presentation/Splash/SplashViewController.swift b/Atcha-iOS/Presentation/Splash/SplashViewController.swift index f91f003b..8c6551f9 100644 --- a/Atcha-iOS/Presentation/Splash/SplashViewController.swift +++ b/Atcha-iOS/Presentation/Splash/SplashViewController.swift @@ -46,27 +46,20 @@ final class SplashViewController: BaseViewController { } private func setupBindings() { - viewModel.$appVersionInfo - .compactMap { $0 } - .receive(on: DispatchQueue.main) - .sink { [weak self] versionInfo in - guard let self else { return } - updateAppVersion(versionInfo) - } - .store(in: &cancellables) + fetchAppStoreVersion() } - private func updateAppVersion(_ version: String) { - let serverVersion = version - let appVersion = AppInfoProvider.versionWithV - - // 서버 버전이 앱 버전보다 높다면 업데이트가 필요한 상황 - if isVersion(appVersion, lessThan: serverVersion) { - showUpdatePopup(isEssential: false) - } else { - viewModel.makeInitialFlow() - } - } +// private func updateAppVersion(_ version: String) { +// let serverVersion = version +// let appVersion = AppInfoProvider.versionWithV +// +// // 서버 버전이 앱 버전보다 높다면 업데이트가 필요한 상황 +// if isVersion(appVersion, lessThan: serverVersion) { +// showUpdatePopup(isEssential: false) +// } else { +// viewModel.makeInitialFlow() +// } +// } /// lhs < rhs 인지 비교 (서버 버전 < 앱 버전?) private func isVersion(_ lhs: String, lessThan rhs: String) -> Bool { @@ -84,31 +77,54 @@ final class SplashViewController: BaseViewController { } private func showUpdatePopup(isEssential: Bool) { - // 1. 팝업 뷰모델 생성 (info 타입은 프로젝트 정의에 맞게 조절하세요) - let popupVM = AtchaPopupViewModel(info: isEssential ? .update_essential : .update_recommended) - let popupVC = AtchaPopupViewController(viewModel: popupVM) + // 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) - // 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) + if isEssential { + print("필수 업데이트입니다. 진행할 수 없습니다.") + } else { + self?.viewModel.makeInitialFlow() + } + }, for: .touchUpInside) + + popupVC.modalPresentationStyle = .overFullScreen + present(popupVC, animated: false) + } + + private func fetchAppStoreVersion() { + guard let url = URL(string: "https://itunes.apple.com/lookup?id=6747877903&country=kr") else { return } + + URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in + guard let self, + let data, + let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let results = json["results"] as? [[String: Any]], + let appStoreVersion = results.first?["version"] as? String else { return } - // 3. [닫기/취소] 버튼 로직 - popupVC.cancelButton.addAction(UIAction { [weak self, weak popupVC] _ in - popupVC?.dismiss(animated: false) + DispatchQueue.main.async { + let appVersion = AppInfoProvider.versionWithV + let storeVersion = "v\(appStoreVersion)" - if isEssential { - print("필수 업데이트입니다. 진행할 수 없습니다.") + if self.isVersion(appVersion, lessThan: storeVersion) { + self.showUpdatePopup(isEssential: false) } else { - self?.viewModel.makeInitialFlow() + self.viewModel.makeInitialFlow() } - }, for: .touchUpInside) - - popupVC.modalPresentationStyle = .overFullScreen - present(popupVC, animated: false) - } + } + }.resume() + } }