From 63e87128786f3e91160b8f097a87f4da7fd223df Mon Sep 17 00:00:00 2001 From: Aaron Brethorst Date: Wed, 29 Jul 2026 22:29:32 -0700 Subject: [PATCH] Rewrite CLAUDE.md and README from scratch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLAUDE.md now reflects the current architecture (TripPlanner facade, OTPMapProvider inversion of control, REST/GraphQL services, vehicle rental stack) and documents the real build/test/lint commands. The README is reorganized around what visitors come for — evaluating OTPKit for their app, trying the demo, or contributing — with working integration code (the old samples used the removed OTPView API), corrected badges, and an install snippet pinned to the latest tag. --- CLAUDE.md | 282 ++++++------------------------------------------ README.markdown | 241 +++++++++++++++++++++++------------------ 2 files changed, 169 insertions(+), 354 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index d25683e..7ea07fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,279 +4,67 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -OTPKit is an OpenTripPlanner client library for iOS, supporting OTP 1.x (REST API) and 2.x (GraphQL API). A OneBusAway Project from the Open Transit Software Foundation. +OTPKit is an OpenTripPlanner client library for iOS (a OneBusAway / Open Transit Software Foundation project). It provides networking, models, and a complete SwiftUI trip-planning UI that host apps embed over their own map view. Supports OTP 1.x (REST) and OTP 2.x (GTFS GraphQL) servers. -### Project Structure -- **OTPKit**: Swift Package containing the core library (iOS 18+, Swift 6.0) -- **OTPKitDemo**: Demo iOS app (Xcode project) showcasing OTPKit functionality -- **OTPKitDemoTests**: Test suite for the demo app using Swift Testing framework -- **Tests/**: Package-level tests using XCTest +Layout: +- `Package.swift` (repo root) — Swift package; sources at `OTPKit/Sources`, tests at `OTPKit/Tests`. iOS 18+, swift-tools 6.0, but `swiftLanguageModes: [.v5]`. +- `Demo/OTPKitDemo.xcodeproj` — UIKit demo app that depends on the local package. +- Dependencies: SwiftUI-Flow (library), ViewInspector (tests only). -The repository contains both a Swift Package (Package.swift) and an Xcode project (OTPKitDemo.xcodeproj). The Xcode project depends on the local Swift Package. +The package is iOS-only (SwiftUI/MapKit/UIKit APIs) — `swift build` / `swift test` on macOS will not work. Always build through xcodebuild with a simulator destination. -### API Support Status -- **OTP 1.x REST API**: ✅ Fully implemented via `RestAPIService` -- **OTP 2.x GraphQL API**: ✅ Implemented via `GraphQLAPIService` (GTFS GraphQL API `plan` query) +## Commands -## Tests and Quality - -### Run Tests ```bash -# Run all tests via Xcode +# Run all package tests xcodebuild test -scheme OTPKit -destination 'platform=iOS Simulator,name=iPhone 17 Pro' -``` -### Linting & Code Quality -```bash -# Run SwiftLint -swiftlint +# Run a single test file/suite (tests are mostly Swift Testing, a couple XCTest) +xcodebuild test -scheme OTPKit \ + -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ + -only-testing:OTPKitTests/TripPlannerViewModelTests -# Auto-fix issues +# Lint — CI runs strict mode and fails the build before tests even run +swiftlint --strict swiftlint --fix - -# Install SwiftLint if needed -brew install swiftlint ``` -#### Pre-commit Hooks -The project uses [pre-commit](https://pre-commit.com) to automatically run SwiftLint and tests before pushing to GitHub. - -```bash -# Install pre-commit (first time setup) -brew install pre-commit - -# Install the git hook scripts for pre-push -pre-commit install --hook-type pre-push - -# (Optional) Run against all files manually -pre-commit run --all-files -``` +CI (`.github/workflows/test.yaml`) runs `swiftlint --strict`, then build-for-testing/test-without-building on **Xcode 26.2**. A local Xcode beta may be newer and more lenient about implicit framework imports — add explicit `import CoreLocation` / `import MapKit` etc. where symbols are used, or CI will fail on code that builds locally. -Once installed, the following checks will run automatically before each push: -1. **SwiftLint** - Code style and best practices validation -2. **Xcode Tests** - Full test suite must pass +SwiftLint config is `.swiftlint.yml` (root), with additional rules disabled for tests in `OTPKit/Tests/.swiftlint.yml`. -If either linting or tests fail, the push will be blocked until issues are fixed. +Optional pre-push hooks via pre-commit (`pre-commit install --hook-type pre-push`) run strict SwiftLint plus the full test suite. ## Architecture -### Package Dependencies -- **External**: - - `SwiftUI-Flow` (from tevelee/SwiftUI-Flow.git, 3.1.0+) - Flow layout for SwiftUI -- **System Frameworks**: - - SwiftUI (UI components) - - MapKit (Map display and interactions) - - CoreLocation (User location services) - - Foundation (Core utilities) - -### Core Components - -**OTPKit Package Structure:** -- `Core/` - Core models, configuration, and types - - `OTPConfiguration` - Main configuration containing server URL, transport modes, and theme - - `Models/TripPlanner/` - Data models for trip planning - - `Itinerary` - Complete journey with timing and segments - - `Leg` - Individual journey segment (walk, transit, etc.) - - `Place` - Location with coordinates and name - - `TransportMode` - Available transport options - - `TripPlanRequest` - Request parameters for trip planning - - `Helper/Location/` - Location services - - `LocationManager` - Singleton for CoreLocation integration - - `SearchManager` - Location search and geocoding - - `Types/` - Custom error types and enums - - `Map/` - Map coordination and provider abstraction - - `OTPMapProvider` - Protocol for external map implementations - - `MapCoordinator` - Manages map state and operations - - `MKMapViewAdapter` - MapKit implementation of OTPMapProvider -- `Network/` - API communication layer - - `APIService` - Protocol defining trip planning interface - - `RestAPIService` - OTP 1.x REST API implementation (actor-based) - - `GraphQLAPIService` - OTP 2.x GTFS GraphQL API implementation (actor-based) - - `URLDataLoader` - Network request handling -- `Presentation/` - SwiftUI views and ViewModels - - `OTPView` - Main entry point view that sets up the environment - - `TripPlannerView` - Primary UI for trip planning - - `ViewModel/TripPlannerViewModel` - Main state management (@MainActor) - - `Sheets/` - Bottom sheet UI components (search, directions, options) - - `BottomControls/` - Controls for location selection and planning - - `TopControls/` - Top UI controls - - `OTPPanel/` - Bottom sheet implementation - -### Key Integration Points - -1. **Initialization**: Host app provides an `OTPMapProvider` implementation, creates `OTPConfiguration` with server URL, then instantiates `OTPView` -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 -5. **Theme Customization**: Configure appearance via `OTPThemeConfiguration` in the config - -### Data Flow - -1. User interactions in UI views trigger actions in `TripPlannerViewModel` -2. ViewModel calls `APIService` to fetch trip plans from OTP server -3. Response models (OTPResponse, Itinerary, etc.) are decoded and stored in ViewModel -4. UI updates reactively via SwiftUI property wrappers (@Published, @StateObject) -5. Map updates are coordinated through `MapCoordinator` which calls methods on the `OTPMapProvider` - -## Development Notes - -- Minimum iOS version: 18.0 -- Swift version: 6.0 -- Swift language mode: Swift 5 (set in Package.swift via `swiftLanguageModes: [.v5]`) -- The package uses SwiftUI and requires iOS platform features (not buildable for macOS due to iOS-specific APIs) -- Location permissions are handled automatically by LocationManager -- The demo app includes an onboarding flow for server configuration -- All models conform to `Codable` for JSON serialization -- Views use `@StateObject`, `@Published`, and `@EnvironmentObject` for reactive updates -- `RestAPIService` is an actor for thread-safe network operations -- `TripPlannerViewModel` is marked `@MainActor` for UI thread safety +### Entry point and ownership model -## Testing +`TripPlanner` (`OTPKit/Sources/OTPKit/Presentation/TripPlanner.swift`, `@MainActor`) is the public facade. The host app constructs it with three things it owns: -### Testing Framework -- **OTPKit Package Tests**: Uses XCTest with async/await patterns -- **Demo App Tests**: Uses Swift Testing framework (`@Test` attribute) -- **Test Helpers**: Located in `OTPKitDemoTests/Helpers/` - - `MockDataLoader` - Mock network responses - - `Fixtures` - Test data and JSON responses - - `OTPTestCase` - Base test class with common setup +1. `OTPConfiguration` — server URL, enabled transport modes, theme (`OTPThemeConfiguration`), and a required `searchRegion` (`MKCoordinateRegion`) that scopes location search. +2. An `APIService` implementation. +3. An `OTPMapProvider` implementation. -### Running Tests -```swift -// Example test pattern for API service -func testFetchPlan() async throws { - let service = RestAPIService(configuration: testConfig) - let request = TripPlanRequest(/* ... */) - let response = try await service.fetchPlan(request) - XCTAssertFalse(response.plan?.itineraries.isEmpty ?? true) -} -``` - -## Common Integration Scenarios - -### Basic Setup -```swift -import OTPKit -import MapKit +`TripPlanner` internally wires up `MapCoordinator` and `TripPlannerViewModel`, and `createTripPlannerView(origin:destination:viaPoint:transportMode:onClose:)` returns the SwiftUI UI (`TripPlannerView`), which the demo hosts in a `PanelHostingController` bottom sheet. Cross-object events flow through `NotificationCenter` (injectable; see `Core/Notifications.swift`). -// 1. Create your map view (OTPKit doesn't provide one) -let mapView = MKMapView() -let mapProvider = MKMapViewAdapter(mapView: mapView) +### The map is inversion-of-control -// 2. Define the search region for location suggestions -let searchRegion = MKCoordinateRegion( - center: CLLocationCoordinate2D(latitude: 47.6062, longitude: -122.3321), - latitudinalMeters: 50000, - longitudinalMeters: 50000 -) +OTPKit never creates a map. The host app owns the map view and hands OTPKit an `OTPMapProvider` (protocol in `Core/Map/OTPMapProvider.swift`) — `MKMapViewAdapter` is the provided MapKit implementation. All map mutations (routes, annotations, camera) go through `MapCoordinator`, which is the only thing that talks to the provider. -// 3. Create configuration with required search region -let config = OTPConfiguration( - otpServerURL: URL(string: "https://otp.example.com")!, - enabledTransportModes: [.transit, .walk, .bike], - themeConfiguration: OTPThemeConfiguration(primaryColor: .blue), - searchRegion: searchRegion -) +### Networking (`OTPKit/Sources/OTPKit/Network/`) -// 4. Initialize API service -let apiService = RestAPIService(baseURL: config.otpServerURL) - -// 5. Create and present OTP view -let otpView = OTPView( - otpConfig: config, - apiService: apiService, - mapProvider: mapProvider -) -``` - -### Custom Theme Configuration -```swift -let themeConfig = OTPThemeConfiguration( - primaryColor: .blue, - secondaryColor: .green, - backgroundColor: .systemBackground -) -let searchRegion = MKCoordinateRegion( - center: CLLocationCoordinate2D(latitude: 47.6062, longitude: -122.3321), - latitudinalMeters: 50000, - longitudinalMeters: 50000 -) -let config = OTPConfiguration( - otpServerURL: url, - themeConfiguration: themeConfig, - searchRegion: searchRegion -) -``` - -### Implementing Custom API Service -```swift -public actor CustomAPIService: APIService { - public func fetchPlan(_ request: TripPlanRequest) async throws -> OTPResponse { - // Custom implementation - // e.g., add authentication, caching, etc. - } -} -``` +`APIService` is the protocol for trip planning; both implementations are actors: +- `RestAPIService` — OTP 1.x REST `/plan` API. +- `GraphQLAPIService` — OTP 2.x GTFS GraphQL `plan` query (`GraphQLPlanResponse` for decoding). -### Implementing Custom Map Provider -```swift -class CustomMapProvider: OTPMapProvider { - // Implement all required protocol methods - // to integrate with your preferred mapping solution - func addRoute(coordinates: [CLLocationCoordinate2D], color: Color, lineWidth: CGFloat, identifier: String) { - // Add route to your map - } - - func addAnnotation(coordinate: CLLocationCoordinate2D, title: String, subtitle: String?, identifier: String, type: OTPAnnotationType) { - // Add marker to your map - } - - // ... implement remaining protocol methods -} -``` - -## State Management Patterns - -### ViewModel Architecture -- `TripPlannerViewModel` is marked with `@MainActor` for UI thread safety -- Uses `@Published` properties for reactive UI updates -- Async/await for API calls with proper error handling - -### Location State Management -```swift -// LocationManager is a singleton -LocationManager.shared.requestLocationPermission() - -// Subscribe to location updates -LocationManager.shared.$currentLocation - .sink { location in - // Handle location update - } -``` +The demo picks REST vs GraphQL per region (`OTPRegionInfo.apiType`). Requests go through `URLDataLoader`, which tests replace with `MockDataLoader` to serve JSON fixtures. -### Sheet Presentation Pattern -- Sheets are defined in the `Sheet` enum: `.locationOptions`, `.directions`, `.search`, `.advancedOptions` -- Each sheet view is self-contained with its own state +Vehicle rental (bikeshare/scooter) is a separate stack: `VehicleRentalSource` (protocol) / `VehicleRentalService` (GraphQL implementation, Sendable) fetch rental stations and free-floating vehicles, filterable by `VehicleFormFactor`. Rental-aware planning uses `viaPoint` on `createTripPlannerView` ("plan a trip using this bike"); OTP servers may require a transit mode (`.transitBikeRental`, not `.bikeRental`) to route through a via point. -## Code Style & Conventions +### State management -### Swift Conventions -- Use Swift 6.0 features (async/await, actors, etc.) -- Follow Swift API Design Guidelines -- Use meaningful variable names (avoid abbreviations) -- Prefer `let` over `var` when possible -- Use trailing closure syntax for single closure parameters -- SwiftLint configuration in `.swiftlint.yml` (disabled rules: `identifier_name`, `todo`; excluded: `OTPKit/.build`) +`TripPlannerViewModel` (`ObservableObject`, main-actor) is the single source of truth for the planning flow: origin/destination selection, plan requests via `APIService`, itinerary/leg selection, and driving `MapCoordinator`. Views in `Presentation/` (Sheets for search/directions/advanced options, TopControls, Buttons) all observe it. `Core/TripProgress/` computes live progress along an active itinerary. `UserDefaultsServices` (`Core/Services/`) persists recent locations. -### SwiftUI Best Practices -- Keep views small and focused (extract subviews) -- Use view modifiers for reusable styling -- Prefer `@StateObject` for view-owned objects -- Use `@EnvironmentObject` for shared state -- Extract complex logic to ViewModels +### Tests (`OTPKit/Tests/`) -### File Organization -- Group related files in folders -- Keep protocols and implementations separate -- Place extensions in separate files when substantial -- Use descriptive file names matching the primary type +Predominantly Swift Testing (`@Test`); `RestAPIServiceTests`/comprehensive tests still use XCTest. JSON fixtures live in `OTPKit/Tests/Fixtures` (copied as a package resource) and load via `Helpers/Fixtures.swift`. `MockDataLoader` stubs the network; `MockMapProvider` stubs the map. `LocalizationTests` verifies the 13 `.lproj` string tables in `Resources/` stay in sync — new user-facing strings must be added to all of them. diff --git a/README.markdown b/README.markdown index 98d0782..6026e3d 100644 --- a/README.markdown +++ b/README.markdown @@ -1,104 +1,124 @@ # 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/) +[![Swift](https://img.shields.io/badge/Swift-6.0-orange.svg)](https://swift.org) +[![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) + +A modern [OpenTripPlanner](https://www.opentripplanner.org) client for iOS, written in Swift and SwiftUI. OTPKit gives your app a complete, production-quality trip planning experience — search, itineraries, turn-by-turn directions, live trip progress — rendered on top of a map view that *you* own. It powers trip planning in the [OneBusAway iOS app](https://github.com/OneBusAway/onebusaway-ios) and is a project of the [Open Transit Software Foundation](https://opentransitsoftwarefoundation.org). ![otpkit-showcase](https://github.com/user-attachments/assets/c5f819f0-4803-4a6e-86df-55f2677499c3) -# Introduction -OpenTripPlanner library for iOS, written in Swift. -**OTPKit** is a reusable library that powers trip planning in the [OneBusAway iOS app](https://github.com/OneBusAway/onebusaway-ios) and can be integrated into any iOS application. +**Jump to what you're here for:** + +- [Add trip planning to your iOS app](#add-trip-planning-to-your-app) +- [See it running in five minutes](#see-it-running-the-demo-app) +- [Contribute to the project](#contributing) + +## Why OTPKit? + +- **Complete UI, not just a networking layer.** Origin/destination search, transport mode selection, itinerary comparison, step-by-step directions, and live progress along an active trip — all included, all SwiftUI, localized into 13 languages. +- **Bring your own map.** OTPKit draws routes and annotations through a small `OTPMapProvider` protocol. Use the bundled `MKMapViewAdapter` for MapKit, or implement the protocol to keep your existing map stack. +- **Works with both OTP generations.** OTP 1.x (REST) and OTP 2.x (GTFS GraphQL) behind a single `APIService` protocol — pick the implementation that matches your server and the rest of your code doesn't change. +- **Shared-mobility aware.** On OTP 2.x servers, OTPKit can fetch bike/scooter share stations and free-floating vehicles and plan rental-aware trips ("get me there using this bike"). +- **Proven in production.** This is the trip planner inside OneBusAway iOS, exercised daily by real riders. + +### Requirements -- Compatible with **iOS 18+** -- Works with **OpenTripPlanner 1.5.x and higher** -- Licensed under **Apache 2.0** -- Provides networking, models, and APIs for building a complete trip planning experience +| | | +|---|---| +| iOS | 18.0+ | +| Swift | 6.0 toolchain (package builds in Swift 5 language mode) | +| Server | OpenTripPlanner 1.5.x+ (REST) or 2.x (GTFS GraphQL) | -## Quick Start +## Add trip planning to your app -### Installation +### 1. Install -Add **OTPKit** to your iOS project using **Swift Package Manager**: +Add OTPKit with Swift Package Manager: ```swift dependencies: [ - .package(url: "https://github.com/OneBusAway/OTPKit.git", from: "1.0.0") + .package(url: "https://github.com/OneBusAway/OTPKit.git", from: "0.16.0") ] ``` -### SwiftUI Usage + +OTPKit is pre-1.0: releases are tagged and safe to pin, but the API may still change between minor versions. + +### 2. Integrate + +Three pieces wire together: a **map provider** (adapting the map view you own), an **API service** (matching your OTP server's generation), and a **`TripPlanner`** (which builds the UI). Here's the complete setup in a UIKit view controller, matching what the demo app does: + ```swift -import OTPKit 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) - } - } +import OTPKit +import SwiftUI +import UIKit + +class ViewController: UIViewController { + private var tripPlanner: TripPlanner? + + override func viewDidLoad() { + super.viewDidLoad() + + // 1. You own the map view; give OTPKit an adapter for it. + let mapView = MKMapView(frame: view.bounds) + view.addSubview(mapView) + let mapProvider = MKMapViewAdapter(mapView: mapView) + + // 2. Point OTPKit at your server, and scope location search + // to your service area. + let config = OTPConfiguration( + otpServerURL: URL(string: "https://otp.example.com/otp/")!, + searchRegion: MKCoordinateRegion( + center: CLLocationCoordinate2D(latitude: 47.6062, longitude: -122.3321), + latitudinalMeters: 50000, + longitudinalMeters: 50000 + ) + ) + + // 3. Pick the service that matches your server: + // RestAPIService for OTP 1.x, GraphQLAPIService for OTP 2.x. + let apiService = RestAPIService(baseURL: config.otpServerURL) + + // 4. Create the planner and present its UI as a bottom sheet. + let planner = TripPlanner( + otpConfig: config, + apiService: apiService, + mapProvider: mapProvider + ) + let plannerView = planner.createTripPlannerView { [weak self] in + self?.dismiss(animated: true) + } + present(PanelHostingController(rootView: plannerView, sourceView: view), animated: true) + tripPlanner = planner + } } ``` -### UIKit Usage +`createTripPlannerView` returns a plain SwiftUI view, so you're not locked into the bottom-sheet presentation — host it however your app's navigation works. It also accepts optional prefill parameters (`origin`, `destination`, `viaPoint`, `transportMode`) for deep-linking straight into a planned trip. -```swift -import OTPKit -import MapKit +### Which API service do I use? -// Define the search region for location suggestions -let searchRegion = MKCoordinateRegion( - center: CLLocationCoordinate2D(latitude: 47.6062, longitude: -122.3321), - latitudinalMeters: 50000, - longitudinalMeters: 50000 -) - -let config = OTPConfiguration( - otpServerURL: URL(string: "https://your-otp-server.com")!, - searchRegion: searchRegion -) - -let apiService = RestAPIService(baseURL: config.otpServerURL) -let tripPlannerView = OTPView(otpConfig: config, apiService: apiService) - -// Embed in UIKit using UIHostingController -let hostingController = UIHostingController(rootView: tripPlannerView) -addChild(hostingController) -view.addSubview(hostingController.view) -``` +| Your OTP server | Use | Notes | +|---|---|---| +| OTP 1.x (REST `/plan` endpoint) | `RestAPIService` | | +| OTP 2.x (GTFS GraphQL API) | `GraphQLAPIService` | Required for bike/scooter rental features | + +Both are Swift actors conforming to `APIService`; you can also implement `APIService` yourself to add authentication, caching, or a custom backend. -## Localization +### Customizing -OTPKit ships translations for Arabic, English, Filipino, French, Italian, Korean, Polish, -Portuguese (Brazil), Russian, Simplified Chinese, Spanish, Traditional Chinese, and Vietnamese. +- **Transport modes:** pass `enabledTransportModes` to `OTPConfiguration` (defaults to transit, walk, bike, car). Rental modes like `.bikeRental` are opt-in and need an OTP 2.x server with rental data. +- **Theme:** pass an `OTPThemeConfiguration` to adjust colors. +- **Map behavior:** implement `OTPMapProvider` to control exactly how routes and stops render on your map. -**Your app must declare which of these languages it supports, or OTPKit will render in English.** -iOS resolves an app's language from the *main* bundle, and OTPKit's strings follow that -resolution — so a host app that ships no localizations pins the whole process to English no -matter what the device language is. +### Localization -If your app is already localized into these languages, there is nothing to do. Otherwise, add -the ones you want to your app target's `Info.plist`: +OTPKit ships translations for Arabic, English, Filipino, French, Italian, Korean, Polish, Portuguese (Brazil), Russian, Simplified Chinese, Spanish, Traditional Chinese, and Vietnamese. + +**Your app must declare which of these languages it supports, or OTPKit will render in English.** iOS resolves an app's language from the *main* bundle, so a host app that ships no localizations pins the whole process to English regardless of the device language. If your app is already localized into the languages you care about, there's nothing to do. Otherwise, declare them in your app target's `Info.plist`: ```xml CFBundleLocalizations @@ -119,57 +139,64 @@ the ones you want to your app target's `Info.plist`: ``` -OTPKit resolves its strings from its own resource bundle, so defining the same keys in your -app's `Localizable.strings` will not override them. To change or add wording, edit -[`OTPKit/Sources/OTPKit/Resources/en.lproj/Localizable.strings`](OTPKit/Sources/OTPKit/Resources/en.lproj/Localizable.strings) -and its sibling locales. - -## Development +OTPKit resolves strings from its own resource bundle, so redefining the same keys in your app's `Localizable.strings` will not override them. To change or add wording, edit [`OTPKit/Sources/OTPKit/Resources/en.lproj/Localizable.strings`](OTPKit/Sources/OTPKit/Resources/en.lproj/Localizable.strings) and its sibling locales. -### SwiftLint +## See it running: the demo app -OTPKit uses [SwiftLint](https://github.com/realm/SwiftLint) to enforce consistent code style and Swift best practices. -Install it locally using Homebrew: +No configuration needed — the demo ships with working public OTP servers: ```bash -brew install swiftlint +git clone https://github.com/OneBusAway/otpkit.git +cd otpkit +open Demo/OTPKitDemo.xcodeproj ``` -### Pre-commit Hooks +Run the **OTPKitDemo** scheme on an iOS simulator, then pick a region on first launch: -The project uses [pre-commit](https://pre-commit.com) to automatically run SwiftLint and tests before pushing to GitHub. +- **San Diego** — OTP 1.x REST +- **Seattle** — OTP 1.x REST +- **Seattle (OTP 2.x GraphQL)** — the GraphQL API, including bike share -```bash -# Install pre-commit (first time setup) -brew install pre-commit +Tip: set the simulator's location (Features → Location) to somewhere inside the region you picked so "current location" trip planning works. -# Install the git hook scripts for pre-push -pre-commit install --hook-type pre-push +## Contributing + +Issues and pull requests are welcome. The library lives in `OTPKit/` (sources and tests) with `Package.swift` at the repo root; the demo app lives in `Demo/`. -# (Optional) Run against all files manually -pre-commit run --all-files --hook-stage pre-push +### Build and test + +The package uses iOS-only frameworks, so build and test through Xcode (not `swift test`): + +```bash +# Run the test suite +xcodebuild test -scheme OTPKit -destination 'platform=iOS Simulator,name=iPhone 17 Pro' ``` -Once installed, the following checks will run automatically before each push: -1. **SwiftLint** - Code style and best practices validation -2. **Xcode Tests** - Full test suite must pass +### Lint + +CI runs SwiftLint in strict mode and fails fast, so run it before pushing: -If either linting or tests fail, the push will be blocked until issues are fixed. +```bash +brew install swiftlint +swiftlint --strict +``` -
+### Pre-push hooks (recommended) -## About the project +[pre-commit](https://pre-commit.com) can run SwiftLint and the test suite automatically before every push: -This project was developed as part of **[Google Summer of Code 2025](https://summerofcode.withgoogle.com/programs/2025/projects/7hA4Gs1k)**, created by **Manu Rajbhar** with guidance from **Aaron Brethorst**. +```bash +brew install pre-commit +pre-commit install --hook-type pre-push +``` -You can read the full final report here: [GSoC 2025 Final Report – OTPKit](https://gist.github.com/manu-r12/cf10fd8c05bc0cab2ca258953e3f8b2b) +## About the project -# License +OTPKit began as a **Google Summer of Code** project and continues to grow with each cohort: -Licensed under Apache 2.0. See LICENSE for more details. +- [GSoC 2025](https://summerofcode.withgoogle.com/programs/2025/projects/7hA4Gs1k): built by **[Manu R](https://github.com/manu-r12)** with mentorship from **[Aaron Brethorst](https://github.com/aaronbrethorst)** — read the [final report](https://gist.github.com/manu-r12/cf10fd8c05bc0cab2ca258953e3f8b2b) +- GSoC 2024: **[Hilmy Veradin](https://github.com/hilmyveradin)** -# Contributors +## License -* [Aaron Brethorst](https://github.com/aaronbrethorst) -* [Manu R](https://github.com/manu-r12) - GSoC 2025 contributor -* [Hilmy Veradin](https://github.com/hilmyveradin) - GSoC 2024 contributor \ No newline at end of file +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.