Skip to content

Commit

Permalink
Initializes CarPlay (#368)
Browse files Browse the repository at this point in the history
* feat: in progressing setting up basics for car play

* feat: basic CarPlay init

* feat: basic CarPlay init

* feat: basic CarPlay init

* feat: basic CarPlay init

* added carplay guide page & minor updates

* added carplay guide page & minor updates

* added carplay guide page & minor updates
  • Loading branch information
Archdoog authored Nov 26, 2024
1 parent 43391a3 commit 7cd7a1e
Show file tree
Hide file tree
Showing 16 changed files with 488 additions and 135 deletions.
16 changes: 16 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ let package = Package(
targets: [
"FerrostarMapLibreUI",
"FerrostarSwiftUI",
"FerrostarCarPlayUI",
] // TODO: Remove FerrostarSwiftUI from FerrostarMapLibreUI once we can fix the demo app swift package config (broken in Xcode 15.3)
),
.library(
name: "FerrostarSwiftUI",
targets: ["FerrostarSwiftUI"]
),
.library(
name: "FerrostarCarPlayUI",
targets: ["FerrostarCarPlayUI"]
),
],
dependencies: [
maplibreSwiftUIDSLPackage,
Expand All @@ -68,6 +73,17 @@ let package = Package(
],
targets: [
binaryTarget,
.target(
name: "FerrostarCarPlayUI",
dependencies: [
.target(name: "FerrostarCore"),
.target(name: "FerrostarSwiftUI"),
.target(name: "FerrostarMapLibreUI"),
.product(name: "MapLibreSwiftDSL", package: "swiftui-dsl"),
.product(name: "MapLibreSwiftUI", package: "swiftui-dsl"),
],
path: "apple/Sources/FerrostarCarPlayUI"
),
.target(
name: "FerrostarCore",
dependencies: [.target(name: "FerrostarCoreFFI")],
Expand Down
117 changes: 117 additions & 0 deletions apple/DemoApp/Demo/AppEnvironment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import CoreLocation
import FerrostarCore
import FerrostarCoreFFI
import SwiftUI

enum AppDefaults {
static let initialLocation = CLLocation(latitude: 37.332726, longitude: -122.031790)
static let mapStyleURL =
URL(string: "https://tiles.stadiamaps.com/styles/outdoors.json?api_key=\(APIKeys.shared.stadiaMapsAPIKey)")!
}

enum DemoAppError: Error {
case noUserLocation
case noRoutes
case other(Error)
}

/// This is a shared core where ferrostar lives
class AppEnvironment: ObservableObject {
var locationProvider: LocationProviding
@Published var ferrostarCore: FerrostarCore
@Published var spokenInstructionObserver: SpokenInstructionObserver

let navigationDelegate = NavigationDelegate()

init(initialLocation: CLLocation = AppDefaults.initialLocation) {
let simulated = SimulatedLocationProvider(location: initialLocation)
simulated.warpFactor = 2
locationProvider = simulated

// Set up the a standard Apple AV Speech Synth.
spokenInstructionObserver = .initAVSpeechSynthesizer()

// Configure the navigation session.
// You have a lot of flexibility here based on your use case.
let config = SwiftNavigationControllerConfig(
stepAdvance: .relativeLineStringDistance(minimumHorizontalAccuracy: 32, automaticAdvanceDistance: 10),
routeDeviationTracking: .staticThreshold(minimumHorizontalAccuracy: 25, maxAcceptableDeviation: 20),
snappedLocationCourseFiltering: .snapToRoute
)

ferrostarCore = try! FerrostarCore(
valhallaEndpointUrl: URL(
string: "https://api.stadiamaps.com/route/v1?api_key=\(APIKeys.shared.stadiaMapsAPIKey)"
)!,
profile: "bicycle",
locationProvider: locationProvider,
navigationControllerConfig: config,
options: ["costing_options": ["bicycle": ["use_roads": 0.2]]],
// This is how you can set up annotation publishing;
// We provide "extended OSRM" support out of the box,
// but this is fully extendable!
annotation: AnnotationPublisher<ValhallaExtendedOSRMAnnotation>.valhallaExtendedOSRM()
)

// NOTE: Not all applications will need a delegate. Read the NavigationDelegate documentation for details.
ferrostarCore.delegate = navigationDelegate

// Initialize text-to-speech; note that this is NOT automatic.
// You must set a spokenInstructionObserver.
// Fortunately, this is pretty easy with the provided class
// backed by AVSpeechSynthesizer.
// You can customize the instance it further as needed,
// or replace with your own.
ferrostarCore.spokenInstructionObserver = spokenInstructionObserver
}

func getRoutes() async throws -> [Route] {
guard let userLocation = locationProvider.lastLocation else {
throw DemoAppError.noUserLocation
}

let waypoints = locations.map { Waypoint(
coordinate: GeographicCoordinate(lat: $0.coordinate.latitude, lng: $0.coordinate.longitude),
kind: .break
) }

let routes = try await ferrostarCore.getRoutes(initialLocation: userLocation,
waypoints: waypoints)

guard let route = routes.first else {
throw DemoAppError.noRoutes
}

print("DemoApp: successfully fetched routes")

if let simulated = locationProvider as? SimulatedLocationProvider {
// This configures the simulator to the desired route.
// The ferrostarCore.startNavigation will still start the location
// provider/simulator.
simulated
.lastLocation = UserLocation(clCoordinateLocation2D: route.geometry.first!.clLocationCoordinate2D)
print("DemoApp: setting initial location")
}

return routes
}

func startNavigation(route: Route) throws {
if let simulated = locationProvider as? SimulatedLocationProvider {
// This configures the simulator to the desired route.
// The ferrostarCore.startNavigation will still start the location
// provider/simulator.
try simulated.setSimulatedRoute(route, resampleDistance: 5)
print("DemoApp: setting route to be simulated")
}

// Starts the navigation state machine.
// It's worth having a look through the parameters,
// as most of the configuration happens here.
try ferrostarCore.startNavigation(route: route)
}

func stopNavigation() {
ferrostarCore.stopNavigation()
}
}
56 changes: 56 additions & 0 deletions apple/DemoApp/Demo/CarPlaySceneDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import CarPlay
import FerrostarCarPlayUI
import FerrostarCore
import SwiftUI
import UIKit

class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
// Get the AppDelegate associated with the SwiftUI App/@main as the type you defined it as.
@UIApplicationDelegateAdaptor(DemoAppDelegate.self) var appDelegate

private var ferrostarManager: FerrostarCarPlayManager?

func configure() {
guard ferrostarManager == nil else { return }

ferrostarManager = FerrostarCarPlayManager(
ferrostarCore: appDelegate.appEnvironment.ferrostarCore,
styleURL: AppDefaults.mapStyleURL
)
}

func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController,
to window: CPWindow
) {
configure()
ferrostarManager!.templateApplicationScene(templateApplicationScene,
didConnect: interfaceController,
to: window)
}
}

extension CarPlaySceneDelegate: CPTemplateApplicationDashboardSceneDelegate {
func templateApplicationDashboardScene(_: CPTemplateApplicationDashboardScene,
didConnect _: CPDashboardController,
to _: UIWindow) {}

func templateApplicationDashboardScene(_: CPTemplateApplicationDashboardScene,
didDisconnect _: CPDashboardController,
from _: UIWindow) {}
}

extension CarPlaySceneDelegate: CPTemplateApplicationInstrumentClusterSceneDelegate {
// swiftlint:disable identifier_name vertical_parameter_alignment
func templateApplicationInstrumentClusterScene(
_: CPTemplateApplicationInstrumentClusterScene,
didConnect _: CPInstrumentClusterController
) {}

func templateApplicationInstrumentClusterScene(
_: CPTemplateApplicationInstrumentClusterScene,
didDisconnectInstrumentClusterController _: CPInstrumentClusterController
) {}
// swiftlint:enable identifier_name vertical_parameter_alignment
}
14 changes: 0 additions & 14 deletions apple/DemoApp/Demo/ConfigurationView.swift

This file was deleted.

9 changes: 9 additions & 0 deletions apple/DemoApp/Demo/DemoApp.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import SwiftUI

// This AppDelegate setup is an easy way to share your environment with CarPlay
class DemoAppDelegate: NSObject, UIApplicationDelegate {
let appEnvironment = AppEnvironment()
}

@main
struct DemoApp: App {
@UIApplicationDelegateAdaptor(DemoAppDelegate.self) private var appDelegate: DemoAppDelegate

var body: some Scene {
WindowGroup {
DemoNavigationView()
.environmentObject(appDelegate.appEnvironment)
.environmentObject(appDelegate.appEnvironment.ferrostarCore)
}
}
}
Loading

0 comments on commit 7cd7a1e

Please sign in to comment.