Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 9 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ If either linting or tests fail, the push will be blocked until issues are fixed
- `RestAPIService/RestAPIService` - OTP 1.x REST API implementation (actor-based)
- `URLDataLoader` - Network request handling
- `Presentation/` - SwiftUI views and ViewModels
- `OTPView` - Main entry point view that sets up the environment
- `TripPlanner` - Main entry point object that creates and presents the trip planning view
- `TripPlannerView` - Primary UI for trip planning
- `ViewModel/TripPlannerViewModel` - Main state management (@MainActor)
- `Sheets/` - Bottom sheet UI components (search, directions, options)
Expand All @@ -103,7 +103,7 @@ If either linting or tests fail, the push will be blocked until issues are fixed

### Key Integration Points

1. **Initialization**: Host app provides an `OTPMapProvider` implementation, creates `OTPConfiguration` with server URL, then instantiates `OTPView`
1. **Initialization**: Host app provides an `OTPMapProvider` implementation, creates `OTPConfiguration` with server URL, then instantiates `TripPlanner`
2. **Map Provider**: OTPKit controls an external map view through the `OTPMapProvider` protocol - host app retains ownership of the actual map view
3. **API Service**: Implement `APIService` protocol for custom networking or use provided `RestAPIService`
4. **Location Services**: `LocationManager.shared` handles location permissions and current location updates
Expand Down Expand Up @@ -144,7 +144,7 @@ If either linting or tests fail, the push will be blocked until issues are fixed
```swift
// Example test pattern for API service
func testFetchPlan() async throws {
let service = RestAPIService(configuration: testConfig)
let service = RestAPIService(baseURL: URL(string: "https://otp.example.com")!)
let request = TripPlanRequest(/* ... */)
let response = try await service.fetchPlan(request)
XCTAssertFalse(response.plan?.itineraries.isEmpty ?? true)
Expand Down Expand Up @@ -181,11 +181,16 @@ let config = OTPConfiguration(
let apiService = RestAPIService(baseURL: config.otpServerURL)

// 5. Create and present OTP view
let otpView = OTPView(
let tripPlanner = TripPlanner(
otpConfig: config,
apiService: apiService,
mapProvider: mapProvider
)

let plannerView = tripPlanner.createTripPlannerView {
// Handle close
}

```

### Custom Theme Configuration
Expand Down
103 changes: 73 additions & 30 deletions README.markdown
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# OTPKit

[![Swift](https://img.shields.io/badge/Swift-5.9-orange.svg)](https://swift.org)
[![iOS](https://img.shields.io/badge/iOS-17.0%2B-lightgrey.svg)](https://developer.apple.com/ios/)
[![iOS](https://img.shields.io/badge/iOS-18.0%2B-lightgrey.svg)](https://developer.apple.com/ios/)
[![SPM](https://img.shields.io/badge/SPM-Supported-brightgreen.svg)](https://swift.org/package-manager/)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Build](https://github.com/OneBusAway/otpkit/actions/workflows/ci.yml/badge.svg)](https://github.com/OneBusAway/otpkit/actions)
[![Build](https://github.com/OneBusAway/otpkit/actions/workflows/test.yaml/badge.svg)](https://github.com/OneBusAway/otpkit/actions/workflows/test.yaml)

![otpkit-showcase](https://github.com/user-attachments/assets/c5f819f0-4803-4a6e-86df-55f2677499c3)

Expand All @@ -31,32 +31,63 @@ dependencies: [
### SwiftUI Usage
```swift
import OTPKit
import SwiftUI
import MapKit

struct ContentView: View {
var body: some View {
// 1. Define the search region for location suggestions
let searchRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 47.6062, longitude: -122.3321),
latitudinalMeters: 50000,
longitudinalMeters: 50000
)

// 2. Configure OTPKit with server URL, theme, and search region
let config = OTPConfiguration(
otpServerURL: URL(string: "https://your-otp-server.com")!,
themeConfiguration: OTPThemeConfiguration(primaryColor: .blue, secondaryColor: .gray),
searchRegion: searchRegion
)

// 3. Create API service for OpenTripPlanner
let apiService = RestAPIService(baseURL: config.otpServerURL)

VStack {
// 4. Add complete trip planner to your app
OTPView(otpConfig: config, apiService: apiService)
}
}
struct OTPHostView: UIViewControllerRepresentable {
let otpServerURL: URL

func makeUIViewController(context: Context) -> OTPDemoViewController {
OTPDemoViewController(serverURL: otpServerURL)
}

func updateUIViewController(_ uiViewController: OTPDemoViewController, context: Context) {}
}

final class OTPDemoViewController: UIViewController {
private let serverURL: URL
private var hostingController: UIHostingController<AnyView>?
private var mapView = MKMapView()

init(serverURL: URL) {
self.serverURL = serverURL
super.init(nibName: nil, bundle: nil)
}

required init?(coder: NSCoder) { nil }

override func viewDidLoad() {
super.viewDidLoad()

let config = OTPConfiguration(
otpServerURL: serverURL,
searchRegion: MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 47.6062, longitude: -122.3321),
latitudinalMeters: 50000,
longitudinalMeters: 50000
)
)

let apiService = RestAPIService(baseURL: config.otpServerURL)
let mapProvider = MKMapViewAdapter(mapView: mapView)
let tripPlanner = TripPlanner(
otpConfig: config,
apiService: apiService,
mapProvider: mapProvider
)

let plannerView = AnyView(tripPlanner.createTripPlannerView {
self.dismiss(animated: true)
})

let host = UIHostingController(rootView: plannerView)
addChild(host)
view.addSubview(host.view)
host.view.frame = view.bounds
host.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
host.didMove(toParent: self)
hostingController = host
}
}
```

Expand All @@ -65,8 +96,8 @@ struct ContentView: View {
```swift
import OTPKit
import MapKit
import SwiftUI

// Define the search region for location suggestions
let searchRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 47.6062, longitude: -122.3321),
latitudinalMeters: 50000,
Expand All @@ -79,12 +110,24 @@ let config = OTPConfiguration(
)

let apiService = RestAPIService(baseURL: config.otpServerURL)
let tripPlannerView = OTPView(otpConfig: config, apiService: apiService)
let mapView = MKMapView()
let mapProvider = MKMapViewAdapter(mapView: mapView)

let tripPlanner = TripPlanner(
otpConfig: config,
apiService: apiService,
mapProvider: mapProvider
)

let plannerView = AnyView(tripPlanner.createTripPlannerView {
// Handle close
})

// Embed in UIKit using UIHostingController
let hostingController = UIHostingController(rootView: tripPlannerView)
let hostingController = UIHostingController(rootView: plannerView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.frame = view.bounds
hostingController.didMove(toParent: self)
```

## Development
Expand Down
Loading