Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Atcha-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2468,7 +2468,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482;
FRAMEWORK_SEARCH_PATHS = (
Expand All @@ -2491,7 +2491,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.7;
MARKETING_VERSION = 1.8;
PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -2515,7 +2515,7 @@
CODE_SIGN_ENTITLEMENTS = "Atcha-iOS/Atcha-iOS.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 23SCTLK482;
EXCLUDED_ARCHS = "";
FRAMEWORK_SEARCH_PATHS = (
Expand All @@ -2538,7 +2538,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.7;
MARKETING_VERSION = 1.8;
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -2563,7 +2563,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 15;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 23SCTLK482;
FRAMEWORK_SEARCH_PATHS = (
Expand All @@ -2586,7 +2586,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.7;
MARKETING_VERSION = 1.8;
PRODUCT_BUNDLE_IDENTIFIER = com.atcha.iOS;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
3 changes: 3 additions & 0 deletions Atcha-iOS/Core/Manager/AlarmManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ final class AlarmManager {

/// 완전 정지: 예약/타이머/진동/알림/오디오 모두 끊기
func stopAlarm(keepSilent: Bool = false) {
autoStopWorkItem?.cancel()
autoStopWorkItem = nil

shouldKeepBackgroundAudio = false
pendingStartWorkItem?.cancel()
pendingStartWorkItem = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ final class CourseSearchViewController: BaseViewController<CourseSearchViewModel
private let noSearchStack: UIStackView = UIStackView()
private let noSearchImageView: UIImageView = UIImageView()
private let noSearchLabel: UILabel = UILabel()
private var previousLatestId: String?

override func viewDidLoad() {
super.viewDidLoad()
Expand Down Expand Up @@ -284,13 +285,37 @@ final class CourseSearchViewController: BaseViewController<CourseSearchViewModel

// MARK: - Course Snapshot 갱신
private func applySnapshot(courses: [CourseUIModel]) {
var snapshot = Snapshot()
// 1. 지금 들어온 데이터 중 1등(가장 늦은 막차)이 누구인지 확인
let currentLatestId = viewModel.latestDepartureCourseId(in: courses)

var snapshot = Snapshot()
snapshot.appendSections([.courseList])
if !courses.isEmpty {
snapshot.appendItems(courses, toSection: .courseList)
snapshot.appendItems(courses, toSection: .courseList)

// 2. 만약 1등이 바뀌었다면? (예: 원래 A였는데 더 늦은 B가 들어옴)
if let lastId = previousLatestId, lastId != currentLatestId {

// [중요] 모든 셀이 아니라, 딱 '이전 1등'과 '현재 1등'만 다시 그리라고 명령합니다.
var itemsToUpdate: [CourseUIModel] = []

// 이전 1등이었던 셀 (이제 배지를 떼야 함)
if let oldItem = courses.first(where: { $0.id == lastId }) {
itemsToUpdate.append(oldItem)
}
// 새로운 1등인 셀 (이제 배지를 달아야 함)
if let newItem = courses.first(where: { $0.id == currentLatestId }) {
itemsToUpdate.append(newItem)
}

if #available(iOS 15.0, *), !itemsToUpdate.isEmpty {
snapshot.reconfigureItems(itemsToUpdate)
}
}

// 3. 상태 저장 및 스냅샷 적용
previousLatestId = currentLatestId

// animatingDifferences를 true로 두면 1 ( ) 2 사이에 부드럽게 slide-in 됩니다.
dataSource.apply(snapshot, animatingDifferences: true)
}

Expand Down
19 changes: 12 additions & 7 deletions Atcha-iOS/Presentation/Location/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -401,13 +401,13 @@ extension MainViewController {
if viewModel.isGuest {
presentLoginAlert()
} else {
AmplitudeManager.shared.track(.course_search_click)

guard let startCoord = viewModel.currentLocation else {
guard let startCoord = mapContainerView.tMapWrapper.mapView.getCenter() else {
view.showToast(message: "현재 위치를 확인 중이에요. 잠시 후 다시 시도해 주세요.")
return
}

AmplitudeManager.shared.track(.course_search_click)

let wrapper = UserDefaultsWrapper.shared
let endLatStr = wrapper.string(forKey: UserDefaultsWrapper.Key.homeLat.rawValue) ?? "37.554722"
let endLonStr = wrapper.string(forKey: UserDefaultsWrapper.Key.homeLon.rawValue) ?? "126.970833"
Expand All @@ -424,7 +424,7 @@ extension MainViewController {
}

viewModel.handleRoute(route: .courseSearch(
startLat: "", startLon: "", startAddress: ""
startLat: String(startCoord.latitude), startLon: String(startCoord.longitude), startAddress: ""
))
}
}
Expand Down Expand Up @@ -509,9 +509,14 @@ extension MainViewController {
mapContainerView.clearMapView()
mapContainerView.beforeUserMarker()

if let coord = viewModel.selectedLocation ?? viewModel.currentLocation {
mapContainerView.setupCenter(location: coord)
if let currentCoord = viewModel.currentLocation {
// 현재 좌표가 있다면 즉시 이동
mapContainerView.setupCenter(location: currentCoord)
viewModel.selectedLocation = currentCoord // 주소 검색 결과도 현위치로 갱신
shouldCenterToCurrentLocationOnce = false
} else {
// 아직 좌표가 없다면, 다음 위치 업데이트 시점에 이동하도록 예약
shouldCenterToCurrentLocationOnce = true
viewModel.setupLocation()
}

Expand Down Expand Up @@ -575,7 +580,7 @@ extension MainViewController {
.compactMap { $0 }
.receive(on: RunLoop.main)
.sink { _ in
// 👉 뷰모델에서 알아서 주소를 검색하므로 뷰컨트롤러는 카메라를 건드리지 않음!
// 뷰모델에서 알아서 주소를 검색하므로 뷰컨트롤러는 카메라를 건드리지 않음!
}
.store(in: &cancellables)
}
Expand Down
42 changes: 11 additions & 31 deletions Atcha-iOS/Presentation/Location/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -485,15 +485,18 @@ extension MainViewModel {
address: lastReverseGeocode?.address,
radius: lastReverseGeocode?.radius)))

case .courseSearch:
guard let currentLocation else { return }
let lat: String = "\(currentLocation.latitude)"
let lon: String = "\(currentLocation.longitude)"
let address: String = address ?? ""
case .courseSearch(let startLat, let startLon, _):

if lastReverseGeocode != nil {
routeHandler?(.courseSearch(startLat: "\(lastReverseGeocode?.lat ?? 0.0)",
startLon: "\(lastReverseGeocode?.lon ?? 0.0)",
startAddress: lastReverseGeocode?.address ?? ""))
} else {
routeHandler?(.courseSearch(startLat: startLat,
startLon: startLon,
startAddress: address ?? ""))
}

routeHandler?(.courseSearch(startLat: lat,
startLon: lon,
startAddress: address))
case .myPage:
routeHandler?(.myPage)

Expand Down Expand Up @@ -605,30 +608,7 @@ extension MainViewModel {
}
}

// MARK: - 2분 타임아웃
extension MainViewModel {
// private func startAlarmTimeoutTimer() {
// alarmTimeoutCancellable?.cancel()
//
// let task = Task { [weak self] in
// guard let self else { return }
//
// do {
// try await Task.sleep(nanoseconds: 120 * 1_000_000_000)
// } catch {
// return
// }
//
// guard !Task.isCancelled else { return }
//
// await MainActor.run {
// self.routeHandler?(.dismissLockScreen)
// }
// }
//
// alarmTimeoutCancellable = AnyCancellable { task.cancel() }
// }

func stopAlarmTimeoutTimer() {
alarmTimeoutCancellable?.cancel()
alarmTimeoutCancellable = nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import TMapSDK
import SnapKit

final class TMapContainerView: UIView {
private var tMapWrapper: TMapWrapper!
var tMapWrapper: TMapWrapper!
var gestureTargetView: UIView { tMapWrapper.mapView }

var onUserInteraction: (() -> Void)?
Expand Down
1 change: 1 addition & 0 deletions Atcha-iOS/Presentation/Lock/LockViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ final class LockViewController: BaseViewController<LockViewModel> {
@objc private func startTapped() {
viewModel.cancelLockScreenTimer()
AlarmManager.shared.stopAlarm()
AlarmManager.shared.removeAllAlarmNotificationsExceptAutoStop()

let wrapper = UserDefaultsWrapper.shared
let legInfo = wrapper.object(forKey: UserDefaultsWrapper.Key.legInfo.rawValue, of: LegInfo.self)
Expand Down
6 changes: 5 additions & 1 deletion Atcha-iOS/Presentation/Main/MainCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ final class MainCoordinator {
self.navigationController.pushViewController(modifyVC, animated: true)

case .detailRoute(let address, let infos, let context):
if navigationController.topViewController is DetailRouteViewController {
print("이미 상세 경로 화면입니다. 중복 push를 방지합니다.")
return
}
let routeDI = diContainer.makeRouteDIContainer()
let vm = routeDI.makeDetailRouteViewModel(address: address, infos: infos, context: context)
let vc = routeDI.makeDetailRouteViewController(viewModel: vm)
Expand Down Expand Up @@ -367,7 +371,7 @@ extension UINavigationController {
UIView.performWithoutAnimation {
if let target = viewControllers.first(where: { $0 is MainViewController }) {
popToViewController(target, animated: true)
} else {
} else {
popToRootViewController(animated: true)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,35 @@ final class HomeRegisterViewModel: BaseViewModel {
private func setupInitialState() {
let defaults = UserDefaultsWrapper.shared

// 1. 이름과 주소가 이미 있다면 (직접 등록했던 유저) 바로 표시
if let buildingName = defaults.string(forKey: UserDefaultsWrapper.Key.buildingName.rawValue),
let address = defaults.string(forKey: UserDefaultsWrapper.Key.homeAddress.rawValue) {
self.selectedState = .selected(name: buildingName, address: address)
} else {
if let lat = defaults.double(forKey: UserDefaultsWrapper.Key.homeLat.rawValue),
let lon = defaults.double(forKey: UserDefaultsWrapper.Key.homeLon.rawValue) {
Task {
if let location = try? await fetchCurrentAddress(lat: lat, lon: lon) {
let name = location.name ?? ""
let address = location.address ?? ""

defaults.set(name, forKey: UserDefaultsWrapper.Key.buildingName.rawValue)
defaults.set(address, forKey: UserDefaultsWrapper.Key.homeAddress.rawValue)

await MainActor.run {
self.selectedState = .selected(name: name, address: address)
}
return
}

// 2. 이름은 없지만 좌표는 있다면 (방금 로그인한 유저)
let lat = defaults.double(forKey: UserDefaultsWrapper.Key.homeLat.rawValue)
let lon = defaults.double(forKey: UserDefaultsWrapper.Key.homeLon.rawValue)

if let lat, let lon, lat != 0.0 && lon != 0.0 {
// 이름이 없으니 좌표로 이름을 새로 따와서 채워주기
Task {
if let location = try? await fetchCurrentAddress(lat: lat, lon: lon) {
let name = location.name ?? ""
let addr = location.address ?? ""

// 따온 이름을 저장해두기 (다음엔 1번 조건에서 걸리도록)
defaults.set(name, forKey: UserDefaultsWrapper.Key.buildingName.rawValue)
defaults.set(addr, forKey: UserDefaultsWrapper.Key.homeAddress.rawValue)

await MainActor.run {
self.selectedState = .selected(name: name, address: addr)
}
}
} else {
self.selectedState = LocationSelectionState.none
}
} else {
self.selectedState = LocationSelectionState.none
}
}

Expand Down
Loading