diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6e72ff5..7a40d4a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,22 +1,17 @@ ## Description - -Please include a summary of the changes and the related issue. Please also include relevant motivation and context. +(Please include a summary of the changes and the related issue. Please also include relevant motivation and context.) ## Related Issues - -Mention your issue by typing the issue number: #(issue number) +(Mention your issue by typing the issue number: #(issue number)) ## Media - -Please include the media if applicable. For UI changes, it is recommended to provide screen record or screenshots +(Please include the media if applicable. For UI changes, it is recommended to provide screen record or screenshots) ## Test Instructions +(Please describe the tests that you ran to verify your changes. Provide instructions so others can reproduce.) -Please describe the tests that you ran to verify your changes. Provide instructions so others can reproduce. - -1. Test A -2. Test B +1. (Test A) +2. (Test B) ## Additional Context - -Add any other context about the pull request here. +(Add any other context about the pull request here.) diff --git a/Examples/OTPKitDemo/OTPKitDemo.xcodeproj/project.pbxproj b/Examples/OTPKitDemo/OTPKitDemo.xcodeproj/project.pbxproj index 134150f..00e93b4 100644 --- a/Examples/OTPKitDemo/OTPKitDemo.xcodeproj/project.pbxproj +++ b/Examples/OTPKitDemo/OTPKitDemo.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0111FD712C6CFBE400B4472E /* OTPKit in Frameworks */ = {isa = PBXBuildFile; productRef = 0111FD702C6CFBE400B4472E /* OTPKit */; }; 014316DF2C6B6F2C00B33240 /* OTPKit in Frameworks */ = {isa = PBXBuildFile; productRef = 014316DE2C6B6F2C00B33240 /* OTPKit */; }; + 015364702C7E93BE00146182 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0153646F2C7E93BE00146182 /* OnboardingView.swift */; }; 01AA23162C758E62008F484E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01AA23152C758E62008F484E /* LaunchScreen.storyboard */; }; 01AA80542C6B6A7500D4038A /* OTPKitDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AA80532C6B6A7500D4038A /* OTPKitDemoApp.swift */; }; 01AA80562C6B6A7500D4038A /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01AA80552C6B6A7500D4038A /* MapView.swift */; }; @@ -18,6 +19,7 @@ /* Begin PBXFileReference section */ 014316E02C6B713D00B33240 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 0153646F2C7E93BE00146182 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; 01AA23152C758E62008F484E /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 01AA80502C6B6A7500D4038A /* OTPKitDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OTPKitDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 01AA80532C6B6A7500D4038A /* OTPKitDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OTPKitDemoApp.swift; sourceTree = ""; }; @@ -62,6 +64,7 @@ 014316E02C6B713D00B33240 /* Info.plist */, 01AA80532C6B6A7500D4038A /* OTPKitDemoApp.swift */, 01AA80552C6B6A7500D4038A /* MapView.swift */, + 0153646F2C7E93BE00146182 /* OnboardingView.swift */, 01AA80572C6B6A7600D4038A /* Assets.xcassets */, 01AA80592C6B6A7600D4038A /* Preview Content */, ); @@ -86,6 +89,7 @@ 01AA804C2C6B6A7500D4038A /* Sources */, 01AA804D2C6B6A7500D4038A /* Frameworks */, 01AA804E2C6B6A7500D4038A /* Resources */, + 933C43692C7EC6B600DEB5B7 /* SwiftLint */, ); buildRules = ( ); @@ -149,11 +153,35 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 933C43692C7EC6B600DEB5B7 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ \"$(uname -m)\" == arm64 ]]; then\n export PATH=\"/opt/homebrew/bin:$PATH\"\nfi\n\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 01AA804C2C6B6A7500D4038A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 015364702C7E93BE00146182 /* OnboardingView.swift in Sources */, 01AA80562C6B6A7500D4038A /* MapView.swift in Sources */, 01AA80542C6B6A7500D4038A /* OTPKitDemoApp.swift in Sources */, ); @@ -291,6 +319,7 @@ DEVELOPMENT_ASSET_PATHS = "\"OTPKitDemo/Preview Content\""; DEVELOPMENT_TEAM = 4ZQCMA634J; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OTPKitDemo/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "OTPKit Demo"; @@ -324,6 +353,7 @@ DEVELOPMENT_ASSET_PATHS = "\"OTPKitDemo/Preview Content\""; DEVELOPMENT_TEAM = 4ZQCMA634J; ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OTPKitDemo/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "OTPKit Demo"; diff --git a/Examples/OTPKitDemo/OTPKitDemo/MapView.swift b/Examples/OTPKitDemo/OTPKitDemo/MapView.swift index 76acd03..b9dcb40 100644 --- a/Examples/OTPKitDemo/OTPKitDemo/MapView.swift +++ b/Examples/OTPKitDemo/OTPKitDemo/MapView.swift @@ -13,28 +13,25 @@ struct MapView: View { @Environment(TripPlannerService.self) private var tripPlanner var body: some View { - TripPlannerExtensionView { - Map(position: tripPlanner.currentCameraPositionBinding, interactionModes: .all) { - tripPlanner.generateMarkers() - tripPlanner.generateMapPolyline() - .stroke(.blue, lineWidth: 5) - } - .mapControls { - if !tripPlanner.isMapMarkingMode { - MapUserLocationButton() - MapPitchToggle() + ZStack { + TripPlannerExtensionView { + Map(position: tripPlanner.currentCameraPositionBinding, interactionModes: .all) { + tripPlanner.generateMarkers() + tripPlanner.generateMapPolyline() + .stroke(.blue, lineWidth: 5) + } + .mapControls { + if !tripPlanner.isMapMarkingMode { + MapUserLocationButton() + MapPitchToggle() + } } } } + } } #Preview { - let planner = TripPlannerService( - apiClient: RestAPI(baseURL: URL(string: "https://otp.prod.sound.obaweb.org/otp/routers/default/")!), - locationManager: CLLocationManager(), - searchCompleter: MKLocalSearchCompleter() - ) - - return MapView() + MapView() } diff --git a/Examples/OTPKitDemo/OTPKitDemo/OTPKitDemoApp.swift b/Examples/OTPKitDemo/OTPKitDemo/OTPKitDemoApp.swift index 4b49c7a..9f1e758 100644 --- a/Examples/OTPKitDemo/OTPKitDemo/OTPKitDemoApp.swift +++ b/Examples/OTPKitDemo/OTPKitDemo/OTPKitDemoApp.swift @@ -21,19 +21,23 @@ import SwiftUI @main struct OTPKitDemoApp: App { - let tripPlannerService = TripPlannerService( - apiClient: RestAPI(baseURL: URL(string: "https://otp.prod.sound.obaweb.org/otp/routers/default/")!), - locationManager: CLLocationManager(), - searchCompleter: MKLocalSearchCompleter() - ) - - let sheetEnvironment = OriginDestinationSheetEnvironment() + @State private var hasCompletedOnboarding = false + @State private var selectedRegionURL: URL? + @State private var tripPlannerService: TripPlannerService? var body: some Scene { WindowGroup { - MapView() - .environment(tripPlannerService) - .environment(sheetEnvironment) + if hasCompletedOnboarding, let service = tripPlannerService { + MapView() + .environment(service) + .environment(OriginDestinationSheetEnvironment()) + } else { + OnboardingView( + hasCompletedOnboarding: $hasCompletedOnboarding, + selectedRegionURL: $selectedRegionURL, + tripPlannerService: $tripPlannerService + ) + } } } } diff --git a/Examples/OTPKitDemo/OTPKitDemo/OnboardingView.swift b/Examples/OTPKitDemo/OTPKitDemo/OnboardingView.swift new file mode 100644 index 0000000..7736eee --- /dev/null +++ b/Examples/OTPKitDemo/OTPKitDemo/OnboardingView.swift @@ -0,0 +1,94 @@ +import SwiftUI +import OTPKit +import MapKit + +/// View to select region for demo purposes +struct OnboardingView: View { + @Binding var hasCompletedOnboarding: Bool + @Binding var selectedRegionURL: URL? + @Binding var tripPlannerService: TripPlannerService? + @State private var selectedRegion: String = "Puget Sound" + + private let regions = [ + "Puget Sound": [ + "url": URL(string: "https://otp.prod.sound.obaweb.org/otp/routers/default/")!, + "center": CLLocationCoordinate2D(latitude: 47.64585, longitude: -122.2963) + ], + "San Diego": [ + "url": URL(string: "https://realtime.sdmts.com:9091/otp/routers/default/")!, + "center": CLLocationCoordinate2D(latitude: 32.731591, longitude: -117.1896335) + ], + "Tampa": [ + "url": URL(string: "https://otp.prod.obahart.org/otp/routers/default/")!, + "center": CLLocationCoordinate2D(latitude: 27.9769105, longitude: -82.445851) + ] + ] + + var body: some View { + VStack { + Spacer(minLength: 20) + Text("Welcome to OTPKitDemo!") + .bold() + .font(.title) + + Text("Please choose your region.") + + List(Array(regions.keys.sorted()), id: \.self) { key in + Button { + selectedRegion = key + } label: { + HStack { + Text(key) + Spacer() + if selectedRegion == key { + Image(systemName: "checkmark") + .foregroundColor(.blue) + } + } + } + .foregroundColor(.primary) + } + + Button { + let selection = regions[selectedRegion]! + + // swiftlint:disable force_cast + let url = selection["url"] as! URL + let center = selection["center"] as! CLLocationCoordinate2D + // swiftlint:enable force_cast + + selectedRegionURL = url + + tripPlannerService = TripPlannerService( + apiClient: RestAPI(baseURL: url), + locationManager: CLLocationManager(), + searchCompleter: MKLocalSearchCompleter() + ) + + tripPlannerService?.changeMapCamera(to: center) + hasCompletedOnboarding = true + + } label: { + Text("OK") + .frame(maxWidth: .infinity, minHeight: 44) + } + .buttonStyle(BorderedProminentButtonStyle()) + .padding() + } + .background(Color(UIColor.systemGroupedBackground)) + } +} + +#Preview { + let planner = TripPlannerService( + apiClient: RestAPI(baseURL: URL(string: "https://otp.prod.sound.obaweb.org/otp/routers/default/")!), + locationManager: CLLocationManager(), + searchCompleter: MKLocalSearchCompleter() + ) + + return OnboardingView( + hasCompletedOnboarding: .constant(true), + selectedRegionURL: .constant(nil), + tripPlannerService: .constant(planner) + ) +} diff --git a/README.md b/README.md index e1407e8..fc8f2fc 100644 --- a/README.md +++ b/README.md @@ -21,15 +21,15 @@ This project was created by Hilmy Veradin with Aaron Brethorst as part of the [G We make extensive use of unit testing in this project to ensure that our code works as expected and our changes do not cause regressions. All PR merges are gated on unit tests passing in GitHub Actions. Please be sure to run tests locally before opening a pull request. Also, please add or update unit tests to account for changes to your code. -### Swiftlint +### SwiftLint -We make extensive use of [Swiflint](https://github.com/realm/SwiftLint) in order to ensure that our code adheres to standard styles and conventions. Please install Swiftlint locally via Homebrew: +We make extensive use of [SwiftLint](https://github.com/realm/SwiftLint) in order to ensure that our code adheres to standard styles and conventions. Please install Swiftlint locally via Homebrew: ``` brew install swiftlint ``` -A clean bill of health from Swiftlint is required for merging pull requests. +A clean bill of health from SwiftLint is required for merging pull requests. ## License diff --git a/Sources/OTPKit/Services/TripPlannerService.swift b/Sources/OTPKit/Services/TripPlannerService.swift index 5b1eff4..bf5dfa4 100644 --- a/Sources/OTPKit/Services/TripPlannerService.swift +++ b/Sources/OTPKit/Services/TripPlannerService.swift @@ -194,6 +194,14 @@ public final class TripPlannerService: NSObject { public func changeMapCamera(_ item: MKMapItem) { currentCameraPosition = MapCameraPosition.item(item) } + + /// Changes the map camera to focus on the given coordinate + /// + /// - Parameter to coordinate: Add the CLLocationCoordinate2D object + public func changeMapCamera(to coordinate: CLLocationCoordinate2D) { + let region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000) + currentCameraPosition = .region(region) + } /// Generates markers for the map based on selected points ///