Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0122b75
RENAME: 도메인 엔티티 폴더링
sangYuLv Jul 23, 2026
d4984b6
FEAT: 이동수단에서 자전거 제거
sangYuLv Jul 23, 2026
d1bc918
DESIGN: 출발/도착 장소 입력 UI
sangYuLv Jul 23, 2026
fcec974
DESIGN: 이동수단 선택 영역 UI
sangYuLv Jul 23, 2026
9c63871
DESIGN: 이동 경로 카드 UI
sangYuLv Jul 23, 2026
d635e4f
DESIGN: 날짜 선택 view controller
sangYuLv Jul 23, 2026
79a5716
FEAT: 경로 검색 repository protocol
sangYuLv Jul 23, 2026
89f2d7e
FEAT: Mock 경로 검색 repository
sangYuLv Jul 23, 2026
76d6c09
FEAT: 경로 검색 use case
sangYuLv Jul 23, 2026
ae98579
DESIGN: 경로 검색 view controller
sangYuLv Jul 23, 2026
04ab0a3
FEAT: 날짜 포맷 옵션 추가
sangYuLv Jul 23, 2026
c59e8f3
FEAT: 현재 위치 탐색 흐름 (use case, repository, mock data)
sangYuLv Jul 23, 2026
9c55de3
FEAT: 장소 검색 흐름 (use case, repository, mock data)
sangYuLv Jul 23, 2026
80c20c8
RENAME: 장소 검색 카드 폴더링
sangYuLv Jul 23, 2026
748531d
FEAT: 장소 검색 view model
sangYuLv Jul 23, 2026
f9eb930
FEAT: 장소 검색 카드 실제 사용 방향에 맞게 수정
sangYuLv Jul 23, 2026
97a7e0d
DESIGN: 장소 검색 view controller
sangYuLv Jul 23, 2026
44aa18c
FEAT: 경로 검색 view model
sangYuLv Jul 23, 2026
92daaa6
DESIGN: 스크롤 영역 변경
sangYuLv Jul 23, 2026
ae9c246
FEAT: 경로 검색 view model 바인딩
sangYuLv Jul 23, 2026
f8b6f03
FEAT: 경로 검색 화면에 장소 검색 카드 연결
sangYuLv Jul 23, 2026
77e3167
DESIGN: 장소 검색 카드 활용 방법 수정
sangYuLv Jul 23, 2026
d90208d
DESIGN: 섹션 간격 수정
sangYuLv Jul 23, 2026
26bf55c
DOCS: 레이아웃 코드 주석
sangYuLv Jul 23, 2026
b49556c
FEAT: 출발 시간을 과거로 선택하지 못하도록 설정
sangYuLv Jul 23, 2026
ecb9a7e
DESIGN: 장소 검색 결과에 따른 대체 텍스트 표시
sangYuLv Jul 24, 2026
c45c1af
FIX: mock 데이터 의도에 맞게 수정
sangYuLv Jul 24, 2026
23b838a
FIX: UI 업데이트 메인 스레드 보장되도록 수정
sangYuLv Jul 24, 2026
c1afc08
DESIGN: 출발 시간 설정 시트 디자인 수정
sangYuLv Jul 24, 2026
ee6b4cc
DESIGN: 장소 검색 결과의 emptyResultLabel 폰트 크기 수정
sangYuLv Jul 24, 2026
bf8b0ee
DESIGN: 출발/도착 장소 입력 버튼 영역 확장
sangYuLv Jul 24, 2026
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
22 changes: 22 additions & 0 deletions WhereAreYou/WhereAreYou/Core/Date+Format.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ extension Date {
Self.koreanDateTimeFormatter.string(from: self)
}

var koreanShortDateTimeString: String {
Self.koreanShortDateTimeFormatter.string(from: self)
}

var koreanTimeString: String {
Self.koreanTimeFormatter.string(from: self)
}

private static let koreanDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ko_KR")
Expand All @@ -31,4 +39,18 @@ extension Date {
return formatter
}()

private static let koreanShortDateTimeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ko_KR")
formatter.dateFormat = "M월 d일 (E) a h:mm"
return formatter
}()

private static let koreanTimeFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ko_KR")
formatter.dateFormat = "HH:mm"
return formatter
}()

}
18 changes: 18 additions & 0 deletions WhereAreYou/WhereAreYou/Data/MockLocationRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// MockLocationRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-24.
//

import Foundation

final class MockLocationRepository: LocationRepository {

func getCurrentLocation(completion: @escaping (Result<Coordinate, Error>) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion(.success(Coordinate(latitude: 37.5665, longitude: 126.9780)))
}
}

}
49 changes: 49 additions & 0 deletions WhereAreYou/WhereAreYou/Data/MockPlaceSearchRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// MockPlaceSearchRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-24.
//

import Foundation

final class MockPlaceSearchRepository: PlaceSearchRepository {

func searchPlaces(
keyword: String,
completion: @escaping (Result<[Place], Error>) -> Void
) {
let trimmed = keyword.trimmingCharacters(in: .whitespaces)
let results: [Place]

if trimmed.isEmpty {
results = []
} else {
results = Self.mockPlaces
}

DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
completion(.success(results))
}
}

private static let mockPlaces: [Place] = [
Place(id: "gangnam", name: "강남역", address: "서울 강남구 강남대로 396",
coordinate: Coordinate(latitude: 37.4979, longitude: 127.0276), type: .subway),
Place(id: "hongdae", name: "홍대입구역", address: "서울 마포구 양화로 160",
coordinate: Coordinate(latitude: 37.5571, longitude: 126.9236), type: .subway),
Place(id: "jamsil", name: "잠실역", address: "서울 송파구 올림픽로 지하 265",
coordinate: Coordinate(latitude: 37.5133, longitude: 127.1001), type: .subway),
Place(id: "seoul_station", name: "서울역", address: "서울 용산구 한강대로 405",
coordinate: Coordinate(latitude: 37.5547, longitude: 126.9707), type: .station),
Place(id: "itaewon", name: "이태원역", address: "서울 용산구 이태원로 지하 180",
coordinate: Coordinate(latitude: 37.5345, longitude: 126.9946), type: .subway),
Place(id: "yeouido", name: "여의도역", address: "서울 영등포구 의사당대로 지하 166",
coordinate: Coordinate(latitude: 37.5217, longitude: 126.9244), type: .subway),
Place(id: "busan_station", name: "부산역", address: "부산 동구 중앙대로 206",
coordinate: Coordinate(latitude: 35.1152, longitude: 129.0408), type: .station),
Place(id: "haeundae", name: "해운대역", address: "부산 해운대구 해운대로 지하 772",
coordinate: Coordinate(latitude: 35.1631, longitude: 129.1635), type: .subway),
]

}
98 changes: 98 additions & 0 deletions WhereAreYou/WhereAreYou/Data/MockRouteSearchRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// MockRouteSearchRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-22.
//

import Foundation

final class MockRouteSearchRepository: RouteSearchRepository {

func searchRoutes(
departure: Place,
destination: Place,
departureTime: Date,
transportType: TransportType,
completion: @escaping (Result<[Route], Error>) -> Void
) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let routes = Self.makeMockRoutes(
departure: departure,
destination: destination,
departureTime: departureTime,
transportType: transportType
)
completion(.success(routes))
}
}

private static func makeMockRoutes(
departure: Place,
destination: Place,
departureTime: Date,
transportType: TransportType
) -> [Route] {
let parking = Place(
id: "parking", name: "주차장", address: "",
coordinate: Coordinate(latitude: 0, longitude: 0), type: .other
)
let busStop = Place(
id: "bus_stop", name: "정류장", address: "",
coordinate: Coordinate(latitude: 0, longitude: 0), type: .station
)

switch transportType {
case .car:
return [
Route(
departureTime: departureTime,
arrivalTime: departureTime.addingTimeInterval(285 * 60),
step: [
RouteStep(departurePoint: departure, destination: parking, estimatedTime: 10, distance: 0.5, transportType: .walk),
RouteStep(departurePoint: parking, destination: destination, estimatedTime: 260, distance: 280, transportType: .car),
RouteStep(departurePoint: parking, destination: destination, estimatedTime: 10, distance: 0.5, transportType: .walk)
],
totalDistance: 281
),
Route(
departureTime: departureTime,
arrivalTime: departureTime.addingTimeInterval(285 * 60),
step: [
RouteStep(departurePoint: departure, destination: parking, estimatedTime: 10, distance: 0.5, transportType: .walk),
RouteStep(departurePoint: parking, destination: destination, estimatedTime: 260, distance: 300, transportType: .car),
RouteStep(departurePoint: parking, destination: destination, estimatedTime: 10, distance: 0.8, transportType: .walk)
],
totalDistance: 301.3
)
]

case .transit:
return [
Route(
departureTime: departureTime,
arrivalTime: departureTime.addingTimeInterval(195 * 60),
step: [
RouteStep(departurePoint: departure, destination: busStop, estimatedTime: 5, distance: 0.3, transportType: .walk),
RouteStep(departurePoint: busStop, destination: destination, estimatedTime: 180, distance: 200, transportType: .transit),
RouteStep(departurePoint: busStop, destination: destination, estimatedTime: 10, distance: 0.5, transportType: .walk)
],
totalDistance: 200.8
)
]

case .walk:
return [
Route(
departureTime: departureTime,
arrivalTime: departureTime.addingTimeInterval(600 * 60),
step: [
RouteStep(departurePoint: departure, destination: destination, estimatedTime: 600, distance: 40, transportType: .walk)
],
totalDistance: 40
)
]
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ struct Route {
let arrivalTime: Date
let step: [RouteStep]
let totalDistance: Double

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
// Created by 이상유 on 2026-07-14.
//

enum TransportType {
enum TransportType: CaseIterable {

case walk
case car
case transit
case bicycle

}
12 changes: 12 additions & 0 deletions WhereAreYou/WhereAreYou/Domain/Repository/LocationRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// LocationRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-24.
//

protocol LocationRepository {

func getCurrentLocation(completion: @escaping (Result<Coordinate, Error>) -> Void)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// PlaceSearchRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-24.
//

protocol PlaceSearchRepository {

func searchPlaces(
keyword: String,
completion: @escaping (Result<[Place], Error>) -> Void
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// RouteSearchRepository.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-22.
//

import Foundation

protocol RouteSearchRepository {

func searchRoutes(
departure: Place,
destination: Place,
departureTime: Date,
transportType: TransportType,
completion: @escaping (Result<[Route], Error>) -> Void
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// GetCurrentLocationUseCase.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-24.
//

final class GetCurrentLocationUseCase {

private let repository: LocationRepository

init(repository: LocationRepository) {
self.repository = repository
}

func execute(completion: @escaping (Result<Coordinate, Error>) -> Void) {
repository.getCurrentLocation(completion: completion)
}

}
23 changes: 23 additions & 0 deletions WhereAreYou/WhereAreYou/Domain/UseCase/SearchPlacesUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// SearchPlacesUseCase.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-24.
//

final class SearchPlacesUseCase {

private let repository: PlaceSearchRepository

init(repository: PlaceSearchRepository) {
self.repository = repository
}

func execute(
keyword: String,
completion: @escaping (Result<[Place], Error>) -> Void
) {
repository.searchPlaces(keyword: keyword, completion: completion)
}

}
34 changes: 34 additions & 0 deletions WhereAreYou/WhereAreYou/Domain/UseCase/SearchRoutesUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// SearchRoutesUseCase.swift
// WhereAreYou
//
// Created by 이상유 on 2026-07-22.
//

import Foundation

final class SearchRoutesUseCase {

private let repository: RouteSearchRepository

init(repository: RouteSearchRepository) {
self.repository = repository
}

func execute(
departure: Place,
destination: Place,
departureTime: Date,
transportType: TransportType,
completion: @escaping (Result<[Route], Error>) -> Void
) {
repository.searchRoutes(
departure: departure,
destination: destination,
departureTime: departureTime,
transportType: transportType,
completion: completion
)
}

}
Loading
Loading