diff --git a/Examples/TripKitUIExample/AppDelegate.swift b/Examples/TripKitUIExample/AppDelegate.swift index be9f66312..9374ab793 100644 --- a/Examples/TripKitUIExample/AppDelegate.swift +++ b/Examples/TripKitUIExample/AppDelegate.swift @@ -17,7 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - TripKit.apiKey = ProcessInfo.processInfo.environment["TRIPGO_API_KEY"] ?? "MY_API_KEY" + TripKit.apiKey = ProcessInfo.processInfo.environment["TRIPGO_API_KEY"] ?? preconditionFailure("Either add your TripGo API Key as an environment variable named `TRIPGO_API_KEY` or add it here, replacing this line.") as! String TripKit.prepareForNewSession() return true diff --git a/Examples/TripKitUIExample/Autocompleter/InMemoryFavoriteManager+Autocompleting.swift b/Examples/TripKitUIExample/Autocompleter/InMemoryFavoriteManager+Autocompleting.swift index 8c0875493..ee4263135 100644 --- a/Examples/TripKitUIExample/Autocompleter/InMemoryFavoriteManager+Autocompleting.swift +++ b/Examples/TripKitUIExample/Autocompleter/InMemoryFavoriteManager+Autocompleting.swift @@ -18,7 +18,7 @@ extension InMemoryFavoriteManager: TKAutocompleting { completion(.success(results)) } - func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { guard let favorite = result.object as? Favorite else { preconditionFailure() } diff --git a/Examples/TripKitUIExample/Autocompleter/InMemoryHistoryManager+Autocompleting.swift b/Examples/TripKitUIExample/Autocompleter/InMemoryHistoryManager+Autocompleting.swift index 175de80e2..f526fdbca 100644 --- a/Examples/TripKitUIExample/Autocompleter/InMemoryHistoryManager+Autocompleting.swift +++ b/Examples/TripKitUIExample/Autocompleter/InMemoryHistoryManager+Autocompleting.swift @@ -18,7 +18,7 @@ extension InMemoryHistoryManager: TKAutocompleting { completion(.success(results)) } - func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { guard let history = result.object as? History else { preconditionFailure() } diff --git a/Examples/TripKitUIExample/Autocompleter/InMemoryHistoryManager+Home.swift b/Examples/TripKitUIExample/Autocompleter/InMemoryHistoryManager+Home.swift index 32f73f368..8d5be2c82 100644 --- a/Examples/TripKitUIExample/Autocompleter/InMemoryHistoryManager+Home.swift +++ b/Examples/TripKitUIExample/Autocompleter/InMemoryHistoryManager+Home.swift @@ -57,7 +57,7 @@ extension InMemoryHistoryManager: TKUIHomeComponentViewModel { var nextAction: Signal { selection.map { - .handleSelection($0.annotation, component: self) + .handleSelection(.annotation($0.annotation), component: self) } } diff --git a/Examples/TripKitUIExample/TripKitUIExample.entitlements b/Examples/TripKitUIExample/TripKitUIExample.entitlements index ee95ab7e5..fc07546cf 100644 --- a/Examples/TripKitUIExample/TripKitUIExample.entitlements +++ b/Examples/TripKitUIExample/TripKitUIExample.entitlements @@ -6,5 +6,7 @@ com.apple.security.network.client + com.apple.security.personal-information.location + diff --git a/Examples/TripKitUIExample/ViewController.swift b/Examples/TripKitUIExample/ViewController.swift index 5b08fdb9a..5f9cbad6f 100644 --- a/Examples/TripKitUIExample/ViewController.swift +++ b/Examples/TripKitUIExample/ViewController.swift @@ -80,7 +80,7 @@ extension MainViewController { return } - let resultsController = TKUIAutocompletionViewController(providers: [TKTripGoGeocoder()]) + let resultsController = TKUIAutocompletionViewController(providers: [TKTripGoGeocoder(), TKRouteAutocompleter()]) resultsController.biasMapRect = randomCity.centerBiasedMapRect resultsController.delegate = self @@ -99,25 +99,33 @@ extension MainViewController { extension MainViewController: TKUIAutocompletionViewControllerDelegate { - func autocompleter(_ controller: TKUIAutocompletionViewController, didSelect annotation: MKAnnotation) { - if let stop = annotation as? TKUIStopAnnotation { + func autocompleter(_ controller: TKUIAutocompletionViewController, didSelect selection: TKAutocompletionSelection) { + switch selection { + case let .annotation(stop as TKUIStopAnnotation): dismiss(animated: true) { self.showDepartures(stop: stop) } - } else { + case let .annotation(annotation): print("Selected \(annotation)") + + case let .result(result): + print("Selected \(result.object)") } } - func autocompleter(_ controller: TKUIAutocompletionViewController, didSelectAccessoryFor annotation: MKAnnotation) { - if let stop = annotation as? TKUIStopAnnotation { + func autocompleter(_ controller: TKUIAutocompletionViewController, didSelectAccessoryFor selection: TKAutocompletionSelection) { + switch selection { + case let .annotation(stop as TKUIStopAnnotation): dismiss(animated: true) { self.showDepartures(stop: stop) } + + case let .annotation(annotation): + print("Selected \(annotation)") - } else { - print("Selected accessor for \(annotation)") + case let .result(result): + print("Selected \(result.object)") } } diff --git a/Sources/TripKit/helpers/TKJSONSanitizer.swift b/Sources/TripKit/helpers/TKJSONSanitizer.swift index 81b5b905e..3f694b2c8 100644 --- a/Sources/TripKit/helpers/TKJSONSanitizer.swift +++ b/Sources/TripKit/helpers/TKJSONSanitizer.swift @@ -7,8 +7,7 @@ import Foundation -/// :nodoc: -public enum TKJSONSanitizer { +enum TKJSONSanitizer { /// Sanitizes the provided input to be JSON compatible03 /// @@ -16,7 +15,7 @@ public enum TKJSONSanitizer { /// /// - Parameter input: Any object that should be made JSON compatible /// - Returns: JSON compatible version or `nil` if not possible - public static func sanitize(_ input: Any) -> Any? { + static func sanitize(_ input: Any) -> Any? { switch input { case is String, is Int, is Double, is Float, is Bool: diff --git a/Sources/TripKit/managers/TKCalendarManager+Autocompleting.swift b/Sources/TripKit/managers/TKCalendarManager+Autocompleting.swift index ff2faa238..f0f90de05 100644 --- a/Sources/TripKit/managers/TKCalendarManager+Autocompleting.swift +++ b/Sources/TripKit/managers/TKCalendarManager+Autocompleting.swift @@ -26,7 +26,7 @@ extension TKCalendarManager: TKAutocompleting { completion(.success(results)) } - public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { guard let event = result.object as? EKEvent else { assertionFailure("Unexpected object: \(result.object).") diff --git a/Sources/TripKit/managers/TKContactsManager+TKAutocompleting.swift b/Sources/TripKit/managers/TKContactsManager+TKAutocompleting.swift index 4c7c74ed6..a01664a51 100644 --- a/Sources/TripKit/managers/TKContactsManager+TKAutocompleting.swift +++ b/Sources/TripKit/managers/TKContactsManager+TKAutocompleting.swift @@ -40,7 +40,7 @@ extension TKContactsManager: TKAutocompleting { } } - public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { guard let contact = result.object as? TKContactsManager.ContactAddress else { preconditionFailure("Unexpected object. We require `result.object` to be `TKContactsManager.ContactAddress`, but got: \(result.object)") } diff --git a/Sources/TripKit/model/API/AlertAPIModels.swift b/Sources/TripKit/model/API/AlertAPIModels.swift index 1d64e4e97..907622c8b 100644 --- a/Sources/TripKit/model/API/AlertAPIModels.swift +++ b/Sources/TripKit/model/API/AlertAPIModels.swift @@ -67,16 +67,7 @@ extension TKAPI { public let routes: [TKAPI.Route]? public let modeInfo: TKModeInfo? } - - public struct Route: Codable, Hashable { - public let id: String - public let name: String? - public let number: String? - public let modeInfo: TKModeInfo - - /// This color applies to an individual service. - public var color: TKColor? { return modeInfo.color } - } + } diff --git a/Sources/TripKit/model/API/RouteAPIModel.swift b/Sources/TripKit/model/API/RouteAPIModel.swift new file mode 100644 index 000000000..c9ad1ed1d --- /dev/null +++ b/Sources/TripKit/model/API/RouteAPIModel.swift @@ -0,0 +1,49 @@ +// +// RouteAPIModel.swift +// TripKit +// +// Created by Adrian Schönig on 27/10/2022. +// Copyright © 2022 SkedGo Pty Ltd. All rights reserved. +// + +import Foundation + +extension TKAPI { + + public struct Route: Codable, Hashable { + public let id: String + + public let routeName: String? + public let routeDescription: String? + public let shortName: String? + private let _routeColor: RGBColor? + + public let operatorID: String? + public let operatorName: String? + + public let modeInfo: TKModeInfo + + @available(*, deprecated, renamed: "shortName") + public var number: String? { shortName } + + @available(*, deprecated, renamed: "routeName") + public var name: String? { routeName } + + public var routeColor: TKColor? { _routeColor?.color } + + /// This color applies to an individual service. + public var color: TKColor? { return routeColor ?? modeInfo.color } + + enum CodingKeys: String, CodingKey { + case id + case routeName + case routeDescription + case shortName + case modeInfo + case _routeColor = "routeColor" + case operatorID = "operatorId" + case operatorName + } + } + +} diff --git a/Sources/TripKit/model/API/StopAPIModel.swift b/Sources/TripKit/model/API/StopAPIModel.swift index 20042395e..f9ff48bd6 100644 --- a/Sources/TripKit/model/API/StopAPIModel.swift +++ b/Sources/TripKit/model/API/StopAPIModel.swift @@ -27,6 +27,9 @@ extension TKAPI { case modeInfo case alertHashCodes case zoneID + case availableRoutes + case operators + case routes } public let code: String @@ -40,13 +43,24 @@ extension TKAPI { public let services: String? public let popularity: Int? public let zoneID: String? - + public let availableRoutes: Int? + public let wheelchairAccessible: Bool? @DefaultEmptyArray public var children: [Stop] public let modeInfo: TKModeInfo + public let operators: [Operator]? + + public let routes: [Route]? + @DefaultEmptyArray public var alertHashCodes: [Int] + + } + + public struct Operator: Codable, Hashable { + public let id: String? + public let name: String? } } diff --git a/Sources/TripKit/model/TKCoordinates.swift b/Sources/TripKit/model/TKCoordinates.swift index f261d1b4e..d851c4eda 100644 --- a/Sources/TripKit/model/TKCoordinates.swift +++ b/Sources/TripKit/model/TKCoordinates.swift @@ -84,6 +84,9 @@ public class TKStopCoordinate: TKModeCoordinate { case services case shortName case popularity + case availableRoutes + case routes + case operators } @objc public class override var supportsSecureCoding: Bool { return true } @@ -95,6 +98,9 @@ public class TKStopCoordinate: TKModeCoordinate { services = stop.services stopShortName = stop.shortName stopSortScore = stop.popularity + availableRoutes = stop.availableRoutes + routes = stop.routes + operators = stop.operators } init(_ stop: TKAPI.ShapeStop, modeInfo: TKModeInfo) { @@ -109,12 +115,15 @@ public class TKStopCoordinate: TKModeCoordinate { isDraggable = false guard let values = try? decoder.container(keyedBy: CodingKeys.self) else { return } - if data["sg_services"] == nil { + if data["sg_stopCode"] == nil { // From the API these comes in the decoder rather than in the "data" field stopCode = try values.decode(String.self, forKey: .stopCode) - services = try? values.decode(String.self, forKey: .services) - stopShortName = try? values.decode(String.self, forKey: .shortName) - stopSortScore = try? values.decode(Int.self, forKey: .popularity) + services = try values.decodeIfPresent(String.self, forKey: .services) + stopShortName = try values.decodeIfPresent(String.self, forKey: .shortName) + stopSortScore = try values.decodeIfPresent(Int.self, forKey: .popularity) + availableRoutes = try values.decodeIfPresent(Int.self, forKey: .availableRoutes) + routes = try values.decodeIfPresent([TKAPI.Route].self, forKey: .routes) + operators = try values.decodeIfPresent([TKAPI.Operator].self, forKey: .operators) } } @@ -151,4 +160,63 @@ public class TKStopCoordinate: TKModeCoordinate { set { data["sg_stopSortScore"] = newValue } } + var availableRoutes: Int? { + get { return data["sg_availableRoutes"] as? Int } + set { data["sg_availableRoutes"] = newValue } + } + + private var _routes: [TKAPI.Route]?? = nil + public var routes: [TKAPI.Route]? { + get { + if let decoded = _routes { + return decoded + } else if let json = data["sg_routes"] as Any? { + if let sanitized = TKJSONSanitizer.sanitize(json), let decoded = try? JSONDecoder().decode([TKAPI.Route].self, withJSONObject: sanitized) { + _routes = decoded + return decoded + } else { + _routes = nil + return nil + } + } else { + _routes = nil + return nil + } + } + + set { + _routes = newValue + if let newValue { + data["sg_routes"] = try? JSONEncoder().encodeJSONObject(newValue) + } + } + } + + private var _operators: [TKAPI.Operator]?? = nil + public var operators: [TKAPI.Operator]? { + get { + if let decoded = _operators { + return decoded + } else if let json = data["sg_operators"] as Any? { + if let sanitized = TKJSONSanitizer.sanitize(json), let decoded = try? JSONDecoder().decode([TKAPI.Operator].self, withJSONObject: sanitized) { + _operators = decoded + return decoded + } else { + _operators = nil + return nil + } + } else { + _operators = nil + return nil + } + } + + set { + _operators = newValue + if let newValue { + data["sg_operators"] = try? JSONEncoder().encodeJSONObject(newValue) + } + } + } + } diff --git a/Sources/TripKit/search/TKAppleGeocoder.swift b/Sources/TripKit/search/TKAppleGeocoder.swift index 117c62b18..bfe5d5046 100644 --- a/Sources/TripKit/search/TKAppleGeocoder.swift +++ b/Sources/TripKit/search/TKAppleGeocoder.swift @@ -78,7 +78,7 @@ extension TKAppleGeocoder: TKAutocompleting { } } - public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { guard let searchCompletion = result.object as? MKLocalSearchCompletion else { completion(.failure(GeocoderError.unexpectedResult)) return diff --git a/Sources/TripKit/search/TKGeocoderHelper.swift b/Sources/TripKit/search/TKGeocoderHelper.swift index 7accd39d9..2309cd786 100644 --- a/Sources/TripKit/search/TKGeocoderHelper.swift +++ b/Sources/TripKit/search/TKGeocoderHelper.swift @@ -44,6 +44,7 @@ public class TKGeocoderHelper: NSObject { case missingAddress case serverFoundNoMatch(String) case unknownServerError(String) + case outdatedResult } @objc(errorForNoLocationFoundForInput:) diff --git a/Sources/TripKit/search/TKGeocoding.swift b/Sources/TripKit/search/TKGeocoding.swift index 3d6a772b0..1a25adac6 100644 --- a/Sources/TripKit/search/TKGeocoding.swift +++ b/Sources/TripKit/search/TKGeocoding.swift @@ -43,15 +43,14 @@ public protocol TKAutocompleting { /// - Parameters: /// - input: Query fragment typed by user /// - mapRect: Last map rect the map view was zoomed to (can be `MKMapRectNull`) - /// - Returns: Autocompletion results for query fragment. Should fire with empty result or error out if nothing found. Needs to complete. + /// - completion: Handled called with the autocompletion results for query fragment. Should fire with empty result or error out if nothing found. Needs to be called. func autocomplete(_ input: String, near mapRect: MKMapRect, completion: @escaping (Result<[TKAutocompletionResult], Error>) -> Void) /// Called to fetch the annotation for a previously returned autocompletion result /// /// - Parameter result: The result for which to fetch the annotation - /// - Returns: Single-observable with the annotation for the result. Can error out if an unknown - /// result was passed in. - func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) + /// - Parameter completion: Completion handler that's called with the annotation for the result, if representable as such. Called with `nil` if not representable, or can also be called with an error if it is representable but the conversion failed. Needs to be called. + func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) #if os(iOS) || os(tvOS) /// Text and action for an additional row to display in the results, e.g., to request @@ -67,6 +66,11 @@ public protocol TKAutocompleting { } +public enum TKAutocompletionSelection { + case annotation(MKAnnotation) + case result(TKAutocompletionResult) +} + extension TKAutocompleting { #if os(iOS) || os(tvOS) diff --git a/Sources/TripKit/search/TKPeliasGeocoder.swift b/Sources/TripKit/search/TKPeliasGeocoder.swift index 4ceb4e11d..fb8f5ddbe 100644 --- a/Sources/TripKit/search/TKPeliasGeocoder.swift +++ b/Sources/TripKit/search/TKPeliasGeocoder.swift @@ -120,7 +120,7 @@ extension TKPeliasGeocoder: TKAutocompleting { } } - public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { guard let coordinate = result.object as? TKNamedCoordinate else { preconditionFailure() } completion(.success(coordinate)) } diff --git a/Sources/TripKit/search/TKRegionAutocompleter.swift b/Sources/TripKit/search/TKRegionAutocompleter.swift index 41f5eb5c5..cefcdeb56 100644 --- a/Sources/TripKit/search/TKRegionAutocompleter.swift +++ b/Sources/TripKit/search/TKRegionAutocompleter.swift @@ -25,8 +25,8 @@ public class TKRegionAutocompleter: TKAutocompleting { let titleScore = TKAutocompletionResult.scoreBased(onNameMatchBetweenSearchTerm: input, candidate: name) guard titleScore > 0 else { return nil } let distanceScore = TKAutocompletionResult.scoreBasedOnDistance(from: city.coordinate, to: .init(mapRect), longDistance: true) - let rawSore = (titleScore * 9 + distanceScore) / 10 - let score = TKAutocompletionResult.rangedScore(forScore: rawSore, betweenMinimum: 10, andMaximum: 70) + let rawScore = (titleScore * 9 + distanceScore) / 10 + let score = TKAutocompletionResult.rangedScore(forScore: rawScore, betweenMinimum: 10, andMaximum: 70) return (city, score) } } @@ -47,7 +47,7 @@ public class TKRegionAutocompleter: TKAutocompleting { completion(.success(results)) } - public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { let city = result.object as! TKRegion.City completion(.success(city)) } diff --git a/Sources/TripKit/search/TKRouteAutocompleter.swift b/Sources/TripKit/search/TKRouteAutocompleter.swift new file mode 100644 index 000000000..c43312e4d --- /dev/null +++ b/Sources/TripKit/search/TKRouteAutocompleter.swift @@ -0,0 +1,73 @@ +// +// TKRouteAutocompleter.swift +// TripKit +// +// Created by Adrian Schönig on 27/10/2022. +// Copyright © 2022 SkedGo Pty Ltd. All rights reserved. +// + +import Foundation +import MapKit + +public class TKRouteAutocompleter: TKAutocompleting { + public init() { + } + + public var operatorID: String? + public var modes: [String] = [] + + private var activeSearch: Task? = nil + + public func autocomplete(_ input: String, near mapRect: MKMapRect, completion: @escaping (Result<[TKAutocompletionResult], Error>) -> Void) { + + self.activeSearch?.cancel() + + let coordinateRegion = MKCoordinateRegion(mapRect) + guard + !input.isEmpty, + let region = TKRegionManager.shared.localRegions(overlapping: coordinateRegion).first + else { + return completion(.success([])) + } + + self.activeSearch = Task { + let routes: [TKAPI.Route] + do { + routes = try await TKBuzzInfoProvider.fetchRoutes(forRegion: region, query: input, modes: modes, operatorID: operatorID) + try Task.checkCancellation() + } catch { + return completion(.failure(error)) + } + + let scored = routes.map { route in + let rawScore = [route.shortName, route.routeName] + .compactMap { $0 } + .map { + TKAutocompletionResult.scoreBased(onNameMatchBetweenSearchTerm: input, candidate: $0) + }.max() ?? 0 + let score = TKAutocompletionResult.rangedScore(forScore: rawScore, betweenMinimum: 30, andMaximum: 80) + return (route, score) + } + + let results = scored.map { (route, score) -> TKAutocompletionResult in + let result = TKAutocompletionResult() + result.object = route + result.title = [route.shortName, route.routeName] + .compactMap { $0 } + .joined(separator: ": ") + result.image = route.modeInfo.image(type: .listMainMode) ?? TKImage.iconModePublicTransport + result.isInSupportedRegion = true + result.score = Int(score) + return result + } + + completion(.success(results)) + } + } + + public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + completion(.success(nil)) + } + +} + diff --git a/Sources/TripKit/search/TKTripGoGeocoder.swift b/Sources/TripKit/search/TKTripGoGeocoder.swift index 1e61b740f..db701be76 100644 --- a/Sources/TripKit/search/TKTripGoGeocoder.swift +++ b/Sources/TripKit/search/TKTripGoGeocoder.swift @@ -121,7 +121,7 @@ extension TKTripGoGeocoder: TKAutocompleting { } } - public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + public func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { guard let named = result.object as? TKNamedCoordinate else { assertionFailure() return completion(.failure(NSError(code: 97813, message: "Internal error"))) diff --git a/Sources/TripKit/server/TKBuzzInfoProvider+Routes.swift b/Sources/TripKit/server/TKBuzzInfoProvider+Routes.swift new file mode 100644 index 000000000..aeb60460c --- /dev/null +++ b/Sources/TripKit/server/TKBuzzInfoProvider+Routes.swift @@ -0,0 +1,43 @@ +// +// TKBuzzInfoProvider+Routes.swift +// TripKit +// +// Created by Adrian Schönig on 27/10/2022. +// Copyright © 2022 SkedGo Pty Ltd. All rights reserved. +// + +import Foundation + +extension TKBuzzInfoProvider { + + /// Fetches a list of routes in for the provided region, optionally filtered + /// + /// - warning: Calling this method without any of the filter parameters can result both in a + /// slow, big response due to the large number of routes in certain regions. + /// + /// - Parameters: + /// - region: The region for which to fetch a list of routes + /// - query: Optional search string to filter routes by their short name (complete matches only) + /// or by their name (partial matches) + /// - modes: If provided, only routes using any of these mode identifiers will be returned, e.g., `pt_pub_bus` + /// - operatorID: If provided, only routes for this operator will be returned + /// - Returns: List of routes + public static func fetchRoutes(forRegion region: TKRegion, query: String? = nil, modes: [String] = [], operatorID: String? = nil) async throws -> [TKAPI.Route] { + var paras: [String: Any] = [ + "region": region.name + ] + + paras["query"] = query + paras["modes"] = modes.isEmpty ? nil : modes + paras["operatorID"] = operatorID + + return try await TKServer.shared.hit( + [TKAPI.Route].self, + .POST, + path: "info/routes.json", + parameters: paras, + region: region + ).result.get() + } + +} diff --git a/Sources/TripKitUI/cards/TKUIHomeCard+Configuration.swift b/Sources/TripKitUI/cards/TKUIHomeCard+Configuration.swift index 10dae3a89..bbc945ff3 100644 --- a/Sources/TripKitUI/cards/TKUIHomeCard+Configuration.swift +++ b/Sources/TripKitUI/cards/TKUIHomeCard+Configuration.swift @@ -23,7 +23,7 @@ public extension TKUIHomeCard { /// Execute the provided callback on tap; the component will only be set if selected via /// the card itself and not something on the map. The closure returns a boolean indicating /// whether other annotations should be hidden when the selection is made. - case callback((MKAnnotation, TKUIHomeComponentViewModel?) -> Bool) + case callback((TKAutocompletionSelection, TKUIHomeComponentViewModel?) -> Bool) /// Default handling shows timetable for stops and routing results for others case `default` diff --git a/Sources/TripKitUI/cards/TKUIHomeCard.swift b/Sources/TripKitUI/cards/TKUIHomeCard.swift index 14db835a4..8ae80e2fc 100644 --- a/Sources/TripKitUI/cards/TKUIHomeCard.swift +++ b/Sources/TripKitUI/cards/TKUIHomeCard.swift @@ -271,35 +271,43 @@ extension TKUIHomeCard { case .showCustomizer(let items): showCustomizer(items: items) dismissSelection() - - case let .handleSelection(annotation, component): - if let city = annotation as? TKRegion.City { - clearSearchBar() - - controller?.draggingCardEnabled = true - controller?.moveCard(to: .collapsed, animated: true) { - // Not animating this as it's typically a big jump - self.homeMapManager?.zoom(to: city, animated: false) - } - return + + case let .handleSelection(.annotation(city as TKRegion.City), _): + clearSearchBar() + + controller?.draggingCardEnabled = true + controller?.moveCard(to: .collapsed, animated: true) { + // Not animating this as it's typically a big jump + self.homeMapManager?.zoom(to: city, animated: false) } + case let .handleSelection(selection, component): switch Self.config.selectionMode { case .selectOnMap: - homeMapManager?.select(annotation) + switch selection { + case let .annotation(annotation): + homeMapManager?.select(annotation) + case .result: + assertionFailure("You are using a `TKAutocompleting` provider that does not return annotations for some of its results. In this case, make sure to use the callback selection mode.") + } + case let .callback(handler): - if handler(annotation, component) { - focusedAnnotationPublisher.onNext(annotation) + if handler(selection, component) { + if case let .annotation(annotation) = selection { + focusedAnnotationPublisher.onNext(annotation) + } } + case .default: exitSearchMode() - let card: TGCard - if let stop = annotation as? TKUIStopAnnotation { - card = TKUITimetableCard(stops: [stop]) - } else { - card = TKUIRoutingResultsCard(destination: annotation) + switch selection { + case let .annotation(stop as TKUIStopAnnotation): + cardController.push(TKUITimetableCard(stops: [stop])) + case let .annotation(annotation): + cardController.push(TKUIRoutingResultsCard(destination: annotation)) + case .result: + assertionFailure("You are using a `TKAutocompleting` provider that does not return annotations for some of its results. In this case, make sure to use the callback selection mode.") } - cardController.push(card) } case let .handleAction(handler): diff --git a/Sources/TripKitUI/cards/TKUINearbyMapManager+Home.swift b/Sources/TripKitUI/cards/TKUINearbyMapManager+Home.swift index 6c5aad8cd..a814c522d 100644 --- a/Sources/TripKitUI/cards/TKUINearbyMapManager+Home.swift +++ b/Sources/TripKitUI/cards/TKUINearbyMapManager+Home.swift @@ -25,7 +25,7 @@ extension TKUINearbyMapManager: TKUICompatibleHomeMapManager { mapSelection .compactMap { [weak self] in guard let annotation = $0 else { return nil } - return .handleSelection(annotation, component: self?.viewModel) + return .handleSelection(.annotation(annotation), component: self?.viewModel) } .asObservable() } diff --git a/Sources/TripKitUI/controller/TKUIAutocompletionViewController.swift b/Sources/TripKitUI/controller/TKUIAutocompletionViewController.swift index 9636e9e0c..bc61aeee7 100644 --- a/Sources/TripKitUI/controller/TKUIAutocompletionViewController.swift +++ b/Sources/TripKitUI/controller/TKUIAutocompletionViewController.swift @@ -15,9 +15,9 @@ import RxSwift import TripKit public protocol TKUIAutocompletionViewControllerDelegate: AnyObject { - func autocompleter(_ controller: TKUIAutocompletionViewController, didSelect annotation: MKAnnotation) + func autocompleter(_ controller: TKUIAutocompletionViewController, didSelect selection: TKAutocompletionSelection) - func autocompleter(_ controller: TKUIAutocompletionViewController, didSelectAccessoryFor annotation: MKAnnotation) + func autocompleter(_ controller: TKUIAutocompletionViewController, didSelectAccessoryFor selection: TKAutocompletionSelection) } diff --git a/Sources/TripKitUI/helper/RxTripKit/TKGeocoding+Rx.swift b/Sources/TripKitUI/helper/RxTripKit/TKGeocoding+Rx.swift index a0986f4a3..df715c9a5 100644 --- a/Sources/TripKitUI/helper/RxTripKit/TKGeocoding+Rx.swift +++ b/Sources/TripKitUI/helper/RxTripKit/TKGeocoding+Rx.swift @@ -69,7 +69,7 @@ public extension TKAutocompleting { /// - Parameter result: The result for which to fetch the annotation /// - Returns: Single-observable with the annotation for the result. Can error out if an unknown /// result was passed in. - func annotation(for result: TKAutocompletionResult) -> Single { + func annotation(for result: TKAutocompletionResult) -> Single { return Single.create { subscriber in self.annotation(for: result) { result in switch result { diff --git a/Sources/TripKitUI/view model/TKUIAutocompletionViewModel.swift b/Sources/TripKitUI/view model/TKUIAutocompletionViewModel.swift index 478c611c4..89c8e9517 100644 --- a/Sources/TripKitUI/view model/TKUIAutocompletionViewModel.swift +++ b/Sources/TripKitUI/view model/TKUIAutocompletionViewModel.swift @@ -22,7 +22,7 @@ class TKUIAutocompletionViewModel { var title: String? = nil } - enum Item { + enum Item: Equatable { case currentLocation case autocompletion(AutocompletionItem) case action(ActionItem) @@ -34,9 +34,9 @@ class TKUIAutocompletionViewModel { } } - fileprivate var annotation: Single? { + fileprivate var selection: Single? { switch self { - case .currentLocation: return .just(TKLocationManager.shared.currentLocation) + case .currentLocation: return .just(.annotation(TKLocationManager.shared.currentLocation)) case .autocompletion(let item): return item.completion.annotation case .action: return nil } @@ -58,7 +58,7 @@ class TKUIAutocompletionViewModel { } } - struct AutocompletionItem { + struct AutocompletionItem: Equatable { let index: Int let completion: TKAutocompletionResult let includeAccessory: Bool @@ -72,7 +72,11 @@ class TKUIAutocompletionViewModel { var provider: TKAutocompleting? { completion.provider as? TKAutocompleting } } - struct ActionItem { + struct ActionItem: Equatable { + static func == (lhs: TKUIAutocompletionViewModel.ActionItem, rhs: TKUIAutocompletionViewModel.ActionItem) -> Bool { + lhs.title == rhs.title + } + fileprivate let provider: TKAutocompleting let title: String @@ -98,9 +102,9 @@ class TKUIAutocompletionViewModel { .asDriver(onErrorDriveWith: Driver.empty()) selection = selected - .compactMap(\.annotation) + .compactMap(\.selection) .asObservable() - .flatMapLatest { fetched -> Observable in + .flatMapLatest { fetched -> Observable in return fetched .asObservable() .catch { error in @@ -113,7 +117,7 @@ class TKUIAutocompletionViewModel { accessorySelection = (accessorySelected ?? .empty()) .compactMap(\.result) .asObservable() - .flatMapLatest { result -> Observable in + .flatMapLatest { result -> Observable in return result.annotation .asObservable() .catch { error in @@ -132,9 +136,9 @@ class TKUIAutocompletionViewModel { let sections: Driver<[Section]> - let selection: Signal + let selection: Signal - let accessorySelection: Signal + let accessorySelection: Signal /// Fires when user taps on the "additional action" element of a `TKAutocompleting` /// provider. If that's the case, you should call `triggerAdditional` on it. @@ -191,12 +195,21 @@ extension Array where Element == TKAutocompletionResult { extension TKAutocompletionResult { - fileprivate var annotation: Single { + fileprivate var annotation: Single { guard let provider = provider as? TKAutocompleting else { assertionFailure() return Single.error(NSError(code: 18376, message: "Bad provider!")) } return provider.annotation(for: self) + .map { [weak self] in + if let annotation = $0 { + return .annotation(annotation) + } else if let self { + return .result(self) + } else { + throw TKGeocoderHelper.GeocodingError.outdatedResult + } + } } } @@ -204,18 +217,6 @@ extension TKAutocompletionResult { // MARK: - RxDataSource protocol conformance -func == (lhs: TKUIAutocompletionViewModel.Item, rhs: TKUIAutocompletionViewModel.Item) -> Bool { - switch (lhs, rhs) { - case (.autocompletion(let left), .autocompletion(let right)): return left.completion == right.completion - case (.action(let left), .action(let right)): return left.title == right.title - case (.currentLocation, .currentLocation): return true - default: return false - } -} - -extension TKUIAutocompletionViewModel.Item: Equatable { -} - extension TKUIAutocompletionViewModel.Item: IdentifiableType { typealias Identity = String var identity: Identity { diff --git a/Sources/TripKitUI/view model/TKUIHomeViewModel+Next.swift b/Sources/TripKitUI/view model/TKUIHomeViewModel+Next.swift index f2bcc13e7..efe7aae4d 100644 --- a/Sources/TripKitUI/view model/TKUIHomeViewModel+Next.swift +++ b/Sources/TripKitUI/view model/TKUIHomeViewModel+Next.swift @@ -29,7 +29,7 @@ extension TKUIHomeCard { /// Use this for the home card to decide what to do when selecing the provided annotation by the provided component. /// The home card will take action according to how it's `selectionMode` is set. - case handleSelection(MKAnnotation, component: TKUIHomeComponentViewModel? = nil) + case handleSelection(TKAutocompletionSelection, component: TKUIHomeComponentViewModel? = nil) /// Shows the user interface to customize the home card, which let's users re-order and toggle /// individual home card components. @@ -54,7 +54,7 @@ extension TKUIHomeViewModel { /// Use this for the home card to decide what to do when selecing the provided annotation by the provided component. /// The home card will take action according to how it's `selectionMode` is set. - case handleSelection(MKAnnotation, component: TKUIHomeComponentViewModel? = nil) + case handleSelection(TKAutocompletionSelection, component: TKUIHomeComponentViewModel? = nil) /// Use this to handle autocompletion providers' trigger actions. Call the handler and /// subscribe to the `Single` that it is returning and if that emits a `true`, call diff --git a/Sources/TripKitUI/view model/TKUIHomeViewModel+Search.swift b/Sources/TripKitUI/view model/TKUIHomeViewModel+Search.swift index 01064dc80..8e102df5d 100644 --- a/Sources/TripKitUI/view model/TKUIHomeViewModel+Search.swift +++ b/Sources/TripKitUI/view model/TKUIHomeViewModel+Search.swift @@ -35,21 +35,25 @@ extension TKUIHomeViewModel { } let nextFromSelection = searchViewModel.selection - .map { annotation -> NextAction in - if let city = annotation as? TKRegion.City { - return .handleSelection(city, component: nil) - } else { + .map { selection -> NextAction in + switch selection { + case .annotation(let city) where city is TKRegion.City: + return .handleSelection(selection, component: nil) + case .annotation(let annotation): return .push(TKUIRoutingResultsCard(destination: annotation), selection: annotation) + case .result: + return .handleSelection(selection, component: nil) } } let nextFromAccessory = searchViewModel.accessorySelection - .map { annotation -> NextAction in - switch annotation { - case let stop as TKUIStopAnnotation: return .push(TKUITimetableCard(stops: [stop]), selection: annotation) - default: - assertionFailure("Unexpected annotation: \(annotation)") - return .push(TKUIRoutingResultsCard(destination: annotation), selection: annotation) + .compactMap { selection -> NextAction? in + switch selection { + case .annotation(let stop as TKUIStopAnnotation): + return .push(TKUITimetableCard(stops: [stop]), selection: stop) + case .annotation, .result: + assertionFailure("Unexpected annotation: \(selection)") + return nil } } diff --git a/Sources/TripKitUI/view model/TKUINearbyViewModel+HomeCard.swift b/Sources/TripKitUI/view model/TKUINearbyViewModel+HomeCard.swift index 6c052a2b2..6d0169ec8 100644 --- a/Sources/TripKitUI/view model/TKUINearbyViewModel+HomeCard.swift +++ b/Sources/TripKitUI/view model/TKUINearbyViewModel+HomeCard.swift @@ -41,7 +41,7 @@ extension TKUINearbyViewModel: TKUIHomeComponentViewModel { } public var nextAction: Signal { - return mapAnnotationToSelect.map { .handleSelection($0, component: self) } + return mapAnnotationToSelect.map { .handleSelection(.annotation($0), component: self) } } public func cell(for item: TKUIHomeComponentItem, at indexPath: IndexPath, in tableView: UITableView) -> UITableViewCell? { diff --git a/Sources/TripKitUI/view model/TKUIRoutingQueryInputViewModel+State.swift b/Sources/TripKitUI/view model/TKUIRoutingQueryInputViewModel+State.swift index 08bb9c1c6..1acfad88a 100644 --- a/Sources/TripKitUI/view model/TKUIRoutingQueryInputViewModel+State.swift +++ b/Sources/TripKitUI/view model/TKUIRoutingQueryInputViewModel+State.swift @@ -12,6 +12,8 @@ import MapKit import RxSwift import RxCocoa +import enum TripKit.TKAutocompletionSelection + extension TKUIRoutingQueryInputViewModel { enum UserAction { @@ -36,10 +38,15 @@ extension TKUIRoutingQueryInputViewModel { var mode: TKUIRoutingResultsViewModel.SearchMode? } - static func buildState(origin: MKAnnotation?, destination: MKAnnotation?, startMode: TKUIRoutingResultsViewModel.SearchMode?, inputs: UIInput, selection: Signal) -> Observable { + static func buildState(origin: MKAnnotation?, destination: MKAnnotation?, startMode: TKUIRoutingResultsViewModel.SearchMode?, inputs: UIInput, selection: Signal) -> Observable { let userActions: Observable = Observable.merge([ - selection.asObservable().map { .selectResult($0) }, + selection.asObservable().compactMap { + switch $0 { + case .annotation(let annotation): return .selectResult(annotation) + case .result: return nil + } + }, inputs.tappedSwap.asObservable().map { .swap }, inputs.searchText.distinctUntilChanged { $0.0 == $1.0 }.map { .typeText($0.0) }, inputs.selectedSearchMode.asObservable().distinctUntilChanged().map { .selectMode($0) }, diff --git a/Sources/TripKitUI/view model/TKUISectionedAlertViewModel.swift b/Sources/TripKitUI/view model/TKUISectionedAlertViewModel.swift index a80f63374..e20f31b80 100644 --- a/Sources/TripKitUI/view model/TKUISectionedAlertViewModel.swift +++ b/Sources/TripKitUI/view model/TKUISectionedAlertViewModel.swift @@ -105,7 +105,7 @@ class TKUISectionedAlertViewModel { extension TKAPI.Route { var title: String { - return number ?? name ?? id + return shortName ?? routeName ?? id } } diff --git a/Sources/TripKitUI/views/alerts/TKUIGroupedAlertCell.swift b/Sources/TripKitUI/views/alerts/TKUIGroupedAlertCell.swift index fa7700dfe..78bff4a8c 100644 --- a/Sources/TripKitUI/views/alerts/TKUIGroupedAlertCell.swift +++ b/Sources/TripKitUI/views/alerts/TKUIGroupedAlertCell.swift @@ -91,11 +91,11 @@ class TKUIGroupedAlertCell: UITableViewCell { serviceColorIndicator.backgroundColor = route.color - routeNumberLabel.text = route.number ?? route.name + routeNumberLabel.text = route.shortName ?? route.routeName routeNumberLabel.font = TKStyleManager.semiboldCustomFont(forTextStyle: .body) - routeNameLabel.text = route.name + routeNameLabel.text = route.routeName routeNameLabel.font = TKStyleManager.customFont(forTextStyle: .footnote) - routeNameLabel.isHidden = (route.name == nil) || (routeNameLabel.text == routeNumberLabel.text) + routeNameLabel.isHidden = (route.routeName == nil) || (routeNameLabel.text == routeNumberLabel.text) let multipleAlerts = alertGroup.alerts.count > 1 diff --git a/Tests/TripKitUITests/TKUIRoutingQueryInputViewModelTest.swift b/Tests/TripKitUITests/TKUIRoutingQueryInputViewModelTest.swift index b12606447..2a011900c 100644 --- a/Tests/TripKitUITests/TKUIRoutingQueryInputViewModelTest.swift +++ b/Tests/TripKitUITests/TKUIRoutingQueryInputViewModelTest.swift @@ -299,7 +299,7 @@ fileprivate class FakeAutocompleter: TKAutocompleting { completion(.success(results)) } - func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { + func annotation(for result: TKAutocompletionResult, completion: @escaping (Result) -> Void) { let annotation = MKPointAnnotation() annotation.title = result.title annotation.coordinate = kCLLocationCoordinate2DInvalid diff --git a/TripKit.xcodeproj/project.pbxproj b/TripKit.xcodeproj/project.pbxproj index 9285c0726..425867542 100644 --- a/TripKit.xcodeproj/project.pbxproj +++ b/TripKit.xcodeproj/project.pbxproj @@ -42,6 +42,10 @@ 3AB4E58127584632006D2363 /* String+NonEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB4E58027584632006D2363 /* String+NonEmpty.swift */; }; 3AB4E58227584632006D2363 /* String+NonEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB4E58027584632006D2363 /* String+NonEmpty.swift */; }; 3AB6C62B1D1CD0060089F687 /* TripKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3AB6C6201D1CD0060089F687 /* TripKit.framework */; }; + 3ABE9D6D290A0F8D0001231F /* TKRouteAutocompleter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABE9D6C290A0F8D0001231F /* TKRouteAutocompleter.swift */; }; + 3ABE9D6E290A0F8D0001231F /* TKRouteAutocompleter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABE9D6C290A0F8D0001231F /* TKRouteAutocompleter.swift */; }; + 3ABE9D73290A1EC70001231F /* RouteAPIModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABE9D72290A1EC70001231F /* RouteAPIModel.swift */; }; + 3ABE9D74290A1EC70001231F /* RouteAPIModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ABE9D72290A1EC70001231F /* RouteAPIModel.swift */; }; 3AC6946E27E3043C00DBD287 /* InMemoryHistoryManager+Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC6946D27E3043B00DBD287 /* InMemoryHistoryManager+Home.swift */; }; 3AC6947327E30CB400DBD287 /* TKUINearbyViewModel+HomeCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AC6947227E30CB400DBD287 /* TKUINearbyViewModel+HomeCard.swift */; }; 3AC6947827E30E2B00DBD287 /* TKUINearbyCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3AC6947627E30E2B00DBD287 /* TKUINearbyCell.xib */; }; @@ -91,6 +95,8 @@ 3ACF47AB229EEDFF0096860D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3ACF47AA229EEDFF0096860D /* Assets.xcassets */; }; 3ACF47AE229EEDFF0096860D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3ACF47AC229EEDFF0096860D /* LaunchScreen.storyboard */; }; 3AD3BA9327422A4C00E33015 /* TGCardViewController in Frameworks */ = {isa = PBXBuildFile; productRef = 3AD3BA9227422A4C00E33015 /* TGCardViewController */; }; + 3AFE1A6F290A1FCE003EE770 /* TKBuzzInfoProvider+Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFE1A6E290A1FCE003EE770 /* TKBuzzInfoProvider+Routes.swift */; }; + 3AFE1A70290A1FCE003EE770 /* TKBuzzInfoProvider+Routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFE1A6E290A1FCE003EE770 /* TKBuzzInfoProvider+Routes.swift */; }; 3AFF283426C6977B007809CD /* TKShareHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFF25EC26C6975F007809CD /* TKShareHelper.swift */; }; 3AFF283526C6977B007809CD /* TKShareURLProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFF25EE26C6975F007809CD /* TKShareURLProvider.swift */; }; 3AFF283626C6977B007809CD /* TKShareHelper+Parsing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AFF25ED26C6975F007809CD /* TKShareHelper+Parsing.swift */; }; @@ -983,6 +989,8 @@ 3AB6C6201D1CD0060089F687 /* TripKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TripKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3AB6C62A1D1CD0060089F687 /* TripKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TripKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3ABA80AB204F1AC100E67BC7 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; + 3ABE9D6C290A0F8D0001231F /* TKRouteAutocompleter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TKRouteAutocompleter.swift; sourceTree = ""; }; + 3ABE9D72290A1EC70001231F /* RouteAPIModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteAPIModel.swift; sourceTree = ""; }; 3AC6946D27E3043B00DBD287 /* InMemoryHistoryManager+Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InMemoryHistoryManager+Home.swift"; sourceTree = ""; }; 3AC6947227E30CB400DBD287 /* TKUINearbyViewModel+HomeCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TKUINearbyViewModel+HomeCard.swift"; sourceTree = ""; }; 3AC6947627E30E2B00DBD287 /* TKUINearbyCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TKUINearbyCell.xib; sourceTree = ""; }; @@ -1053,6 +1061,7 @@ 3ACF47AD229EEDFF0096860D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 3ACF47AF229EEDFF0096860D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3ADCB5F626CDFCC400E4C13A /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; + 3AFE1A6E290A1FCE003EE770 /* TKBuzzInfoProvider+Routes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TKBuzzInfoProvider+Routes.swift"; sourceTree = ""; }; 3AFF25EC26C6975F007809CD /* TKShareHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TKShareHelper.swift; sourceTree = ""; }; 3AFF25ED26C6975F007809CD /* TKShareHelper+Parsing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TKShareHelper+Parsing.swift"; sourceTree = ""; }; 3AFF25EE26C6975F007809CD /* TKShareURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TKShareURLProvider.swift; sourceTree = ""; }; @@ -2035,6 +2044,7 @@ children = ( 3AFF264C26C6975F007809CD /* parsing */, 3AFF265226C6975F007809CD /* TKBuzzInfoProvider.swift */, + 3AFE1A6E290A1FCE003EE770 /* TKBuzzInfoProvider+Routes.swift */, 3AFF264826C6975F007809CD /* TKDeparturesProvider.swift */, 3AFF263D26C6975F007809CD /* TKError.swift */, 3A89090727E473EE005A49B1 /* TKLocationProvider.swift */, @@ -2088,6 +2098,7 @@ 3AFF265A26C6975F007809CD /* TKNamedCoordinate+BuzzData.swift */, 3AFF265626C6975F007809CD /* TKPeliasGeocoder.swift */, 3AFF265D26C6975F007809CD /* TKRegionAutocompleter.swift */, + 3ABE9D6C290A0F8D0001231F /* TKRouteAutocompleter.swift */, 3AFF266026C6975F007809CD /* TKTripGoGeocoder.swift */, ); path = search; @@ -2196,6 +2207,7 @@ 3AFF26A026C6975F007809CD /* PricingTableAPIModel.swift */, 3AFF26A626C6975F007809CD /* RegionInfoAPIModel.swift */, 3AFF26A726C6975F007809CD /* RegionsAPIModel.swift */, + 3ABE9D72290A1EC70001231F /* RouteAPIModel.swift */, 3AFF26AC26C6975F007809CD /* RoutingAPIModel.swift */, 3AFF269F26C6975F007809CD /* ServiceAPIModel.swift */, 3AFF26A826C6975F007809CD /* ShareCarAPIModel.swift */, @@ -3425,6 +3437,7 @@ 3AFF28D926C697D9007809CD /* SegmentTemplate+CoreDataProperties.swift in Sources */, 3AFF297726C69836007809CD /* UIColor+TripKitDefault.swift in Sources */, 3AFF286A26C697A3007809CD /* TKJSONCache.swift in Sources */, + 3ABE9D74290A1EC70001231F /* RouteAPIModel.swift in Sources */, 3AFF293D26C6980D007809CD /* TKAppleGeocoder.swift in Sources */, 3AFF28BC26C697D9007809CD /* TKPathFriendliness.swift in Sources */, 3AFF29AA26C6985D007809CD /* NSUserDefaults+SharedDefaults.m in Sources */, @@ -3565,6 +3578,7 @@ 3AFF283C26C6977C007809CD /* TKShareHelper.swift in Sources */, 3AFF296526C6981E007809CD /* TKRouter.swift in Sources */, 3AFF296C26C6981E007809CD /* TKRoutingServer.swift in Sources */, + 3AFE1A70290A1FCE003EE770 /* TKBuzzInfoProvider+Routes.swift in Sources */, 3AFF287D26C697AB007809CD /* TKRegionManager.swift in Sources */, 3AFF288A26C697CA007809CD /* RoutingAPIModel.swift in Sources */, 3AB4E58227584632006D2363 /* String+NonEmpty.swift in Sources */, @@ -3609,6 +3623,7 @@ 3AFF283E26C6977C007809CD /* TKShareHelper+Parsing.swift in Sources */, 3AFF288826C697CA007809CD /* RegionInfoAPIModel.swift in Sources */, 3AFF2A0426C69994007809CD /* NSUserDefaults+SharedDefaults.swift in Sources */, + 3ABE9D6E290A0F8D0001231F /* TKRouteAutocompleter.swift in Sources */, 3AFF296B26C6981E007809CD /* TKRealTime.swift in Sources */, 3AFF29D626C69864007809CD /* TKTransportModes.m in Sources */, 3A1AABB926CB7BB100FDDB06 /* TKVehicular.swift in Sources */, @@ -3884,6 +3899,7 @@ 3AFF292226C697DA007809CD /* TKSecureTransformers.swift in Sources */, 3AFF295126C6981D007809CD /* TKRegion.swift in Sources */, 3AFF291826C697DA007809CD /* SegmentTemplate+CoreDataProperties.swift in Sources */, + 3ABE9D73290A1EC70001231F /* RouteAPIModel.swift in Sources */, 3AFF297A26C69837007809CD /* UIColor+TripKitDefault.swift in Sources */, 3AFF285C26C697A3007809CD /* TKJSONCache.swift in Sources */, 3AFF292F26C6980D007809CD /* TKAppleGeocoder.swift in Sources */, @@ -3908,6 +3924,7 @@ 3AFF283B26C6977B007809CD /* Trip+Shareable.swift in Sources */, 3AFF293226C6980D007809CD /* TKNamedCoordinate+Attribution.swift in Sources */, 3AFF293326C6980D007809CD /* TKGeocoding.swift in Sources */, + 3AFE1A6F290A1FCE003EE770 /* TKBuzzInfoProvider+Routes.swift in Sources */, 3AFF294A26C6981D007809CD /* TKTripFetcher.swift in Sources */, 3AFF295226C6981D007809CD /* TKTransportModes.swift in Sources */, 3AFF287426C697AA007809CD /* TKCalendarManager.swift in Sources */, @@ -4071,6 +4088,7 @@ 3AFF283626C6977B007809CD /* TKShareHelper+Parsing.swift in Sources */, 3AFF289A26C697CB007809CD /* RegionInfoAPIModel.swift in Sources */, 3AFF29F926C69993007809CD /* NSUserDefaults+SharedDefaults.swift in Sources */, + 3ABE9D6D290A0F8D0001231F /* TKRouteAutocompleter.swift in Sources */, 3AFF295426C6981D007809CD /* TKRealTime.swift in Sources */, 3AFF29B326C69863007809CD /* TKTransportModes.m in Sources */, 3AFF294F26C6981D007809CD /* TKServer+Regions.swift in Sources */, @@ -4712,6 +4730,7 @@ CODE_SIGN_STYLE = Automatic; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = JFT62W5LA3; + "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -4751,6 +4770,7 @@ CODE_SIGN_STYLE = Automatic; DERIVE_MACCATALYST_PRODUCT_BUNDLE_IDENTIFIER = YES; DEVELOPMENT_TEAM = JFT62W5LA3; + "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/Examples/TripKitUIExample/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 13.0;