Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/ios speed limit view #119

Merged
merged 23 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7711e23
feat: basic speed limit views
Archdoog Jun 12, 2024
2ec63a4
feat: basic speed limit views + swiftformat
Archdoog Jun 12, 2024
bfa472f
Merge branch 'main' of github.com:stadiamaps/ferrostar into feat/ios-…
Archdoog Jun 14, 2024
2038eb0
Added speed limit, grid views and updated demo app
Archdoog Jun 20, 2024
76fc0ae
Fixed arrival testing snapshots
Archdoog Jun 20, 2024
45d6b5b
re-built ferrostar.swift to test swiftformat failure
Archdoog Jun 20, 2024
c8c8be6
Apply suggestions from code review
Archdoog Jun 25, 2024
467568c
revisions based on discussions
Archdoog Jun 26, 2024
6ecaad3
revisions based on discussions
Archdoog Jun 26, 2024
7be623d
revisions based on discussions
Archdoog Jun 26, 2024
e49a1f9
added speed limit provider & updated testing
Archdoog Jun 28, 2024
67d5aa3
added speed limit provider & updated testing
Archdoog Jun 28, 2024
8dc74ca
added speed limit provider & updated testing
Archdoog Jun 28, 2024
557ad2d
Apply suggestions from code review
Archdoog Jul 2, 2024
de3d79a
renamed ferrostar UI types to NavigationUI
Archdoog Jul 2, 2024
a4033e4
Added TestingFormatterCollection
Archdoog Jul 2, 2024
9402679
Merge branch 'main' of github.com:stadiamaps/ferrostar into feat/ios-…
Archdoog Jul 2, 2024
c22e135
Added TestingFormatterCollection
Archdoog Jul 2, 2024
1d458b6
Corrected additional formatters in testing and color for snapshot bac…
Archdoog Jul 2, 2024
3062081
Fixed testing DateFormat
Archdoog Jul 2, 2024
4cb8e94
Fixed testing DateFormat
Archdoog Jul 2, 2024
2671a22
More date format fixes
ianthetechie Jul 2, 2024
dbd45f8
swiftformat
ianthetechie Jul 2, 2024
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
113 changes: 42 additions & 71 deletions apple/DemoApp/Demo/DemoNavigationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import CoreLocation
import FerrostarCore
import FerrostarCoreFFI
import FerrostarMapLibreUI
import FerrostarSwiftUI
import MapLibre
import MapLibreSwiftDSL
import MapLibreSwiftUI
Expand Down Expand Up @@ -68,7 +69,8 @@ struct DemoNavigationView: View {
navigationState: ferrostarCore.state,
camera: $camera,
snappedZoom: .constant(18),
useSnappedCamera: $snappedCamera
useSnappedCamera: $snappedCamera,
onTapExit: { stopNavigation() }
) {
let source = ShapeSource(identifier: "userLocation") {
// Demonstrate how to add a dynamic overlay;
Expand All @@ -78,58 +80,30 @@ struct DemoNavigationView: View {
}
}
CircleStyleLayer(identifier: "foo", source: source)
}
.overlay(alignment: .bottomLeading) {
} topCenter: {
if let errorMessage {
NavigationUIBanner(severity: .error) {
Text(errorMessage)
}
.onTapGesture {
self.errorMessage = nil
}
} else if isFetchingRoutes {
NavigationUIBanner(severity: .loading) {
Text("Loading route...")
}
}
} bottomTrailing: {
VStack {
HStack {
if isFetchingRoutes {
Text("Loading route...")
.font(.caption)
.padding(.all, 8)
.foregroundColor(.white)
.background(Color.black.opacity(0.7).clipShape(.buttonBorder, style: FillStyle()))
}

if let errorMessage {
Text(errorMessage)
.font(.caption)
.padding(.all, 8)
.foregroundColor(.white)
.background(Color.red.opacity(0.7).clipShape(.buttonBorder, style: FillStyle()))
.onTapGesture {
self.errorMessage = nil
}
}

Spacer()

NavigationLink {
ConfigurationView()
} label: {
Image(systemName: "gear")
}
Text(locationLabel)
.font(.caption)
.padding(.all, 8)
.background(
Color.white
.clipShape(.buttonBorder, style: FillStyle())
.shadow(radius: 4)
)
.padding(.top, 128) // TODO: Move the controls layer to a VStack w/ the InstructionsView
}

Spacer()
.foregroundColor(.white)
.background(Color.black.opacity(0.7).clipShape(.buttonBorder, style: FillStyle()))

HStack {
Text(locationLabel)
.font(.caption)
.padding(.all, 8)
.foregroundColor(.white)
.background(Color.black.opacity(0.7).clipShape(.buttonBorder, style: FillStyle()))

Spacer()

if locationServicesEnabled {
Button("Start Navigation") {
if locationServicesEnabled {
if ferrostarCore.state == nil {
NavigationUIButton {
Task {
do {
isFetchingRoutes = true
Expand All @@ -140,32 +114,24 @@ struct DemoNavigationView: View {
errorMessage = "\(error.localizedDescription)"
}
}
} label: {
Text("Start Nav")
.font(.body.bold())
}
.disabled(routes?.isEmpty == true)
.padding(.all, 8)
.background(
Color.white
.clipShape(.buttonBorder, style: FillStyle())
.shadow(radius: 4)
)
} else {
Button("Enable Location Services") {
// TODO: enable location services.
}
.padding(.all, 8)
.background(
Color.white
.clipShape(.buttonBorder, style: FillStyle())
.shadow(radius: 4)
)
.shadow(radius: 10)
}
} else {
NavigationUIButton {
// TODO: enable location services.
} label: {
Text("Enable Location Services")
}
}
}
.padding()
.padding(.bottom, 72)
.task {
await getRoutes()
}
}
.task {
await getRoutes()
}
}
}
Expand Down Expand Up @@ -232,6 +198,11 @@ struct DemoNavigationView: View {
)
}

func stopNavigation() {
ferrostarCore.stopNavigation()
camera = .center(initialLocation.coordinate, zoom: 14)
}

var locationLabel: String {
guard let userLocation = locationProvider.lastLocation else {
return "No location - authed as \(locationProvider.authorizationStatus)"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "744a0ae659b64807871ea942d6e59965bff2d0e86920ea6d1ba0dc9587578660",
"originHash" : "2de37ddd210ec3a7ac1bb31b310d6cec67f2fc16d8665c70ac79a0a5df2a8d43",
"pins" : [
{
"identity" : "maplibre-gl-native-distribution",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,34 @@ import MapLibreSwiftUI
import SwiftUI

/// A navigation view that dynamically switches between portrait and landscape orientations.
public struct DynamicallyOrientingNavigationView: View {
public struct DynamicallyOrientingNavigationView<
TopCenter: View,
TopTrailing: View,
MidLeading: View,
BottomTrailing: View
>: View {
@Environment(\.navigationFormatterCollection) var formatterCollection: any FormatterCollection

// TODO: Add orientation handling once the landscape view is constructed.
@State private var orientation = UIDeviceOrientation.unknown

let styleURL: URL
let distanceFormatter: Formatter
// TODO: Configurable camera and user "puck" rotation modes

private var navigationState: NavigationState?
private let userLayers: [StyleLayerDefinition]

var topCenter: TopCenter
var topTrailing: TopTrailing
var midLeading: MidLeading
var bottomTrailing: BottomTrailing

@Binding var camera: MapViewCamera
@Binding var snappedZoom: Double
@Binding var useSnappedCamera: Bool

var onTapExit: () -> Void

/// Initialize a map view tuned for turn by turn navigation.
///
/// - Parameters:
Expand All @@ -38,13 +51,23 @@ public struct DynamicallyOrientingNavigationView: View {
camera: Binding<MapViewCamera>,
snappedZoom: Binding<Double>,
useSnappedCamera: Binding<Bool>,
distanceFormatter: Formatter = MKDistanceFormatter(),
@MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] }
onTapExit: @escaping () -> Void = {},
@MapViewContentBuilder makeMapContent: () -> [StyleLayerDefinition] = { [] },
@ViewBuilder topCenter: () -> TopCenter = { Spacer() },
@ViewBuilder topTrailing: () -> TopTrailing = { Spacer() },
@ViewBuilder midLeading: () -> MidLeading = { Spacer() },
@ViewBuilder bottomTrailing: () -> BottomTrailing = { Spacer() }
) {
self.styleURL = styleURL
self.navigationState = navigationState
self.distanceFormatter = distanceFormatter
self.onTapExit = onTapExit

userLayers = makeMapContent()
self.topCenter = topCenter()
self.topTrailing = topTrailing()
self.midLeading = midLeading()
self.bottomTrailing = bottomTrailing()

_camera = camera
_snappedZoom = snappedZoom
_useSnappedCamera = useSnappedCamera
Expand All @@ -61,17 +84,21 @@ public struct DynamicallyOrientingNavigationView: View {
camera: $camera,
snappedZoom: $snappedZoom,
useSnappedCamera: $useSnappedCamera,
distanceFormatter: distanceFormatter
) {
userLayers
}
onTapExit: onTapExit,
makeMapContent: { userLayers },
topCenter: { topCenter },
topTrailing: { topTrailing },
midLeading: { midLeading },
bottomTrailing: { bottomTrailing }
)
}
}
}

#Preview("Portrait Navigation View (Imperial)") {
// TODO: Make map URL configurable but gitignored
let state = NavigationState.modifiedPedestrianExample(droppingNWaypoints: 4)

let formatter = MKDistanceFormatter()
formatter.locale = Locale(identifier: "en-US")
formatter.units = .imperial
Expand All @@ -81,9 +108,9 @@ public struct DynamicallyOrientingNavigationView: View {
navigationState: state,
camera: .constant(.center(state.snappedLocation.clLocation.coordinate, zoom: 12)),
snappedZoom: .constant(18),
useSnappedCamera: .constant(true),
distanceFormatter: formatter
useSnappedCamera: .constant(true)
)
.navigationFormatterCollection(FoundationFormatterCollection(distanceFormatter: formatter))
}

#Preview("Portrait Navigation View (Metric)") {
Expand All @@ -98,7 +125,7 @@ public struct DynamicallyOrientingNavigationView: View {
navigationState: state,
camera: .constant(.center(state.snappedLocation.clLocation.coordinate, zoom: 12)),
snappedZoom: .constant(18),
useSnappedCamera: .constant(true),
distanceFormatter: formatter
useSnappedCamera: .constant(true)
)
.navigationFormatterCollection(FoundationFormatterCollection(distanceFormatter: formatter))
}
Loading
Loading