├── compass-tools-android ├── api │ └── compass-tools-android.api ├── src │ └── androidMain │ │ ├── kotlin │ │ └── dev │ │ │ └── jordond │ │ │ └── compass │ │ │ └── tools │ │ │ ├── ContextProviderInitializer.kt │ │ │ └── ContextProvider.kt │ │ └── AndroidManifest.xml └── build.gradle.kts ├── art └── logo-full.png ├── demo ├── iosApp │ ├── Configuration │ │ └── Config.xcconfig │ └── iosApp │ │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── app-icon-1024.png │ │ │ └── Contents.json │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ │ ├── iOSApp.swift │ │ ├── ContentView.swift │ │ └── Info.plist ├── composeApp │ └── src │ │ ├── androidMain │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── kotlin │ │ │ └── dev │ │ │ │ └── jordond │ │ │ │ └── compass │ │ │ │ └── demo │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ │ ├── iosMain │ │ └── kotlin │ │ │ └── MainViewController.kt │ │ ├── commonMain │ │ └── kotlin │ │ │ ├── geolocation │ │ │ └── BrowserGeolocationScreen.kt │ │ │ ├── geocoder │ │ │ ├── PlatformGeocoderScreen.kt │ │ │ ├── PlatformGeocoderFallbackScreen.kt │ │ │ ├── MapboxGeocoderScreen.kt │ │ │ ├── OpenCageGeocoderScreen.kt │ │ │ └── GoogleMapsGeocoderScreen.kt │ │ │ ├── Platform.kt │ │ │ └── App.kt │ │ ├── desktopMain │ │ └── kotlin │ │ │ └── main.kt │ │ ├── wasmJsMain │ │ ├── kotlin │ │ │ ├── main.kt │ │ │ └── geolocation │ │ │ │ └── BrowserGeolocationScreen.browser.kt │ │ └── resources │ │ │ └── index.html │ │ ├── browserMain │ │ └── resources │ │ │ └── index.html │ │ ├── nonBrowser │ │ └── kotlin │ │ │ └── geolocation │ │ │ └── BrowserGeolocationScreen.nonBrowser.kt │ │ ├── mobileMain │ │ └── kotlin │ │ │ └── Platform.mobile.kt │ │ └── nonMobileMain │ │ └── kotlin │ │ └── Platform.nonMobile.kt └── README.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── compass-geolocation-browser ├── src │ ├── jsMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── jordond │ │ │ └── compass │ │ │ └── geolocation │ │ │ └── browser │ │ │ ├── internal │ │ │ └── Platform.js.kt │ │ │ └── api │ │ │ ├── Navigator.js.kt │ │ │ ├── model │ │ │ └── CreateGeolocationOptions.kt │ │ │ └── Geolocation.js.kt │ ├── wasmJsMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── jordond │ │ │ └── compass │ │ │ └── geolocation │ │ │ └── browser │ │ │ ├── internal │ │ │ └── Platform.wasmJs.kt │ │ │ └── api │ │ │ ├── Navigator.wasmJs.kt │ │ │ ├── model │ │ │ └── CreateGeolocationOptions.kt │ │ │ └── Geolocation.wasmJs.kt │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── geolocation │ │ ├── browser │ │ ├── internal │ │ │ ├── Platform.kt │ │ │ └── Mapper.kt │ │ ├── api │ │ │ ├── model │ │ │ │ ├── GeolocationPosition.kt │ │ │ │ ├── GeolocationPositionError.kt │ │ │ │ ├── CreateGeolocationOptions.kt │ │ │ │ └── GeolocationCoordinates.kt │ │ │ └── Navigator.kt │ │ └── BrowserLocator.kt │ │ └── BrowserGeolocator.kt └── build.gradle.kts ├── .github ├── ci-gradle.properties ├── dependabot.yml └── version.sh ├── docs ├── roadmap.md ├── autocomplete │ ├── via-geocoding.md │ ├── geocoding │ │ ├── web-api-service.md │ │ └── android-ios.md │ └── overview.md ├── geocoding │ ├── request-a-geocoder-api.md │ └── web-api-service │ │ ├── mapbox.md │ │ ├── opencage.md │ │ └── google-maps.md ├── misc │ ├── demo.md │ └── contributing.md ├── geocoder │ └── overview.md ├── geolocation │ ├── location.md │ ├── browser.md │ ├── overview.md │ └── android-ios.md └── SUMMARY.md ├── compass-geocoder-mobile ├── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── dev │ │ │ └── jordond │ │ │ └── compass │ │ │ └── geocoder │ │ │ └── mobile │ │ │ └── internal │ │ │ ├── AddressMapper.kt │ │ │ └── Operation.android.kt │ ├── commonMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── jordond │ │ │ └── compass │ │ │ └── geocoder │ │ │ ├── mobile │ │ │ ├── Extensions.kt │ │ │ └── MobilePlatformGeocoder.kt │ │ │ └── MobileGeocoder.kt │ └── iosMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── geocoder │ │ └── mobile │ │ ├── internal │ │ ├── Operation.ios.kt │ │ └── PlacemarkMapper.kt │ │ └── MobilePlatformGeocoder.ios.kt └── build.gradle.kts ├── compass-core ├── src │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ ├── exception │ │ ├── NotFoundException.kt │ │ └── NotSupportedException.kt │ │ ├── InternalCompassApi.kt │ │ ├── Coordinates.kt │ │ └── Priority.kt └── build.gradle.kts ├── compass-geolocation-mobile ├── src │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── dev │ │ │ └── jordond │ │ │ └── compass │ │ │ └── geolocation │ │ │ └── mobile │ │ │ └── internal │ │ │ └── Mapper.kt │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── geolocation │ │ └── mobile │ │ └── MobileLocator.kt └── build.gradle.kts ├── .gitignore ├── compass-geocoder ├── src │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── geocoder │ │ ├── exception │ │ └── GeocodeException.kt │ │ ├── Geocoder.kt │ │ ├── PlatformGeocoder.kt │ │ └── ForwardGeocoder.kt └── build.gradle.kts ├── compass-geolocation ├── src │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── geolocation │ │ ├── exception │ │ └── GeolocationException.kt │ │ ├── LocationRequest.kt │ │ ├── TrackingUpdate.kt │ │ └── Extensions.kt └── build.gradle.kts ├── compass-tools-web ├── src │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── tools │ │ └── web │ │ ├── parameter │ │ ├── QueryParamValue.kt │ │ ├── QueryParametersBuilder.kt │ │ ├── QueryParamListValue.kt │ │ └── QueryParameters.kt │ │ ├── exception │ │ └── WebException.kt │ │ └── Request.kt └── build.gradle.kts ├── compass-geocoder-web-mapbox ├── src │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── geocoder │ │ └── web │ │ ├── mapbox │ │ └── internal │ │ │ ├── FeatureResponse.kt │ │ │ ├── CoordinatesResponse.kt │ │ │ ├── PropertiesResponse.kt │ │ │ ├── GeocodeResponse.kt │ │ │ └── ContextResponse.kt │ │ ├── parameter │ │ ├── MapboxWorldView.kt │ │ ├── MapboxTypes.kt │ │ └── MapboxBoundingBox.kt │ │ ├── MapboxForwardEndpoint.kt │ │ └── MapboxReverseEndpoint.kt └── build.gradle.kts ├── .run ├── demo.ios.run.xml ├── demo.browser.run.xml └── demo.desktop.run.xml ├── compass-permissions-mobile ├── src │ ├── iosMain │ │ └── kotlin │ │ │ └── dev │ │ │ └── jordond │ │ │ └── compass │ │ │ └── permissions │ │ │ ├── mobile │ │ │ ├── Extensions.ios.kt │ │ │ └── internal │ │ │ │ ├── PermissionStateMapper.kt │ │ │ │ └── LocationPermissionManagerDelegate.kt │ │ │ └── PermissionController.ios.kt │ ├── androidMain │ │ ├── kotlin │ │ │ └── dev │ │ │ │ └── jordond │ │ │ │ └── compass │ │ │ │ └── permissions │ │ │ │ └── mobile │ │ │ │ ├── internal │ │ │ │ └── activity │ │ │ │ │ ├── ActivityProviderInitializer.kt │ │ │ │ │ ├── ActivityLifecycleObserver.kt │ │ │ │ │ └── ActivityProvider.kt │ │ │ │ └── Extensions.android.kt │ │ └── AndroidManifest.xml │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── permissions │ │ ├── mobile │ │ └── Extensions.kt │ │ └── MobilePermissionController.kt ├── api │ └── compass-permissions-mobile.api └── build.gradle.kts ├── compass-permissions ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── dev │ └── jordond │ └── compass │ └── permissions │ ├── exception │ └── PermissionException.kt │ ├── PermissionState.kt │ └── LocationPermissionController.kt ├── compass-autocomplete ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── dev │ └── jordond │ └── compass │ └── autocomplete │ ├── AutocompleteOptions.kt │ ├── AutocompleteService.kt │ ├── internal │ └── DefaultAutocomplete.kt │ └── Autocomplete.kt ├── compass-geocoder-web-googlemaps ├── src │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── geocoder │ │ └── web │ │ ├── google │ │ └── internal │ │ │ ├── GeometryResponse.kt │ │ │ ├── StatusResponse.kt │ │ │ └── ResultResponse.kt │ │ ├── GoogleMapsReverseEndpoint.kt │ │ ├── GoogleMapsForwardEndpoint.kt │ │ └── parameter │ │ └── GoogleMapsLocationType.kt └── build.gradle.kts ├── compass-autocomplete-mobile ├── src │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── autocomplete │ │ ├── mobile │ │ ├── internal │ │ │ └── DefaultMobileAutocompleteService.kt │ │ └── MobileAutocompleteService.kt │ │ └── MobileAutocomplete.kt ├── build.gradle.kts └── api │ └── compass-autocomplete-mobile.api ├── compass-autocomplete-web ├── build.gradle.kts ├── src │ └── commonMain │ │ └── kotlin │ │ └── dev │ │ └── jordond │ │ └── compass │ │ └── autocomplete │ │ ├── web │ │ └── HttpAutocompleteService.kt │ │ └── HttpAutocomplete.kt └── api │ ├── android │ └── compass-autocomplete-web.api │ └── jvm │ └── compass-autocomplete-web.api ├── renovate.json ├── compass-geocoder-web-opencage ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── dev │ └── jordond │ └── compass │ └── geocoder │ └── web │ ├── opencage │ └── internal │ │ ├── GeocodeResponse.kt │ │ └── ResultResponse.kt │ ├── OpenCageForwardEndpoint.kt │ └── OpenCageReverseEndpoint.kt ├── compass-geocoder-web-template ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── dev │ └── jordond │ └── compass │ └── geocoder │ └── web │ ├── template │ └── internal │ │ └── GeocodeResponse.kt │ ├── parameter │ └── TemplateParameters.kt │ ├── TemplateForwardEndpoint.kt │ └── TemplateReverseEndpoint.kt ├── compass-autocomplete-geocoder-mapbox └── build.gradle.kts ├── compass-autocomplete-geocoder-googlemaps └── build.gradle.kts ├── LICENSE ├── compass-geocoder-web ├── build.gradle.kts └── src │ └── commonMain │ └── kotlin │ └── dev │ └── jordond │ └── compass │ └── geocoder │ └── web │ ├── ReverseHttpApiPlatformGeocoder.kt │ └── ForwardHttpApiPlatformGeocoder.kt ├── gradle.properties └── settings.gradle.kts /compass-tools-android/api/compass-tools-android.api: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /art/logo-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/art/logo-full.png -------------------------------------------------------------------------------- /demo/iosApp/Configuration/Config.xcconfig: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | BUNDLE_ID=dev.jordond.compass.demo 3 | APP_NAME=demo -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | demo 3 | -------------------------------------------------------------------------------- /demo/iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /demo/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demo/iosApp/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordond/compass/HEAD/demo/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /demo/composeApp/src/iosMain/kotlin/MainViewController.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.window.ComposeUIViewController 2 | 3 | @Suppress("unused") 4 | fun MainViewController() = ComposeUIViewController { App() } -------------------------------------------------------------------------------- /compass-geolocation-browser/src/jsMain/kotlin/dev/jordond/compass/geolocation/browser/internal/Platform.js.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.internal 2 | 3 | public actual external class Object 4 | -------------------------------------------------------------------------------- /.github/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=false 2 | org.gradle.parallel=true 3 | org.gradle.jvmargs=-Xmx4608m -XX:MaxMetaspaceSize=1536m -XX:+HeapDumpOnOutOfMemoryError 4 | kotlin.compiler.execution.strategy=in-process -------------------------------------------------------------------------------- /demo/composeApp/src/commonMain/kotlin/geolocation/BrowserGeolocationScreen.kt: -------------------------------------------------------------------------------- 1 | package geolocation 2 | 3 | import cafe.adriel.voyager.core.screen.Screen 4 | 5 | expect class BrowserGeolocationScreen constructor() : Screen -------------------------------------------------------------------------------- /compass-geolocation-browser/src/wasmJsMain/kotlin/dev/jordond/compass/geolocation/browser/internal/Platform.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.internal 2 | 3 | public actual external class Object : JsAny 4 | -------------------------------------------------------------------------------- /demo/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /docs/roadmap.md: -------------------------------------------------------------------------------- 1 | # 🚗 Roadmap 2 | 3 | These items are in no particular order. 4 | 5 | * Add support for different languages 6 | * Currently only English is supported 7 | * Autocomplete support from third party APIs like Google's Places API 8 | -------------------------------------------------------------------------------- /docs/autocomplete/via-geocoding.md: -------------------------------------------------------------------------------- 1 | # 🌎 Via Geocoding 2 | 3 | Currently Autocomplete is powered by the existing Geocoder functionality of Compass. In the future, 4 | we plan to integrate with a dedicated autocomplete API service such as Google Places API. -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /demo/composeApp/src/desktopMain/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.window.Window 2 | import androidx.compose.ui.window.application 3 | 4 | fun main() = application { 5 | Window(onCloseRequest = ::exitApplication, title = "Compass Demo") { 6 | App() 7 | } 8 | } -------------------------------------------------------------------------------- /compass-core/src/commonMain/kotlin/dev/jordond/compass/exception/NotFoundException.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.exception 2 | 3 | /** 4 | * Thrown when a geocoder or geolocation operation returns null. 5 | */ 6 | public class NotFoundException : Throwable("Unable to find requested result.") -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/browser/internal/Platform.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.internal 2 | 3 | /** 4 | * Define a platform interface for creating objects in JS or WASM. 5 | */ 6 | public expect class Object 7 | -------------------------------------------------------------------------------- /compass-core/src/commonMain/kotlin/dev/jordond/compass/exception/NotSupportedException.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.exception 2 | 3 | /** 4 | * Indicates that operation is not supported on this platform. 5 | */ 6 | public class NotSupportedException : Throwable("Operation is not supported on this device") -------------------------------------------------------------------------------- /demo/composeApp/src/wasmJsMain/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.ui.ExperimentalComposeUiApi 2 | import androidx.compose.ui.window.CanvasBasedWindow 3 | 4 | @OptIn(ExperimentalComposeUiApi::class) 5 | fun main() { 6 | CanvasBasedWindow(canvasElementId = "ComposeTarget") { App(isBrowser = true) } 7 | } -------------------------------------------------------------------------------- /compass-geolocation-browser/src/jsMain/kotlin/dev/jordond/compass/geolocation/browser/api/Navigator.js.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api 2 | 3 | public actual external val navigator: Navigator? 4 | 5 | public actual external class Navigator { 6 | public actual val geolocation: Geolocation? 7 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /compass-geolocation-browser/src/wasmJsMain/kotlin/dev/jordond/compass/geolocation/browser/api/Navigator.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api 2 | 3 | public actual external val navigator: Navigator? 4 | 5 | public actual external class Navigator { 6 | public actual val geolocation: Geolocation? 7 | } -------------------------------------------------------------------------------- /demo/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "app-icon-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /compass-geolocation-mobile/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/geocoding/request-a-geocoder-api.md: -------------------------------------------------------------------------------- 1 | # ❓ Request a Geocoder API 2 | 3 | If there is a Web based Geocoder API that you would like to be added to Compass.\ 4 | \ 5 | You can either [create an issue](https://github.com/jordond/compass/issues/new), or implement it yourself and open up a PR. If you plan on going that way, make sure you read [contributing.md](../misc/contributing.md "mention")first. 6 | -------------------------------------------------------------------------------- /demo/composeApp/src/wasmJsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Compass Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.iml 3 | .gradle 4 | .idea 5 | .DS_Store 6 | build 7 | */build 8 | captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | xcuserdata/ 13 | Pods/ 14 | *.jks 15 | *yarn.lock 16 | demo/iosApp/Podfile.lock 17 | demo/iosApp/Pods/* 18 | demo/iosApp/iosApp.xcworkspace/* 19 | demo/iosApp/iosApp.xcodeproj/* 20 | !demo/iosApp/iosApp.xcodeproj/project.pbxproj 21 | publish.sh 22 | .kotlin -------------------------------------------------------------------------------- /demo/composeApp/src/browserMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Compass Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /compass-geocoder/src/commonMain/kotlin/dev/jordond/compass/geocoder/exception/GeocodeException.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.exception 2 | 3 | /** 4 | * Indicate something went wrong while using the geocoding service. 5 | * 6 | * @param message The error message. 7 | */ 8 | public class GeocodeException(message: String?) : Throwable( 9 | "Geocoding failed: ${message ?: "Unknown error"}", 10 | ) -------------------------------------------------------------------------------- /compass-core/src/commonMain/kotlin/dev/jordond/compass/InternalCompassApi.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass 2 | 3 | @Target( 4 | allowedTargets = [ 5 | AnnotationTarget.CLASS, 6 | AnnotationTarget.PROPERTY, 7 | AnnotationTarget.FUNCTION, 8 | AnnotationTarget.TYPEALIAS, 9 | ], 10 | ) 11 | @RequiresOptIn(level = RequiresOptIn.Level.ERROR) 12 | public annotation class InternalCompassApi 13 | -------------------------------------------------------------------------------- /compass-geolocation/src/commonMain/kotlin/dev/jordond/compass/geolocation/exception/GeolocationException.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.exception 2 | 3 | /** 4 | * Indicate something went wrong while using the geolocation service. 5 | * 6 | * @param message The error message. 7 | */ 8 | public class GeolocationException(message: String?) : Throwable( 9 | "Geolocation failed: ${message ?: "Unknown error"}", 10 | ) -------------------------------------------------------------------------------- /compass-tools-web/src/commonMain/kotlin/dev/jordond/compass/tools/web/parameter/QueryParamValue.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.tools.web.parameter 2 | 3 | /** 4 | * Represents a query parameter value. 5 | * 6 | * Encoded by [QueryParameters] to make a URL safe string. 7 | */ 8 | public interface QueryParamValue { 9 | 10 | /** 11 | * The value of the query parameter. 12 | */ 13 | public val value: String 14 | } -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/mapbox/internal/FeatureResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.mapbox.internal 2 | 3 | import dev.jordond.compass.InternalCompassApi 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @InternalCompassApi 8 | @Serializable 9 | public data class FeatureResponse( 10 | @SerialName("properties") 11 | val properties: PropertiesResponse? = null, 12 | ) -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" 9 | directory: "gradle/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /compass-tools-web/src/commonMain/kotlin/dev/jordond/compass/tools/web/parameter/QueryParametersBuilder.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.tools.web.parameter 2 | 3 | /** 4 | * Denotes a class that can build a [QueryParameters] instance of type [T]. 5 | * 6 | * @param T the type of [QueryParameters] to build. 7 | */ 8 | public interface QueryParametersBuilder { 9 | 10 | /** 11 | * @return a [QueryParameters] instance of type [T]. 12 | */ 13 | public fun build(): T 14 | } -------------------------------------------------------------------------------- /compass-geolocation-browser/src/wasmJsMain/kotlin/dev/jordond/compass/geolocation/browser/api/model/CreateGeolocationOptions.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api.model 2 | 3 | import dev.jordond.compass.geolocation.browser.internal.Object 4 | 5 | public actual fun createGeolocationOptions( 6 | enableHighAccuracy: Boolean, 7 | timeout: Double, 8 | maximumAge: Double, 9 | ): Object = 10 | js("({enableHighAccuracy: enableHighAccuracy, timeout: timeout, maximumAge: maximumAge})") -------------------------------------------------------------------------------- /compass-geolocation-browser/src/jsMain/kotlin/dev/jordond/compass/geolocation/browser/api/model/CreateGeolocationOptions.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api.model 2 | 3 | import dev.jordond.compass.geolocation.browser.internal.Object 4 | 5 | public actual fun createGeolocationOptions( 6 | enableHighAccuracy: Boolean, 7 | timeout: Double, 8 | maximumAge: Double, 9 | ): Object = js( 10 | "({enableHighAccuracy: enableHighAccuracy, timeout: timeout, maximumAge: maximumAge})", 11 | ) as Object -------------------------------------------------------------------------------- /.run/demo.ios.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /compass-tools-web/src/commonMain/kotlin/dev/jordond/compass/tools/web/exception/WebException.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.tools.web.exception 2 | 3 | import io.ktor.http.HttpStatusCode 4 | 5 | /** 6 | * Represents an exception that occurred during a web request. 7 | * 8 | * @property statusCode The status code of the response. 9 | * @property message The message of the exception. 10 | */ 11 | public class WebException( 12 | public val statusCode: HttpStatusCode, 13 | public override val message: String, 14 | ) : Throwable(message) -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/mapbox/internal/CoordinatesResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.mapbox.internal 2 | 3 | import dev.jordond.compass.InternalCompassApi 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @InternalCompassApi 8 | @Serializable 9 | public data class CoordinatesResponse( 10 | @SerialName("latitude") 11 | val latitude: Double, 12 | 13 | @SerialName("longitude") 14 | val longitude: Double, 15 | ) -------------------------------------------------------------------------------- /compass-permissions-mobile/src/iosMain/kotlin/dev/jordond/compass/permissions/mobile/Extensions.ios.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.mobile 2 | 3 | import platform.Foundation.NSURL 4 | import platform.UIKit.UIApplication 5 | import platform.UIKit.UIApplicationOpenSettingsURLString 6 | 7 | internal actual fun openPermissionSettings() { 8 | UIApplication.sharedApplication().openURL( 9 | url = NSURL(string = UIApplicationOpenSettingsURLString), 10 | options = emptyMap(), 11 | completionHandler = null 12 | ) 13 | } -------------------------------------------------------------------------------- /compass-permissions-mobile/src/androidMain/kotlin/dev/jordond/compass/permissions/mobile/internal/activity/ActivityProviderInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.mobile.internal.activity 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | 6 | internal class ActivityProviderInitializer : Initializer { 7 | 8 | override fun create(context: Context): ActivityProvider { 9 | return ActivityProvider.create(context) 10 | } 11 | 12 | override fun dependencies(): List>> = emptyList() 13 | } -------------------------------------------------------------------------------- /compass-permissions-mobile/src/commonMain/kotlin/dev/jordond/compass/permissions/mobile/Extensions.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.mobile 2 | 3 | import dev.jordond.compass.permissions.LocationPermissionController 4 | 5 | /** 6 | * Opens the settings for the app. 7 | * 8 | * This is useful for directing the user to the app settings to enable permissions after they have 9 | * been denied permanently. 10 | */ 11 | public fun LocationPermissionController.Companion.openSettings() { 12 | openPermissionSettings() 13 | } 14 | 15 | internal expect fun openPermissionSettings() -------------------------------------------------------------------------------- /compass-tools-android/src/androidMain/kotlin/dev/jordond/compass/tools/ContextProviderInitializer.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.tools 2 | 3 | import android.content.Context 4 | import androidx.startup.Initializer 5 | import dev.jordond.compass.InternalCompassApi 6 | 7 | @InternalCompassApi 8 | internal class ContextProviderInitializer : Initializer { 9 | 10 | override fun create(context: Context): ContextProvider { 11 | return ContextProvider.create(context) 12 | } 13 | 14 | override fun dependencies(): List>> = emptyList() 15 | } 16 | -------------------------------------------------------------------------------- /compass-permissions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.dokka) 8 | alias(libs.plugins.publish) 9 | alias(libs.plugins.convention.multiplatform) 10 | } 11 | 12 | configureMultiplatform() 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.compassCore) 18 | implementation(libs.kotlinx.coroutines.core) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /demo/iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import ComposeApp 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | func makeUIViewController(context: Context) -> UIViewController { 7 | MainViewControllerKt.MainViewController() 8 | } 9 | 10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 11 | } 12 | 13 | struct ContentView: View { 14 | var body: some View { 15 | ComposeView() 16 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler 17 | } 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /compass-geocoder/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.dokka) 8 | alias(libs.plugins.publish) 9 | alias(libs.plugins.convention.multiplatform) 10 | } 11 | 12 | configureMultiplatform() 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.compassCore) 18 | implementation(libs.kotlinx.coroutines.core) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /compass-autocomplete/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.dokka) 8 | alias(libs.plugins.publish) 9 | alias(libs.plugins.convention.multiplatform) 10 | } 11 | 12 | configureMultiplatform() 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | implementation(projects.compassCore) 18 | implementation(libs.kotlinx.coroutines.core) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /compass-core/src/commonMain/kotlin/dev/jordond/compass/Coordinates.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | /** 6 | * A geographic location represented by latitude and longitude. 7 | * 8 | * Valid latitude values are between -90.0 and 90.0, both inclusive. 9 | * Valid longitude values are between -180.0 and 180.0, both inclusive. 10 | * 11 | * @property latitude The latitude of the location. 12 | * @property longitude The longitude of the location. 13 | */ 14 | @Poko 15 | public class Coordinates( 16 | public val latitude: Double, 17 | public val longitude: Double, 18 | ) -------------------------------------------------------------------------------- /compass-autocomplete/src/commonMain/kotlin/dev/jordond/compass/autocomplete/AutocompleteOptions.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.autocomplete 2 | 3 | import dev.drewhamilton.poko.Poko 4 | 5 | /** 6 | * Options for configuring an [Autocomplete]. 7 | * 8 | * @property minimumQuery The minimum number of characters required in a query before an 9 | * autocomplete request is made. 10 | */ 11 | @Poko 12 | public open class AutocompleteOptions( 13 | public val minimumQuery: Int = DEFAULT_MINIMUM_QUERY, 14 | ) { 15 | 16 | internal companion object { 17 | 18 | private const val DEFAULT_MINIMUM_QUERY = 3 19 | } 20 | } -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/mapbox/internal/PropertiesResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.mapbox.internal 2 | 3 | import dev.jordond.compass.InternalCompassApi 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @InternalCompassApi 8 | @Serializable 9 | public data class PropertiesResponse( 10 | @SerialName("name") 11 | val name: String? = null, 12 | 13 | @SerialName("coordinates") 14 | val coordinates: CoordinatesResponse? = null, 15 | 16 | @SerialName("context") 17 | val context: ContextResponse? = null, 18 | ) -------------------------------------------------------------------------------- /compass-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.dokka) 8 | alias(libs.plugins.publish) 9 | alias(libs.plugins.convention.multiplatform) 10 | } 11 | 12 | configureMultiplatform() 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(libs.kotlinx.coroutines.core) 18 | } 19 | 20 | androidMain.dependencies { 21 | api(libs.kotlinx.coroutines.android) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /compass-geolocation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.dokka) 8 | alias(libs.plugins.publish) 9 | alias(libs.plugins.convention.multiplatform) 10 | } 11 | 12 | configureMultiplatform() 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.compassCore) 18 | api(projects.compassPermissions) 19 | 20 | implementation(libs.kotlinx.coroutines.core) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/kotlin/dev/jordond/compass/demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.demo 2 | 3 | import App 4 | import android.os.Bundle 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.tooling.preview.Preview 9 | 10 | class MainActivity : ComponentActivity() { 11 | override fun onCreate(savedInstanceState: Bundle?) { 12 | super.onCreate(savedInstanceState) 13 | 14 | setContent { 15 | App() 16 | } 17 | } 18 | } 19 | 20 | @Preview 21 | @Composable 22 | fun AppAndroidPreview() { 23 | App() 24 | } -------------------------------------------------------------------------------- /compass-geocoder-web-googlemaps/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/google/internal/GeometryResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.google.internal 2 | 3 | import dev.jordond.compass.InternalCompassApi 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @InternalCompassApi 8 | @Serializable 9 | public data class GeometryResponse( 10 | @Serializable 11 | val location: LocationResponse? = null, 12 | ) 13 | 14 | @InternalCompassApi 15 | @Serializable 16 | public data class LocationResponse( 17 | @SerialName("lat") 18 | val lat: Double, 19 | 20 | @SerialName("lng") 21 | val lng: Double, 22 | ) -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/parameter/MapboxWorldView.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.parameter 2 | 3 | import dev.jordond.compass.tools.web.parameter.QueryParamValue 4 | 5 | /** 6 | * The world view parameter is used to filter the results to a specific country or region. 7 | * 8 | * See [Mapbox Geocoding API](https://docs.mapbox.com/api/search/geocoding-v6/#forward-geocoding-with-search-text-input) 9 | */ 10 | public enum class MapboxWorldView : QueryParamValue { 11 | AR, 12 | CN, 13 | IN, 14 | JP, 15 | MA, 16 | RU, 17 | TR, 18 | US; 19 | 20 | override val value: String = name.lowercase() 21 | } -------------------------------------------------------------------------------- /compass-core/src/commonMain/kotlin/dev/jordond/compass/Priority.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass 2 | 3 | /** 4 | * Represents the priority of a location request. 5 | */ 6 | public enum class Priority { 7 | 8 | /** 9 | * Balanced power and accuracy. The default priority. 10 | */ 11 | Balanced, 12 | 13 | /** 14 | * High accuracy and high power consumption. 15 | * 16 | * **Note:** On Android this requires the LOCATION_FINE permission. 17 | */ 18 | HighAccuracy, 19 | 20 | /** 21 | * Low power and low accuracy. 22 | */ 23 | LowPower, 24 | 25 | /** 26 | * Lowest accuracy and lowest power consumption. 27 | */ 28 | Passive, 29 | } -------------------------------------------------------------------------------- /compass-tools-android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.Platform 2 | import dev.jordond.compass.convention.configureMultiplatform 3 | 4 | plugins { 5 | alias(libs.plugins.android.library) 6 | alias(libs.plugins.multiplatform) 7 | alias(libs.plugins.dokka) 8 | alias(libs.plugins.publish) 9 | alias(libs.plugins.convention.multiplatform) 10 | } 11 | 12 | configureMultiplatform(Platform.Android) 13 | 14 | kotlin { 15 | sourceSets { 16 | androidMain.dependencies { 17 | implementation(projects.compassCore) 18 | implementation(libs.androidx.startup) 19 | implementation(libs.androidx.activity) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /compass-tools-web/src/commonMain/kotlin/dev/jordond/compass/tools/web/parameter/QueryParamListValue.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.tools.web.parameter 2 | 3 | /** 4 | * Denotes a query parameter value that is a list of values. 5 | * 6 | * @param T The type of the list elements. 7 | * @property separator The separator to use when joining the list elements into a single string. 8 | * @property values The list of values. 9 | */ 10 | public interface QueryParamListValue : QueryParamValue { 11 | 12 | public val separator: String 13 | get() = "," 14 | 15 | public val values: List 16 | 17 | override val value: String 18 | get() = values.joinToString(separator) { it.value } 19 | } 20 | -------------------------------------------------------------------------------- /compass-autocomplete-mobile/src/commonMain/kotlin/dev/jordond/compass/autocomplete/mobile/internal/DefaultMobileAutocompleteService.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.autocomplete.mobile.internal 2 | 3 | import dev.jordond.compass.Place 4 | import dev.jordond.compass.autocomplete.AutocompleteService 5 | import dev.jordond.compass.geocoder.mobile.MobilePlatformGeocoder 6 | 7 | internal class DefaultMobileAutocompleteService( 8 | private val platformGeocoder: MobilePlatformGeocoder, 9 | ) : AutocompleteService { 10 | 11 | override fun isAvailable(): Boolean = platformGeocoder.isAvailable() 12 | 13 | override suspend fun search(query: String): List { 14 | return platformGeocoder.placeFromAddress(query) 15 | } 16 | } -------------------------------------------------------------------------------- /docs/autocomplete/geocoding/web-api-service.md: -------------------------------------------------------------------------------- 1 | # 📈 Web API Service 2 | 3 | Compass supports Autocomplete on all platforms using the Geocoding services provided by Mapbox and 4 | Google Maps. 5 | 6 | ## Getting started 7 | 8 | To get started you first need to create an autocomplete instance: 9 | 10 | ```kotlin 11 | val autocomplete = Autocomplete.googleMapsGeocoder(apiKey = "your-api-key") 12 | ``` 13 | 14 | Then you can use the `autocomplete` instance: 15 | 16 | ```kotlin 17 | val results = mutableListOf() 18 | 19 | suspend fun search(query: String) { 20 | val result = autocomplete.getOrNull() ?: return 21 | if (result.isNotEmpty()) { 22 | results.clear() 23 | results.addAll(result) 24 | } 25 | } 26 | ``` -------------------------------------------------------------------------------- /compass-geolocation-browser/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.Platforms 2 | import dev.jordond.compass.convention.configureMultiplatform 3 | 4 | plugins { 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.dokka) 7 | alias(libs.plugins.poko) 8 | alias(libs.plugins.publish) 9 | alias(libs.plugins.convention.multiplatform) 10 | } 11 | 12 | configureMultiplatform(Platforms.Browser) 13 | 14 | kotlin { 15 | sourceSets { 16 | commonMain.dependencies { 17 | api(projects.compassCore) 18 | api(projects.compassPermissions) 19 | implementation(projects.compassGeolocation) 20 | implementation(libs.kotlinx.coroutines.core) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /compass-autocomplete-web/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.kotlinx.serialization) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform() 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | api(projects.compassToolsWeb) 20 | implementation(projects.compassAutocomplete) 21 | 22 | implementation(libs.kotlinx.coroutines.core) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /compass-permissions-mobile/api/compass-permissions-mobile.api: -------------------------------------------------------------------------------- 1 | public final class dev/jordond/compass/permissions/MobilePermissionControllerKt { 2 | public static final fun LocationPermissionController ()Ldev/jordond/compass/permissions/LocationPermissionController; 3 | public static final fun MobileLocationPermissionController ()Ldev/jordond/compass/permissions/LocationPermissionController; 4 | public static final fun mobile (Ldev/jordond/compass/permissions/LocationPermissionController$Companion;)Ldev/jordond/compass/permissions/LocationPermissionController; 5 | } 6 | 7 | public final class dev/jordond/compass/permissions/mobile/ExtensionsKt { 8 | public static final fun openSettings (Ldev/jordond/compass/permissions/LocationPermissionController$Companion;)V 9 | } 10 | 11 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "labels": [ 7 | "dependencies" 8 | ], 9 | "packageRules": [ 10 | { 11 | "groupName": "Kotlin", 12 | "matchPackagePrefixes": [ 13 | "androidx.compose.compiler", 14 | "com.google.devtools.ksp", 15 | "org.jetbrains.kotlin", 16 | "org.jetbrains.kotlinx:binary-compatibility-validator" 17 | ] 18 | } 19 | ], 20 | "ignoreDeps": [ 21 | "org.jetbrains.kotlinx:kotlinx-coroutines-test", 22 | "org.jetbrains.kotlinx:kotlinx-coroutines-android", 23 | "org.jetbrains.kotlinx:kotlinx-coroutines-core", 24 | "androidx.core:core-ktx" 25 | ] 26 | } 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /demo/composeApp/src/commonMain/kotlin/geocoder/PlatformGeocoderScreen.kt: -------------------------------------------------------------------------------- 1 | package geocoder 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import cafe.adriel.voyager.core.screen.Screen 6 | import createGeocoder 7 | 8 | /** 9 | * This screen demonstrates if you support Mobile + other platforms, but don't want to fallback 10 | * to a HTTP based PlatformGeocoder 11 | */ 12 | class PlatformGeocoderScreen : Screen { 13 | 14 | @Composable 15 | override fun Content() { 16 | val geocoder = remember { createGeocoder() } 17 | 18 | GeocoderContent( 19 | title = "Google Maps", 20 | geocoder = geocoder, 21 | apiKey = null, 22 | updateApiKey = null, 23 | ) 24 | } 25 | } -------------------------------------------------------------------------------- /compass-permissions-mobile/src/androidMain/kotlin/dev/jordond/compass/permissions/mobile/Extensions.android.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.mobile 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.provider.Settings 6 | import dev.jordond.compass.tools.ContextProvider 7 | 8 | internal actual fun openPermissionSettings() { 9 | val context = ContextProvider.getInstance().context.applicationContext 10 | val packageName = context.applicationInfo.packageName 11 | val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 12 | .apply { 13 | flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK 14 | data = Uri.fromParts("package", packageName, null) 15 | } 16 | 17 | context.startActivity(intent) 18 | } -------------------------------------------------------------------------------- /compass-autocomplete/src/commonMain/kotlin/dev/jordond/compass/autocomplete/AutocompleteService.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.autocomplete 2 | 3 | /** 4 | * Represents a service that provides autocomplete suggestions. 5 | * 6 | * @param T The type of the autocomplete suggestions. 7 | */ 8 | public interface AutocompleteService { 9 | 10 | /** 11 | * Checks if the service is available. 12 | * 13 | * @return `true` if the service is available, `false` otherwise. 14 | */ 15 | public fun isAvailable(): Boolean 16 | 17 | /** 18 | * Searches for autocomplete suggestions. 19 | * 20 | * @param query The search query. 21 | * @return A list of autocomplete suggestions. 22 | */ 23 | public suspend fun search(query: String): List 24 | 25 | public companion object 26 | } -------------------------------------------------------------------------------- /compass-autocomplete-mobile/src/commonMain/kotlin/dev/jordond/compass/autocomplete/mobile/MobileAutocompleteService.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.autocomplete.mobile 2 | 3 | import dev.jordond.compass.Place 4 | import dev.jordond.compass.autocomplete.AutocompleteService 5 | import dev.jordond.compass.autocomplete.mobile.internal.DefaultMobileAutocompleteService 6 | import dev.jordond.compass.geocoder.mobile.MobilePlatformGeocoder 7 | 8 | @Suppress("FunctionName") 9 | public fun MobileAutocompleteService( 10 | locale: String? = null, 11 | platformGeocoder: MobilePlatformGeocoder = MobilePlatformGeocoder(locale), 12 | ): AutocompleteService = DefaultMobileAutocompleteService( 13 | platformGeocoder = platformGeocoder, 14 | ) 15 | 16 | public fun AutocompleteService.Companion.mobile(): AutocompleteService = 17 | MobileAutocompleteService() -------------------------------------------------------------------------------- /compass-permissions-mobile/src/iosMain/kotlin/dev/jordond/compass/permissions/mobile/internal/PermissionStateMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.mobile.internal 2 | 3 | import dev.jordond.compass.permissions.PermissionState 4 | import platform.CoreLocation.CLAuthorizationStatus 5 | 6 | internal val CLAuthorizationStatus.toPermissionState: PermissionState 7 | get() = when (this) { 8 | platform.CoreLocation.kCLAuthorizationStatusAuthorizedAlways, 9 | platform.CoreLocation.kCLAuthorizationStatusAuthorizedWhenInUse, 10 | -> PermissionState.Granted 11 | platform.CoreLocation.kCLAuthorizationStatusNotDetermined -> PermissionState.NotDetermined 12 | platform.CoreLocation.kCLAuthorizationStatusDenied -> PermissionState.DeniedForever 13 | else -> error("Unknown location authorization status $this") 14 | } 15 | -------------------------------------------------------------------------------- /compass-tools-android/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 16 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /compass-autocomplete-mobile/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.Platforms 2 | import dev.jordond.compass.convention.configureMultiplatform 3 | 4 | plugins { 5 | alias(libs.plugins.android.library) 6 | alias(libs.plugins.multiplatform) 7 | alias(libs.plugins.poko) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform(Platforms.Mobile) 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | implementation(projects.compassAutocomplete) 20 | implementation(projects.compassGeocoder) 21 | implementation(projects.compassGeocoderMobile) 22 | 23 | implementation(libs.kotlinx.coroutines.core) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /demo/composeApp/src/nonBrowser/kotlin/geolocation/BrowserGeolocationScreen.nonBrowser.kt: -------------------------------------------------------------------------------- 1 | package geolocation 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import cafe.adriel.voyager.core.screen.Screen 10 | 11 | actual class BrowserGeolocationScreen actual constructor() : Screen { 12 | 13 | @Composable 14 | override fun Content() { 15 | Column( 16 | horizontalAlignment = Alignment.CenterHorizontally, 17 | verticalArrangement = Arrangement.Center, 18 | modifier = Modifier, 19 | ) { 20 | Text("Not supported on non-browser platforms") 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.kotlinx.serialization) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform() 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | implementation(projects.compassGeocoder) 20 | api(projects.compassGeocoderWeb) 21 | api(projects.compassToolsWeb) 22 | 23 | implementation(libs.kotlinx.coroutines.core) 24 | implementation(libs.kotlinx.serialization.json) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/commonMain/kotlin/dev/jordond/compass/geocoder/mobile/Extensions.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.mobile 2 | 3 | import dev.jordond.compass.Place 4 | import dev.jordond.compass.geocoder.Geocoder 5 | 6 | /** 7 | * Get a list of [Place]s for a given address or `null` if unsuccessful. 8 | * 9 | * In most cases, the list will contain a single [Place]. However, in some cases, it may return 10 | * multiple [Place]s. 11 | * 12 | * @receiver The [Geocoder] to use for geocoding. 13 | * @param address The address to geocode. 14 | * @return A list of [Place]s or `null` if the geocoding was unsuccessful, either due to a geocoder 15 | * error or not being able to find the location. 16 | */ 17 | public suspend fun Geocoder.placesOrNull(address: String): List? { 18 | return (platformGeocoder as? MobilePlatformGeocoder)?.placeFromAddress(address) 19 | } -------------------------------------------------------------------------------- /demo/composeApp/src/mobileMain/kotlin/Platform.mobile.kt: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.geocoder.PlatformGeocoder 2 | import dev.jordond.compass.geocoder.mobile.MobilePlatformGeocoder 3 | import dev.jordond.compass.geolocation.Locator 4 | import dev.jordond.compass.geolocation.mobile.mobile 5 | import dev.jordond.compass.permissions.LocationPermissionController 6 | import dev.jordond.compass.permissions.mobile.openSettings 7 | 8 | actual fun getPlatformGeocoder(): PlatformGeocoder = getPlatformGeocoderOrFallback("") 9 | 10 | actual fun getPlatformGeocoderOrFallback(apiKey: String): PlatformGeocoder { 11 | return MobilePlatformGeocoder() 12 | } 13 | 14 | actual fun getPlatformLocator(): Locator { 15 | return Locator.mobile() 16 | } 17 | 18 | actual val canShowAppSettings: Boolean = true 19 | 20 | actual fun showAppSettings() { 21 | LocationPermissionController.openSettings() 22 | } -------------------------------------------------------------------------------- /compass-geocoder-web-googlemaps/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.kotlinx.serialization) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform() 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | implementation(projects.compassGeocoder) 20 | api(projects.compassGeocoderWeb) 21 | api(projects.compassToolsWeb) 22 | 23 | implementation(libs.kotlinx.coroutines.core) 24 | implementation(libs.kotlinx.serialization.json) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /compass-geocoder-web-opencage/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.kotlinx.serialization) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform() 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | implementation(projects.compassGeocoder) 20 | api(projects.compassGeocoderWeb) 21 | api(projects.compassToolsWeb) 22 | 23 | implementation(libs.kotlinx.coroutines.core) 24 | implementation(libs.kotlinx.serialization.json) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /compass-geocoder-web-template/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.kotlinx.serialization) 8 | // alias(libs.plugins.dokka) 9 | // alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform() 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | implementation(projects.compassGeocoder) 20 | api(projects.compassGeocoderWeb) 21 | api(projects.compassToolsWeb) 22 | 23 | implementation(libs.kotlinx.coroutines.core) 24 | implementation(libs.kotlinx.serialization.json) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/browser/internal/Mapper.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.internal 2 | 3 | import dev.jordond.compass.Altitude 4 | import dev.jordond.compass.Azimuth 5 | import dev.jordond.compass.Coordinates 6 | import dev.jordond.compass.Location 7 | import dev.jordond.compass.Speed 8 | import dev.jordond.compass.geolocation.browser.api.model.GeolocationPosition 9 | 10 | internal fun GeolocationPosition.toModel(): Location = Location( 11 | coordinates = Coordinates(coords.latitude, coords.longitude), 12 | accuracy = coords.accuracy, 13 | altitude = coords.altitude?.let { Altitude(it, coords.altitudeAccuracy?.toFloat()) }, 14 | azimuth = coords.heading?.let { Azimuth(it.toFloat(), null) }, 15 | speed = coords.speed?.let { Speed(it.toFloat(), null) }, 16 | timestampMillis = timestamp.toLong(), 17 | ) -------------------------------------------------------------------------------- /compass-autocomplete-geocoder-mapbox/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.kotlinx.serialization) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform() 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | api(projects.compassToolsWeb) 20 | implementation(projects.compassAutocomplete) 21 | api(projects.compassGeocoderWebMapbox) 22 | 23 | implementation(libs.kotlinx.coroutines.core) 24 | implementation(libs.kotlinx.serialization.json) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /compass-geolocation-browser/src/jsMain/kotlin/dev/jordond/compass/geolocation/browser/api/Geolocation.js.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api 2 | 3 | import dev.jordond.compass.geolocation.browser.api.model.GeolocationPosition 4 | import dev.jordond.compass.geolocation.browser.api.model.GeolocationPositionError 5 | import dev.jordond.compass.geolocation.browser.internal.Object 6 | 7 | public actual external class Geolocation { 8 | 9 | public actual fun getCurrentPosition( 10 | success: (GeolocationPosition?) -> Unit, 11 | error: (GeolocationPositionError) -> Unit, 12 | options: Object, 13 | ) 14 | 15 | public actual fun watchPosition( 16 | success: (GeolocationPosition?) -> Unit, 17 | error: (GeolocationPositionError) -> Unit, 18 | options: Object, 19 | ): Int 20 | 21 | public actual fun clearWatch(watchId: Int) 22 | } -------------------------------------------------------------------------------- /compass-geolocation-browser/src/wasmJsMain/kotlin/dev/jordond/compass/geolocation/browser/api/Geolocation.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api 2 | 3 | import dev.jordond.compass.geolocation.browser.api.model.GeolocationPosition 4 | import dev.jordond.compass.geolocation.browser.api.model.GeolocationPositionError 5 | import dev.jordond.compass.geolocation.browser.internal.Object 6 | 7 | public actual external class Geolocation { 8 | 9 | public actual fun getCurrentPosition( 10 | success: (GeolocationPosition?) -> Unit, 11 | error: (GeolocationPositionError) -> Unit, 12 | options: Object, 13 | ) 14 | 15 | public actual fun watchPosition( 16 | success: (GeolocationPosition?) -> Unit, 17 | error: (GeolocationPositionError) -> Unit, 18 | options: Object, 19 | ): Int 20 | 21 | public actual fun clearWatch(watchId: Int) 22 | } -------------------------------------------------------------------------------- /compass-autocomplete-geocoder-googlemaps/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.kotlinx.serialization) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform() 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | api(projects.compassToolsWeb) 20 | implementation(projects.compassAutocomplete) 21 | api(projects.compassGeocoderWebGooglemaps) 22 | 23 | implementation(libs.kotlinx.coroutines.core) 24 | implementation(libs.kotlinx.serialization.json) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/browser/api/model/GeolocationPosition.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api.model 2 | 3 | /** 4 | * The GeolocationPosition interface represents the position of the concerned device at a given 5 | * time. The position, represented by a GeolocationCoordinates object, comprehends the 2D position 6 | * of the device, on a spheroid representing the Earth, but also its altitude and its speed. 7 | * 8 | * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPosition) 9 | * 10 | * @property coords The [GeolocationCoordinates] object containing the device's current location. 11 | * @property timestamp The time at which the location was retrieved. 12 | */ 13 | public external class GeolocationPosition { 14 | 15 | public val coords: GeolocationCoordinates 16 | 17 | public val timestamp: Double 18 | } -------------------------------------------------------------------------------- /compass-permissions/src/commonMain/kotlin/dev/jordond/compass/permissions/exception/PermissionException.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.exception 2 | 3 | /** 4 | * Defines an exception that is related to Geolocation permissions. 5 | */ 6 | public sealed class PermissionException(message: String) : Throwable(message) 7 | 8 | /** 9 | * The user denied the permission request. 10 | * 11 | * @param permission The permission that was denied. 12 | */ 13 | public class PermissionDeniedException( 14 | permission: String = "Location", 15 | ) : PermissionException("Permission $permission was denied") 16 | 17 | /** 18 | * The user denied the permission request and selected "Don't ask again". 19 | * 20 | * @param permission The permission that was denied. 21 | */ 22 | public class PermissionDeniedForeverException( 23 | permission: String = "Location", 24 | ) : PermissionException("Permission $permission was denied permanently") 25 | -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/browser/api/Navigator.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api 2 | 3 | import dev.jordond.compass.InternalCompassApi 4 | 5 | /** 6 | * Browser provided Navigator object for accessing Geolocation API. 7 | * 8 | * See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) 9 | */ 10 | @InternalCompassApi 11 | public expect val navigator: Navigator? 12 | 13 | /** 14 | * Browser provided Navigator object for accessing Geolocation API. 15 | * 16 | * See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) 17 | */ 18 | @InternalCompassApi 19 | public expect class Navigator { 20 | 21 | /** 22 | * GeoLocation API for accessing the browser's location data. 23 | * 24 | * See [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) 25 | */ 26 | public val geolocation: Geolocation? 27 | } -------------------------------------------------------------------------------- /docs/geocoding/web-api-service/mapbox.md: -------------------------------------------------------------------------------- 1 | # Mapbox 2 | 3 | To use [Mapbox API ](https://docs.mapbox.com/api/search/geocoding/)as your geocoding backend you will need to do the following: 4 | 5 | ### Obtain an API key 6 | 7 | Create an account for [Mapbox](https://account.mapbox.com/), then copy your access token. 8 | 9 | ### Create the Geocoder 10 | 11 | Simple pass your API key to the `Geocoder` function: 12 | 13 | ```kotlin 14 | val geocoder = MapboxGeocoder(apiKey = "your-api-key") 15 | 16 | // Or this helper function 17 | val geocoder = Geocoder(apiKey = "your-api-key") 18 | ``` 19 | 20 | #### Customizing Google Maps request 21 | 22 | The Mapbox geocoding API has some [optional parameters.](https://docs.mapbox.com/api/search/geocoding/#forward-geocoding-with-search-text-input) You can modify these by passing them into the `Geocoder` function: 23 | 24 | ```kotlin 25 | val geocoder = MapboxGeocoder(apiKey = "your-api-key") { 26 | limit = 3 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/geocoding/web-api-service/opencage.md: -------------------------------------------------------------------------------- 1 | # OpenCage 2 | 3 | To use [OpenCage API](https://opencagedata.com/api) as your geocoding backend you will need to do 4 | the following: 5 | 6 | ### Obtain an API key 7 | 8 | Create an account for [OpenCage](https://opencagedata.com/dashboard), then copy your access token. 9 | 10 | ### Create the Geocoder 11 | 12 | Simple pass your API key to the `Geocoder` function: 13 | 14 | ```kotlin 15 | val geocoder = OpenCageGeocoder(apiKey = "your-api-key") 16 | 17 | // Or this helper function 18 | val geocoder = Geocoder(apiKey = "your-api-key") 19 | ``` 20 | 21 | #### Customizing Google Maps request 22 | 23 | The OpenCage geocoding API has 24 | some [optional parameters.](https://opencagedata.com/api#optional-params) You can modify these by 25 | passing them into the `Geocoder` function: 26 | 27 | ```kotlin 28 | val geocoder = OpenCageGeocoder(apiKey = "your-api-key") { 29 | limit = 3 30 | noRecord = true 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /compass-geocoder-web-googlemaps/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/google/internal/StatusResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.google.internal 2 | 3 | import dev.jordond.compass.InternalCompassApi 4 | import kotlinx.serialization.SerialName 5 | 6 | /** 7 | * The possible status responses from the Google Maps Geocoding API. 8 | * 9 | * @see [Status and Error Codes](https://developers.google.com/maps/documentation/geocoding/requests-reverse-geocoding#reverse-response) 10 | */ 11 | @Suppress("unused") 12 | @InternalCompassApi 13 | public enum class StatusResponse { 14 | @SerialName("OK") 15 | Ok, 16 | 17 | @SerialName("ZERO_RESULTS") 18 | ZeroResults, 19 | 20 | @SerialName("OVER_QUERY_LIMIT") 21 | OverQueryLimit, 22 | 23 | @SerialName("REQUEST_DENIED") 24 | RequestDenied, 25 | 26 | @SerialName("INVALID_REQUEST") 27 | InvalidRequest, 28 | 29 | @SerialName("UNKNOWN_ERROR") 30 | UnknownError, 31 | } -------------------------------------------------------------------------------- /compass-geocoder-mobile/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.Platforms 2 | import dev.jordond.compass.convention.configureMultiplatform 3 | 4 | plugins { 5 | alias(libs.plugins.android.library) 6 | alias(libs.plugins.multiplatform) 7 | alias(libs.plugins.poko) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform(Platforms.Mobile) 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | implementation(projects.compassGeocoder) 20 | implementation(libs.kotlinx.coroutines.core) 21 | } 22 | 23 | androidMain.dependencies { 24 | implementation(projects.compassToolsAndroid) 25 | implementation(libs.kotlinx.coroutines.android) 26 | implementation(libs.androidx.activity) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /compass-geocoder-web-template/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/template/internal/GeocodeResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.template.internal 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | internal data class GeocodeResponse( 10 | @SerialName("results") 11 | val results: List = emptyList(), 12 | ) 13 | 14 | @Serializable 15 | internal data class ResultResponse( 16 | @SerialName("foo") 17 | val foo: String? = null, 18 | ) 19 | 20 | internal fun GeocodeResponse.toCoordinates(): List { 21 | return results.mapNotNull { 22 | // TODO: Implement this 23 | null 24 | } 25 | } 26 | 27 | internal fun GeocodeResponse.toPlaces(): List { 28 | return results.mapNotNull { 29 | // TODO: Implement this 30 | null 31 | } 32 | } -------------------------------------------------------------------------------- /compass-permissions-mobile/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 17 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /compass-tools-android/src/androidMain/kotlin/dev/jordond/compass/tools/ContextProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.tools 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import dev.jordond.compass.InternalCompassApi 6 | 7 | /** 8 | * Class for providing the application context. 9 | */ 10 | @InternalCompassApi 11 | public class ContextProvider(public val context: Context) { 12 | 13 | @InternalCompassApi 14 | public companion object { 15 | 16 | @SuppressLint("StaticFieldLeak") 17 | private var instance: ContextProvider? = null 18 | 19 | public fun create(context: Context): ContextProvider { 20 | if (instance == null) { 21 | instance = ContextProvider(context) 22 | } 23 | 24 | return instance!! 25 | } 26 | 27 | public fun getInstance(): ContextProvider = instance 28 | ?: throw IllegalStateException("ContextProvider has not been initialized") 29 | } 30 | } -------------------------------------------------------------------------------- /.run/demo.browser.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /compass-geolocation/src/commonMain/kotlin/dev/jordond/compass/geolocation/LocationRequest.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation 2 | 3 | import dev.drewhamilton.poko.Poko 4 | import dev.jordond.compass.Priority 5 | 6 | /** 7 | * Represents a request for location updates. 8 | * 9 | * @param priority The priority of the request. 10 | * @param interval The interval at which to receive location updates, in milliseconds. 11 | * @param maximumAge The maximum age of a cached location that can be used before a new location is 12 | * requested, in milliseconds. 13 | * @param ignoreAvailableCheck Whether to ignore the availability of location services when 14 | * requesting a location. If `true`, the request will be made regardless of whether location 15 | * services are available, potentially resulting in an error if they are not. 16 | */ 17 | @Poko 18 | public class LocationRequest( 19 | public val priority: Priority = Priority.Balanced, 20 | public val interval: Long = 5000L, 21 | public val maximumAge: Long = 0L, 22 | public val ignoreAvailableCheck: Boolean = false, 23 | ) -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/androidMain/kotlin/dev/jordond/compass/geocoder/mobile/internal/AddressMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.mobile.internal 2 | 3 | import android.location.Address 4 | import dev.jordond.compass.Coordinates 5 | import dev.jordond.compass.Place 6 | 7 | internal fun Address.toPlace(): Place = Place( 8 | coordinates = Coordinates(latitude, longitude), 9 | name = featureName, 10 | street = getAddressLine(0), 11 | isoCountryCode = countryCode, 12 | country = countryName, 13 | postalCode = postalCode, 14 | administrativeArea = adminArea, 15 | subAdministrativeArea = subAdminArea, 16 | locality = locality, 17 | subLocality = subLocality, 18 | thoroughfare = thoroughfare, 19 | subThoroughfare = subThoroughfare, 20 | ) 21 | 22 | internal fun List
.toPlaces(): List = map { it.toPlace() } 23 | 24 | internal fun Address.toCoordinates(): Coordinates = 25 | Coordinates(latitude = latitude, longitude = longitude) 26 | 27 | internal fun List
.toCoordinates(): List = map { it.toCoordinates() } -------------------------------------------------------------------------------- /demo/composeApp/src/commonMain/kotlin/Platform.kt: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.geocoder.Geocoder 2 | import dev.jordond.compass.geocoder.PlatformGeocoder 3 | import dev.jordond.compass.geolocation.Geolocator 4 | import dev.jordond.compass.geolocation.Locator 5 | 6 | /** 7 | * If an API key is provided, create a platform geocoder which falls back to an HTTP Geocoder 8 | * with the API key.. 9 | * 10 | * If it is not provided it will create a platform geocoder. 11 | */ 12 | fun createGeocoder(apiKey: String? = null): Geocoder { 13 | val platformGeocoder = 14 | if (apiKey == null) getPlatformGeocoder() 15 | else getPlatformGeocoderOrFallback(apiKey) 16 | return Geocoder(platformGeocoder) 17 | } 18 | 19 | expect fun getPlatformGeocoder(): PlatformGeocoder 20 | 21 | expect fun getPlatformGeocoderOrFallback(apiKey: String): PlatformGeocoder 22 | 23 | fun createGeolocator(): Geolocator { 24 | return Geolocator(getPlatformLocator()) 25 | } 26 | 27 | expect fun getPlatformLocator(): Locator 28 | 29 | expect val canShowAppSettings: Boolean 30 | 31 | expect fun showAppSettings() -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/parameter/MapboxTypes.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.parameter 2 | 3 | import dev.jordond.compass.tools.web.parameter.QueryParamListValue 4 | import dev.jordond.compass.tools.web.parameter.QueryParamValue 5 | 6 | /** 7 | * The types of features that can be returned by the Mapbox Geocoding API. 8 | * 9 | * See [Mapbox Geocoding API](https://docs.mapbox.com/api/search/geocoding-v6/#forward-geocoding-with-search-text-input) 10 | */ 11 | public enum class MapboxTypes : QueryParamValue { 12 | Country, 13 | Region, 14 | Postcode, 15 | District, 16 | Place, 17 | Locality, 18 | Neighborhood, 19 | Street, 20 | Address; 21 | 22 | override val value: String = name.lowercase() 23 | } 24 | 25 | /** 26 | * A list of [MapboxTypes] to be used as a query parameter. 27 | */ 28 | public class MapboxTypesList( 29 | override val values: List, 30 | ) : QueryParamListValue { 31 | 32 | public constructor(vararg values: MapboxTypes) : this(values.toList()) 33 | } -------------------------------------------------------------------------------- /demo/composeApp/src/wasmJsMain/kotlin/geolocation/BrowserGeolocationScreen.browser.kt: -------------------------------------------------------------------------------- 1 | package geolocation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import cafe.adriel.voyager.core.model.rememberScreenModel 6 | import cafe.adriel.voyager.core.screen.Screen 7 | import dev.jordond.compass.geolocation.Geolocator 8 | import dev.jordond.compass.geolocation.Locator 9 | import dev.jordond.compass.geolocation.browser.browser 10 | import dev.stateholder.extensions.collectAsState 11 | 12 | actual class BrowserGeolocationScreen actual constructor() : Screen { 13 | 14 | @Composable 15 | override fun Content() { 16 | val model = rememberScreenModel { 17 | GeolocationModel(Geolocator(Locator.browser())) 18 | } 19 | val state by model.collectAsState() 20 | 21 | GeolocationContent( 22 | state = state, 23 | currentLocation = model::currentLocation, 24 | startTracking = model::startTracking, 25 | stopTracking = model::stopTracking, 26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/browser/BrowserLocator.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser 2 | 3 | import dev.jordond.compass.geolocation.Locator 4 | import dev.jordond.compass.geolocation.browser.internal.DefaultBrowserLocator 5 | 6 | /** 7 | * A locator that uses a web browser to determine the current location. 8 | */ 9 | public interface BrowserLocator : Locator 10 | 11 | /** 12 | * Creates a new [BrowserLocator] that uses a web browser to determine the current location. 13 | * 14 | * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) 15 | * 16 | * @return A new [BrowserLocator]. 17 | */ 18 | public fun createBrowserLocator(): BrowserLocator = DefaultBrowserLocator() 19 | 20 | /** 21 | * Creates a new [BrowserLocator] that uses a web browser to determine the current location. 22 | * 23 | * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) 24 | * 25 | * @return A new [BrowserLocator]. 26 | */ 27 | public fun Locator.Companion.browser(): BrowserLocator = createBrowserLocator() 28 | 29 | -------------------------------------------------------------------------------- /compass-permissions-mobile/src/commonMain/kotlin/dev/jordond/compass/permissions/MobilePermissionController.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions 2 | 3 | /** 4 | * Creates a new [LocationPermissionController] for Android and iOS. 5 | * 6 | * @return A new [LocationPermissionController]. 7 | */ 8 | public fun LocationPermissionController(): LocationPermissionController { 9 | return createPermissionController() 10 | } 11 | 12 | /** 13 | * Creates a new [LocationPermissionController] for Android and iOS. 14 | * 15 | * @return A new [LocationPermissionController]. 16 | */ 17 | @Suppress("FunctionName") 18 | public fun MobileLocationPermissionController(): LocationPermissionController { 19 | return LocationPermissionController() 20 | } 21 | 22 | /** 23 | * Creates a new [LocationPermissionController] for Android and iOS. 24 | * 25 | * @return A new [LocationPermissionController]. 26 | */ 27 | public fun LocationPermissionController.Companion.mobile(): LocationPermissionController { 28 | return LocationPermissionController() 29 | } 30 | 31 | internal expect fun createPermissionController(): LocationPermissionController -------------------------------------------------------------------------------- /demo/composeApp/src/commonMain/kotlin/geocoder/PlatformGeocoderFallbackScreen.kt: -------------------------------------------------------------------------------- 1 | package geocoder 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.derivedStateOf 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | import cafe.adriel.voyager.core.screen.Screen 10 | import createGeocoder 11 | 12 | /** 13 | * This screen demonstrates how to support Mobile with its native geocoder, as well as falling back 14 | * to an http based geocoder on other platforms. 15 | */ 16 | class PlatformGeocoderFallbackScreen : Screen { 17 | 18 | @Composable 19 | override fun Content() { 20 | var apiKey by remember { mutableStateOf("") } 21 | val geocoder by remember(apiKey) { 22 | derivedStateOf { createGeocoder(apiKey) } 23 | } 24 | 25 | GeocoderContent( 26 | title = "Google Maps", 27 | geocoder = geocoder, 28 | apiKey = apiKey, 29 | updateApiKey = { apiKey = it }, 30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jordon de Hoog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /compass-autocomplete-mobile/src/commonMain/kotlin/dev/jordond/compass/autocomplete/MobileAutocomplete.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.autocomplete 2 | 3 | import dev.jordond.compass.Place 4 | import dev.jordond.compass.autocomplete.mobile.mobile 5 | import kotlinx.coroutines.CoroutineDispatcher 6 | import kotlinx.coroutines.Dispatchers 7 | 8 | public fun Autocomplete( 9 | options: AutocompleteOptions = AutocompleteOptions(), 10 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 11 | ): Autocomplete = MobileAutocomplete(options, dispatcher) 12 | 13 | @Suppress("FunctionName") 14 | public fun MobileAutocomplete( 15 | options: AutocompleteOptions = AutocompleteOptions(), 16 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 17 | ): Autocomplete = Autocomplete.mobile(options, dispatcher) 18 | 19 | public fun Autocomplete.Companion.mobile( 20 | options: AutocompleteOptions = AutocompleteOptions(), 21 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 22 | ): Autocomplete = Autocomplete( 23 | service = AutocompleteService.mobile(), 24 | options = options, 25 | dispatcher = dispatcher 26 | ) -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /compass-permissions-mobile/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.Platforms 2 | import dev.jordond.compass.convention.configureMultiplatform 3 | 4 | plugins { 5 | alias(libs.plugins.android.library) 6 | alias(libs.plugins.multiplatform) 7 | alias(libs.plugins.poko) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform(Platforms.Mobile) 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | api(projects.compassPermissions) 20 | implementation(libs.kotlinx.coroutines.core) 21 | } 22 | 23 | androidMain.dependencies { 24 | implementation(projects.compassToolsAndroid) 25 | api(libs.play.services.location) 26 | implementation(libs.androidx.activity) 27 | implementation(libs.androidx.fragment) 28 | implementation(libs.androidx.startup) 29 | 30 | } 31 | 32 | iosMain.dependencies { 33 | implementation(libs.kotlinx.atomicfu) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /.run/demo.desktop.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/misc/demo.md: -------------------------------------------------------------------------------- 1 | # 📲 Demo 2 | 3 | A demo is available in the `/demo` folder. 4 | 5 | ### Before running 6 | 7 | * Check your system with [KDoctor](https://github.com/Kotlin/kdoctor) 8 | * Install the [Kotlin Multiplatform Mobile plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile) 9 | 10 | ### Running 11 | 12 | Clone the repository and open it in Android Studio. In the "Run configuration" drop-down you should see the following: 13 | 14 | * demo.composeApp 15 | * demo.browser 16 | * demo.desktop 17 | * demo.ios 18 | 19 | Select the target you want and click Run. 20 | 21 | ### Manual 22 | 23 | To manually run the demo you can do the following: 24 | 25 | #### Android 26 | 27 | To run the application on android device/emulator: 28 | 29 | * open project in Android Studio and run imported android run configuration 30 | 31 | To build the application bundle: 32 | 33 | * run `./gradlew :demo:composeApp:assembleDebug` 34 | * find `.apk` file in `demo/composeApp/build/outputs/apk/debug/composeApp-debug.apk` 35 | 36 | #### iOS 37 | 38 | To run the application on iPhone device/simulator: 39 | 40 | * Open `iosApp/iosApp.xcworkspace` in Xcode and run standard configuration 41 | -------------------------------------------------------------------------------- /docs/geocoding/web-api-service/google-maps.md: -------------------------------------------------------------------------------- 1 | # Google Maps 2 | 3 | To use[ Google Maps API](https://developers.google.com/maps/documentation/geocoding/overview) as your geocoding backend you will need to do the following: 4 | 5 | ### Obtain an API key 6 | 7 | 1. First you will need to [create a cloud project and enable the API](https://developers.google.com/maps/documentation/geocoding/cloud-setup) 8 | 2. Create the [API key](https://developers.google.com/maps/documentation/geocoding/get-api-key) 9 | 3. Copy your key 10 | 11 | ### Create the Geocoder 12 | 13 | Simple pass your API key to the `Geocoder` function: 14 | 15 | ```kotlin 16 | val geocoder = GoogleMapsGeocoder(apiKey = "your-api-key") 17 | 18 | // Or this helper function 19 | val geocoder = Geocoder(apiKey = "your-api-key") 20 | ``` 21 | 22 | #### Customizing Google Maps request 23 | 24 | The Google Maps geocoding API has some [optional parameters](https://developers.google.com/maps/documentation/geocoding/requests-geocoding). You can modify these by passing them into the `Geocoder` function: 25 | 26 | ```kotlin 27 | val geocoder = GoogleMapsGeocoder(apiKey = "your-api-key") { 28 | locationTypes(GoogleMapsLocationType.GeometricCenter) 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/autocomplete/geocoding/android-ios.md: -------------------------------------------------------------------------------- 1 | # 📱 Android / iOS 2 | 3 | Compass supports Autocomplete natively on Android and iOS by using the built in Geocoding services. 4 | 5 | {% hint style="info" %} 6 | If your project supports both Mobile and other targets, you need to configure your setup to provide 7 | a `Autocomplete` for each platform. Check 8 | out [mixed-platforms.md](../usage/mixed-platforms.md "mention")for more information. 9 | {% endhint %} 10 | 11 | ## Getting started 12 | 13 | To get started with Autocomplete on Android and iOS, you need to create an instance 14 | of `Autocomplete`: 15 | 16 | ```kotlin 17 | val autocomplete = Autocomplete.mobile() 18 | ``` 19 | 20 | Then you can use the `autocomplete` instance: 21 | 22 | ```kotlin 23 | val results = mutableListOf() 24 | 25 | suspend fun search(query: String) { 26 | val result = autocomplete.getOrNull() ?: return 27 | if (result.isNotEmpty()) { 28 | results.clear() 29 | results.addAll(result) 30 | } 31 | } 32 | ``` 33 | 34 | {% hint style="info" %} 35 | The Mobile Autocomplete uses the device's built-in Geocoding services to provide the results. These 36 | results can vary in quality and quantity. 37 | {% endhint %} -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Material Kolor Demo 2 | 3 | 4 | 5 | ## Before running! 6 | 7 | - check your system with [KDoctor](https://github.com/Kotlin/kdoctor) 8 | - install JDK 8 on your machine 9 | - install 10 | the [Kotlin Multiplatform Mobile plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile) 11 | 12 | ## Running 13 | 14 | Open the project in Android Studio and let it sync the project. In the configuration dropdown should 15 | be the following: 16 | 17 | - androidApp 18 | - iosApp 19 | 20 | You can select a configuration and run it, or follow the steps below. 21 | 22 | ### Android 23 | 24 | To run the application on android device/emulator: 25 | 26 | - open project in Android Studio and run imported android run configuration 27 | 28 | To build the application bundle: 29 | 30 | - run `./gradlew :composeApp:assembleDebug` 31 | - find `.apk` file in `composeApp/build/outputs/apk/debug/composeApp-debug.apk` 32 | 33 | ### iOS 34 | 35 | To run the application on iPhone device/simulator: 36 | 37 | - Open `iosApp/iosApp.xcworkspace` in Xcode and run standard configuration 38 | - Or 39 | use [Kotlin Multiplatform Mobile plugin](https://plugins.jetbrains.com/plugin/14936-kotlin-multiplatform-mobile) 40 | for Android Studio 41 | -------------------------------------------------------------------------------- /docs/geocoder/overview.md: -------------------------------------------------------------------------------- 1 | # 🌎 Overview 2 | 3 | Geocoding is the process of turning coordinates into a Place (reverse), or turning an address name into coordinates. 4 | 5 | Compass offers the following features: 6 | 7 | * Reverse: Turn coordinates (latitude, longitude) into a Place object. 8 | * Forward: Turn an address search query into coordinates. 9 | * Android and iOS support, using built-in services. 10 | * Support for web based API services. 11 | * Currently support for [Google Maps ](https://developers.google.com/maps/documentation/geocoding)and [Mapbox](https://docs.mapbox.com/#search) is included. 12 | * Easily create a wrapper for your own API. 13 | 14 | {% hint style="info" %} 15 | Built-in services are used for Android and iOS. That means that there is restrictions to its usage. You can be throttled or blocked from using the geocoding services if you make too many requests in a short period of time. 16 | {% endhint %} 17 | 18 | ### Quick start 19 | 20 | ```kotlin 21 | fun getPlaceFromCoordinates(lat: Long, lng: Long): Place? { 22 | val geocoder = Geocoder() 23 | return geocoder.placeOrNull() 24 | } 25 | ``` 26 | 27 | In this quick example the `Geocoder()` function is a convenience function for creating a `Geocoder` 28 | object. Each of the `geocoder-*` artifacts provide one. 29 | -------------------------------------------------------------------------------- /compass-permissions-mobile/src/androidMain/kotlin/dev/jordond/compass/permissions/mobile/internal/activity/ActivityLifecycleObserver.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.mobile.internal.activity 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.os.Bundle 6 | import android.util.Log 7 | import androidx.activity.ComponentActivity 8 | 9 | internal fun createActivityLifecycleObserver( 10 | block: (ComponentActivity) -> Unit, 11 | ): Application.ActivityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks { 12 | 13 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { 14 | if (activity is ComponentActivity) { 15 | block(activity) 16 | } else { 17 | Log.e("CompassTools", "Activity is not a ComponentActivity. Cannot attach lifecycle observer.") 18 | } 19 | } 20 | 21 | override fun onActivityStarted(activity: Activity) {} 22 | override fun onActivityResumed(activity: Activity) {} 23 | override fun onActivityPaused(activity: Activity) {} 24 | override fun onActivityStopped(activity: Activity) {} 25 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} 26 | override fun onActivityDestroyed(activity: Activity) {} 27 | } -------------------------------------------------------------------------------- /compass-tools-web/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.dokka) 8 | alias(libs.plugins.publish) 9 | alias(libs.plugins.convention.multiplatform) 10 | } 11 | 12 | configureMultiplatform() 13 | kotlin { 14 | sourceSets { 15 | commonMain.dependencies { 16 | implementation(projects.compassCore) 17 | 18 | implementation(libs.kermit) 19 | implementation(libs.kotlinx.coroutines.core) 20 | api(libs.kotlinx.serialization.json) 21 | api(libs.ktor.client.core) 22 | api(libs.ktor.client.contentNegotiation) 23 | api(libs.ktor.client.logging) 24 | api(libs.ktor.serialization.json) 25 | } 26 | 27 | androidMain.dependencies { 28 | api(libs.ktor.client.android) 29 | } 30 | 31 | appleMain.dependencies { 32 | api(libs.ktor.client.darwin) 33 | } 34 | 35 | jsMain.dependencies { 36 | api(libs.ktor.client.js) 37 | } 38 | 39 | jvmMain.dependencies { 40 | api(libs.ktor.client.okhttp) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docs/geolocation/location.md: -------------------------------------------------------------------------------- 1 | # 🌐 Location 2 | 3 | The result from Geolocation operations will return a `Location` object, which looks like this: 4 | 5 | ```kotlin 6 | class Location( 7 | public val coordinates: Coordinates, 8 | public val accuracy: Double, 9 | public val azimuth: Azimuth?, 10 | public val speed: Speed?, 11 | public val altitude: Altitude?, 12 | public val timestampMillis: Long, 13 | ) 14 | ``` 15 | 16 | You can read the [KDocs here](https://docs.compass.jordond.dev/compass-core/dev.jordond.compass/-location/index.html). 17 | 18 | ### Detailed location data 19 | 20 | Depending on the `Priority` you use to request the location, determines what kind of data is present. 21 | 22 | All location results will return the coordinates and the accuracy of those coordinates in meters. In order to get the other values you will need to use a more accurate `Priority`, like so: 23 | 24 | ```kotlin 25 | val location: Location = geolocator.current(Priority.HighAccuracy) 26 | ``` 27 | 28 | There is still a chance those values will be `null`, since it is up to the device to determine what data is returned. 29 | 30 | {% hint style="info" %} 31 | On Android the `Priority.HighAccuracy` requires the `ACCESS_FINE_LOCATION` permission. This will be requested automatically by Compass. 32 | {% endhint %} 33 | -------------------------------------------------------------------------------- /demo/composeApp/src/commonMain/kotlin/geocoder/MapboxGeocoderScreen.kt: -------------------------------------------------------------------------------- 1 | package geocoder 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.derivedStateOf 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.saveable.rememberSaveable 9 | import androidx.compose.runtime.setValue 10 | import cafe.adriel.voyager.core.screen.Screen 11 | import dev.jordond.compass.geocoder.MapboxGeocoder 12 | import dev.jordond.compass.tools.web.HttpApiEndpoint 13 | 14 | /** 15 | * This screen demonstrates how to use the Mapbox Geocoder for all platforms. 16 | */ 17 | class MapboxGeocoderScreen : Screen { 18 | 19 | @Composable 20 | override fun Content() { 21 | var apiKey by rememberSaveable { mutableStateOf("") } 22 | val geocoder by remember(apiKey) { 23 | derivedStateOf { 24 | MapboxGeocoder( 25 | apiKey = apiKey, 26 | client = HttpApiEndpoint.httpClient(enableLogging = true) 27 | ) 28 | } 29 | } 30 | 31 | GeocoderContent( 32 | title = "Mapbox", 33 | geocoder = geocoder, 34 | apiKey = apiKey, 35 | updateApiKey = { apiKey = it }, 36 | ) 37 | } 38 | } -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [🧭 Compass](README.md) 4 | * [🖥️ Artifacts](supported-platforms.md) 5 | * [🚗 Roadmap](roadmap.md) 6 | 7 | ## Setup 8 | 9 | * [📇 All Dependencies](setup/add-dependencies.md) 10 | * [🤖 Android / iOS](setup/android-ios.md) 11 | * [📱 Mixed platforms](usage/mixed-platforms.md) 12 | 13 | ## Geocoding 14 | 15 | * [🌎 Geocoding Overview](geocoder/overview.md) 16 | * [📈 Web API Service](geocoding/web-api-service/README.md) 17 | * [Google Maps](geocoding/web-api-service/google-maps.md) 18 | * [Mapbox](geocoding/web-api-service/mapbox.md) 19 | * [❓ Request a Geocoder API](geocoding/request-a-geocoder-api.md) 20 | 21 | ## Geolocation 22 | 23 | * [📍 Geolocation Overview](geolocation/overview.md) 24 | * [🔍 Geolocator](geolocation/geolocator.md) 25 | * [🌐 Location](geolocation/location.md) 26 | * [📱 Android / iOS](geolocation/android-ios.md) 27 | * [🖥️ Browser](geolocation/browser.md) 28 | 29 | ## Autocomplete 30 | 31 | * [🔍 Autocomplete Overview](autocomplete/overview.md) 32 | * [🌎 Via Geocoding](autocomplete/via-geocoding.md) 33 | * [📱 Android / iOS](autocomplete/geocoding/android-ios.md) 34 | * [📈 Web API Service](autocomplete/geocoding/web-api-service.md) 35 | 36 | ## Misc 37 | 38 | * [📲 Demo](misc/demo.md) 39 | * [💾 Contributing](misc/contributing.md) 40 | * [🔧 Compass Reference](https://docs.compass.jordond.dev) 41 | -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/iosMain/kotlin/dev/jordond/compass/geocoder/mobile/internal/Operation.ios.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.mobile.internal 2 | 3 | import dev.jordond.compass.geocoder.exception.GeocodeException 4 | import kotlinx.coroutines.suspendCancellableCoroutine 5 | import platform.CoreLocation.CLGeocodeCompletionHandler 6 | import platform.CoreLocation.CLGeocoder 7 | import platform.CoreLocation.CLPlacemark 8 | import kotlin.coroutines.resume 9 | import kotlin.coroutines.resumeWithException 10 | 11 | internal suspend fun geocodeOperation( 12 | block: CLGeocoder.(listener: CLGeocodeCompletionHandler) -> Unit, 13 | ): List { 14 | val geocoder = CLGeocoder() 15 | 16 | @Suppress("UNCHECKED_CAST") 17 | return suspendCancellableCoroutine { continuation -> 18 | val completionHandler: CLGeocodeCompletionHandler = { result, error -> 19 | when { 20 | error != null -> 21 | continuation.resumeWithException(GeocodeException(error.localizedDescription)) 22 | else -> { 23 | continuation.resume((result as? List) ?: emptyList()) 24 | } 25 | } 26 | } 27 | 28 | geocoder.block(completionHandler) 29 | 30 | continuation.invokeOnCancellation { 31 | geocoder.cancelGeocode() 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /compass-geolocation-mobile/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.Platforms 2 | import dev.jordond.compass.convention.configureMultiplatform 3 | 4 | plugins { 5 | alias(libs.plugins.android.library) 6 | alias(libs.plugins.multiplatform) 7 | alias(libs.plugins.poko) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform(Platforms.Mobile) 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | implementation(projects.compassPermissions) 20 | implementation(projects.compassPermissionsMobile) 21 | implementation(projects.compassGeolocation) 22 | 23 | implementation(libs.kotlinx.coroutines.core) 24 | implementation(libs.kermit) 25 | } 26 | 27 | androidMain.dependencies { 28 | implementation(projects.compassToolsAndroid) 29 | api(libs.play.services.location) 30 | implementation(libs.play.services.coroutines) 31 | implementation(libs.androidx.activity) 32 | implementation(libs.androidx.fragment) 33 | implementation(libs.androidx.startup) 34 | } 35 | 36 | iosMain.dependencies { 37 | implementation(libs.kotlinx.atomicfu) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /demo/composeApp/src/nonMobileMain/kotlin/Platform.nonMobile.kt: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.geocoder.NotSupportedPlatformGeocoder 2 | import dev.jordond.compass.geocoder.PlatformGeocoder 3 | import dev.jordond.compass.geocoder.web.GoogleMapsPlatformGeocoder 4 | import dev.jordond.compass.geocoder.web.HttpApiPlatformGeocoder 5 | import dev.jordond.compass.geocoder.web.parameter.GoogleMapsLocationType 6 | import dev.jordond.compass.geolocation.Locator 7 | import dev.jordond.compass.geolocation.NotSupportedLocator 8 | import dev.jordond.compass.tools.web.HttpApiEndpoint 9 | 10 | /** 11 | * Normally you would use a custom [PlatformGeocoder] here or a [HttpApiPlatformGeocoder]. But in 12 | * this demo that is handled by the different screens that demonstrate different geocoder APIs. 13 | */ 14 | actual fun getPlatformGeocoder(): PlatformGeocoder { 15 | return NotSupportedPlatformGeocoder 16 | } 17 | 18 | actual fun getPlatformGeocoderOrFallback(apiKey: String): PlatformGeocoder { 19 | return GoogleMapsPlatformGeocoder( 20 | apiKey = apiKey, 21 | client = HttpApiEndpoint.httpClient(enableLogging = true) 22 | ) { 23 | locationType(GoogleMapsLocationType.Approximate) 24 | } 25 | } 26 | 27 | actual fun getPlatformLocator(): Locator { 28 | return NotSupportedLocator 29 | } 30 | 31 | actual val canShowAppSettings: Boolean = false 32 | 33 | actual fun showAppSettings() { 34 | // No-op 35 | } -------------------------------------------------------------------------------- /demo/composeApp/src/commonMain/kotlin/geocoder/OpenCageGeocoderScreen.kt: -------------------------------------------------------------------------------- 1 | package geocoder 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.derivedStateOf 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.saveable.rememberSaveable 9 | import androidx.compose.runtime.setValue 10 | import cafe.adriel.voyager.core.screen.Screen 11 | import dev.jordond.compass.geocoder.MapboxGeocoder 12 | import dev.jordond.compass.geocoder.OpenCageGeocoder 13 | import dev.jordond.compass.tools.web.HttpApiEndpoint 14 | 15 | /** 16 | * This screen demonstrates how to use the Mapbox Geocoder for all platforms. 17 | */ 18 | class OpenCageGeocoderScreen : Screen { 19 | 20 | @Composable 21 | override fun Content() { 22 | var apiKey by rememberSaveable { mutableStateOf("") } 23 | val geocoder by remember(apiKey) { 24 | derivedStateOf { 25 | OpenCageGeocoder( 26 | apiKey = apiKey, 27 | client = HttpApiEndpoint.httpClient(enableLogging = true) 28 | ) 29 | } 30 | } 31 | 32 | GeocoderContent( 33 | title = "OpenCage", 34 | geocoder = geocoder, 35 | apiKey = apiKey, 36 | updateApiKey = { apiKey = it }, 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /compass-geocoder-web/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import dev.jordond.compass.convention.configureMultiplatform 2 | 3 | plugins { 4 | alias(libs.plugins.android.library) 5 | alias(libs.plugins.multiplatform) 6 | alias(libs.plugins.poko) 7 | alias(libs.plugins.kotlinx.serialization) 8 | alias(libs.plugins.dokka) 9 | alias(libs.plugins.publish) 10 | alias(libs.plugins.convention.multiplatform) 11 | } 12 | 13 | configureMultiplatform() 14 | 15 | kotlin { 16 | sourceSets { 17 | commonMain.dependencies { 18 | api(projects.compassCore) 19 | api(projects.compassToolsWeb) 20 | implementation(projects.compassGeocoder) 21 | 22 | implementation(libs.kermit) 23 | implementation(libs.kotlinx.coroutines.core) 24 | api(libs.kotlinx.serialization.json) 25 | api(libs.ktor.client.core) 26 | api(libs.ktor.client.contentNegotiation) 27 | api(libs.ktor.client.logging) 28 | api(libs.ktor.serialization.json) 29 | } 30 | 31 | androidMain.dependencies { 32 | api(libs.ktor.client.android) 33 | } 34 | 35 | appleMain.dependencies { 36 | api(libs.ktor.client.darwin) 37 | } 38 | 39 | jsMain.dependencies { 40 | api(libs.ktor.client.js) 41 | } 42 | 43 | jvmMain.dependencies { 44 | api(libs.ktor.client.okhttp) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /compass-geocoder/src/commonMain/kotlin/dev/jordond/compass/geocoder/Geocoder.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder 2 | 3 | import dev.jordond.compass.geocoder.internal.DefaultGeocoder 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | 7 | /** 8 | * Provides geocoding operations: 9 | * 10 | * - Convert an address to a latitude and longitude (forward geocoding) 11 | * - Convert a latitude and longitude to an address (reverse geocoding) 12 | */ 13 | public interface Geocoder : ForwardGeocoder, ReverseGeocoder { 14 | 15 | public val platformGeocoder: PlatformGeocoder 16 | 17 | /** 18 | * Check if the geocoder is available. 19 | * 20 | * @return `true` if the geocoder is available, `false` otherwise. 21 | */ 22 | public fun isAvailable(): Boolean 23 | 24 | public companion object { 25 | 26 | @Suppress("ConstPropertyName") 27 | public const val DefaultMaxResults: Int = 5 28 | } 29 | } 30 | 31 | /** 32 | * Create a new [Geocoder] instance for geocoding operations. 33 | * 34 | * @param platformGeocoder The platform-specific geocoder to use. 35 | * @param dispatcher The [CoroutineDispatcher] to use for geocoding operations. 36 | * @return A new [Geocoder] instance. 37 | */ 38 | public fun Geocoder( 39 | platformGeocoder: PlatformGeocoder, 40 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 41 | ): Geocoder = DefaultGeocoder(platformGeocoder, dispatcher) -------------------------------------------------------------------------------- /compass-geolocation/src/commonMain/kotlin/dev/jordond/compass/geolocation/TrackingUpdate.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation 2 | 3 | import dev.drewhamilton.poko.Poko 4 | import dev.jordond.compass.Location 5 | 6 | /** 7 | * Represents the current status of a [Geolocator] tracking operation. 8 | */ 9 | public sealed interface TrackingStatus { 10 | 11 | /** 12 | * Check whether the tracking status is active. 13 | * 14 | * @returns `true` if the tracking status is [Tracking] or [Update], `false` otherwise. 15 | */ 16 | public val isActive: Boolean 17 | get() = this is Tracking || this is Update 18 | 19 | /** 20 | * Tracker is idle and waiting to start. 21 | */ 22 | public data object Idle : TrackingStatus 23 | 24 | /** 25 | * Tracker is actively tracking the user's location, and waiting for an update. 26 | */ 27 | public data object Tracking : TrackingStatus 28 | 29 | /** 30 | * Tracker has received an update to the user's location. 31 | * 32 | * @property location The updated location. 33 | */ 34 | @Poko 35 | public class Update(public val location: Location) : TrackingStatus 36 | 37 | /** 38 | * Tracker has encountered an error and is no longer active. 39 | * 40 | * @property cause The error that caused the tracking to stop. 41 | */ 42 | @Poko 43 | public class Error(public val cause: GeolocatorResult.Error) : TrackingStatus 44 | } -------------------------------------------------------------------------------- /compass-permissions/src/commonMain/kotlin/dev/jordond/compass/permissions/PermissionState.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions 2 | 3 | import dev.jordond.compass.permissions.exception.PermissionDeniedException 4 | import dev.jordond.compass.permissions.exception.PermissionDeniedForeverException 5 | 6 | /** 7 | * Describes the state of the location permission. 8 | */ 9 | public enum class PermissionState { 10 | 11 | /** 12 | * The permission has not been determined. 13 | */ 14 | NotDetermined, 15 | 16 | /** 17 | * The permission has been granted. 18 | */ 19 | Granted, 20 | 21 | /** 22 | * The permission has been denied. 23 | */ 24 | Denied, 25 | 26 | /** 27 | * The permission has been denied forever. 28 | * 29 | * This means the user has denied the permission and selected "Don't ask again". The user must 30 | * manually enable the permission in the settings. 31 | */ 32 | DeniedForever, 33 | } 34 | 35 | /** 36 | * Throws an exception if the permission state is not [PermissionState.Granted] or 37 | * [PermissionState.NotDetermined]. 38 | */ 39 | public fun PermissionState.throwOnError() { 40 | when (this) { 41 | PermissionState.NotDetermined, 42 | PermissionState.Granted, 43 | -> Unit 44 | PermissionState.Denied -> throw PermissionDeniedException() 45 | PermissionState.DeniedForever -> throw PermissionDeniedForeverException() 46 | } 47 | } -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/browser/api/model/GeolocationPositionError.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api.model 2 | 3 | /** 4 | * Represents an error that occurred while retrieving the geolocation. 5 | * 6 | * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError) 7 | * 8 | * @property code The error code. 9 | * @property message A human-readable message describing the error. 10 | */ 11 | public external class GeolocationPositionError { 12 | 13 | /** 14 | * The error code. 15 | * 16 | * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError/code) 17 | * 18 | * The following values are supported: 19 | * - 1: Permission denied 20 | * - 2: Position unavailable 21 | * - 3: Timeout 22 | */ 23 | public var code: Int 24 | 25 | /** 26 | * A human-readable message describing the error. 27 | * 28 | * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError/message) 29 | */ 30 | public var message: String 31 | } 32 | 33 | internal enum class GeolocationPositionErrorCode(val code: Int) { 34 | PermissionDenied(1), 35 | PositionUnavailable(2), 36 | Timeout(3) 37 | } 38 | 39 | internal fun GeolocationPositionError.value(): GeolocationPositionErrorCode = 40 | GeolocationPositionErrorCode.entries.first { it.code == code } 41 | -------------------------------------------------------------------------------- /docs/misc/contributing.md: -------------------------------------------------------------------------------- 1 | # 💾 Contributing 2 | 3 | Compass is open to contributions, if there is a bug or feature. Feel free to open up a PR or an issue. 4 | 5 | Please make every effort to follow existing conventions and style in order to keep the code as readable as possible. 6 | 7 | Contribute code changes through GitHub by forking the repository and sending a pull request. We squash all pull requests on merge. 8 | 9 | If you add, remove, or change `public` objects, make sure you update the binary-compatibility definitions by running the following command: 10 | 11 | ```bash 12 | ./gradlew apiDump 13 | ``` 14 | 15 | Then commit and push the changed files. 16 | 17 | ### Custom Geocoder API 18 | 19 | Compass includes a template module: `compass-geocoder-web-template` 20 | 21 | If there is a Geocoding Web API that you would wish to add to Compass, follow these steps: 22 | 23 | 1. Clone the repo: `git clone git@github.com:jordond/compass` 24 | 2. Make a copy of the `compass-geocoder-web-template` module 25 | 3. Rename the copy to match the web service you're adding: 26 | 1. ex: `compass-geocoder-web-myapi` 27 | 4. Search for the word "Template" in the module and replace it with your service name 28 | 5. Implement the `HttpApiPlatformGeocoder` interface 29 | 6. Customize the URL, query parameters, and the response models 30 | 7. Implement the `ForwardEndpoint` and `ReverseEndpoint` 31 | 8. Finish the implementation 32 | 9. Run `./gradlew apiDump` and commit the changes 33 | 10. Push & open a Pull Request 34 | 35 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle 2 | org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx4096M" 3 | org.gradle.caching=true 4 | org.gradle.configuration-cache=true 5 | 6 | # Kotlin 7 | kotlin.code.style=official 8 | 9 | # Android 10 | android.useAndroidX=true 11 | android.nonTransitiveRClass=true 12 | 13 | # MPP 14 | kotlin.mpp.stability.nowarn=true 15 | kotlin.mpp.enableCInteropCommonization=true 16 | kotlin.mpp.androidSourceSetLayoutVersion=2 17 | 18 | # Compose 19 | org.jetbrains.compose.experimental.uikit.enabled=true 20 | org.jetbrains.compose.experimental.wasm.enabled=true 21 | org.jetbrains.compose.experimental.jscanvas.enabled=true 22 | org.jetbrains.compose.experimental.macos.enabled=true 23 | 24 | # Maven 25 | SONATYPE_HOST=CENTRAL_PORTAL 26 | SONATYPE_AUTOMATIC_RELEASE=true 27 | RELEASE_SIGNING_ENABLED=true 28 | 29 | GROUP=dev.jordond.compass 30 | 31 | POM_NAME=compass 32 | POM_DESCRIPTION=A multiplatform location library. 33 | POM_INCEPTION_YEAR=2024 34 | POM_URL=https://github.com/jordond/compass 35 | 36 | POM_LICENCE_NAME=The MIT License 37 | POM_LICENCE_URL=https://github.com/jordond/compass/blob/master/LICENSE 38 | POM_LICENCE_DIST=repo 39 | 40 | POM_SCM_URL=https://github.com/jordond/compass 41 | POM_SCM_CONNECTION=scm:git:https://github.com/jordond/compass.git 42 | POM_SCM_DEV_CONNECTION=scm:git:git@github.com:jordond/compass.git 43 | 44 | POM_DEVELOPER_ID=jordond 45 | POM_DEVELOPER_NAME=Jordon de Hoog 46 | POM_DEVELOPER_URL=https://github.com/jordond 47 | POM_DEVELOPER_EMAIL=jordon.dehoog@gmail.com -------------------------------------------------------------------------------- /compass-permissions/src/commonMain/kotlin/dev/jordond/compass/permissions/LocationPermissionController.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions 2 | 3 | import dev.jordond.compass.Priority 4 | import dev.jordond.compass.permissions.exception.PermissionDeniedException 5 | import dev.jordond.compass.permissions.exception.PermissionDeniedForeverException 6 | 7 | /** 8 | * Defines a controller for managing location permissions. 9 | */ 10 | public interface LocationPermissionController { 11 | 12 | /** 13 | * Checks if the app has the necessary permissions to access the device's location. 14 | * 15 | * @return `true` if the app has the necessary permissions, `false` otherwise. 16 | */ 17 | public fun hasPermission(): Boolean 18 | 19 | /** 20 | * Requests the necessary permissions to access the device's location. 21 | * 22 | * @return The state of the permission after the request. 23 | */ 24 | public suspend fun requirePermissionFor(priority: Priority): PermissionState 25 | 26 | /** 27 | * Requests the necessary permissions to access the device's location, and throws an exception 28 | * if the permission is denied. 29 | * 30 | * @throws PermissionDeniedException If the permission is denied. 31 | * @throws PermissionDeniedForeverException If the permission is missing. 32 | */ 33 | public suspend fun requirePermissionForOrThrow(priority: Priority) { 34 | requirePermissionFor(priority).also { it.throwOnError() } 35 | } 36 | 37 | public companion object 38 | } 39 | -------------------------------------------------------------------------------- /demo/composeApp/src/commonMain/kotlin/geocoder/GoogleMapsGeocoderScreen.kt: -------------------------------------------------------------------------------- 1 | package geocoder 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.derivedStateOf 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.saveable.rememberSaveable 9 | import androidx.compose.runtime.setValue 10 | import cafe.adriel.voyager.core.screen.Screen 11 | import dev.jordond.compass.geocoder.GoogleMapsGeocoder 12 | import dev.jordond.compass.geocoder.web.parameter.GoogleMapsLocationType 13 | import dev.jordond.compass.tools.web.HttpApiEndpoint 14 | 15 | /** 16 | * This screen demonstrates how to use the Google Maps Geocoder for all platforms. 17 | */ 18 | class GoogleMapsGeocoderScreen : Screen { 19 | 20 | @Composable 21 | override fun Content() { 22 | var apiKey by rememberSaveable { mutableStateOf("") } 23 | val geocoder by remember(apiKey) { 24 | derivedStateOf { 25 | GoogleMapsGeocoder( 26 | apiKey = apiKey, 27 | client = HttpApiEndpoint.httpClient(enableLogging = true) 28 | ) { 29 | locationType(GoogleMapsLocationType.Approximate) 30 | } 31 | } 32 | } 33 | 34 | GeocoderContent( 35 | title = "Google Maps", 36 | geocoder = geocoder, 37 | apiKey = apiKey, 38 | updateApiKey = { apiKey = it }, 39 | ) 40 | } 41 | } -------------------------------------------------------------------------------- /compass-autocomplete/src/commonMain/kotlin/dev/jordond/compass/autocomplete/internal/DefaultAutocomplete.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.autocomplete.internal 2 | 3 | import dev.jordond.compass.autocomplete.Autocomplete 4 | import dev.jordond.compass.autocomplete.AutocompleteOptions 5 | import dev.jordond.compass.autocomplete.AutocompleteResult 6 | import dev.jordond.compass.autocomplete.AutocompleteService 7 | import kotlinx.coroutines.CancellationException 8 | import kotlinx.coroutines.CoroutineDispatcher 9 | import kotlinx.coroutines.withContext 10 | 11 | internal class DefaultAutocomplete( 12 | private val service: AutocompleteService, 13 | override val options: AutocompleteOptions, 14 | private val dispatcher: CoroutineDispatcher, 15 | ) : Autocomplete { 16 | 17 | override suspend fun search(query: String): AutocompleteResult { 18 | if (!service.isAvailable()) { 19 | return AutocompleteResult.NotSupported 20 | } 21 | 22 | try { 23 | if (query.length <= options.minimumQuery) { 24 | return AutocompleteResult.Success(emptyList()) 25 | } 26 | 27 | val result = withContext(dispatcher) { 28 | service.search(query) 29 | } 30 | 31 | return AutocompleteResult.Success(result) 32 | } catch (cancellation: CancellationException) { 33 | throw cancellation 34 | } catch (cause: Throwable) { 35 | return AutocompleteResult.Failed(cause.message ?: "An unknown error occurred") 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/iosMain/kotlin/dev/jordond/compass/geocoder/mobile/MobilePlatformGeocoder.ios.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.mobile 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import dev.jordond.compass.geocoder.mobile.internal.geocodeOperation 6 | import dev.jordond.compass.geocoder.mobile.internal.toCoordinates 7 | import dev.jordond.compass.geocoder.mobile.internal.toPlaces 8 | import platform.CoreLocation.CLLocation 9 | import platform.Foundation.NSLocale 10 | 11 | internal class IosPlatformGeocoder(private val locale: NSLocale?) : MobilePlatformGeocoder { 12 | 13 | override fun isAvailable(): Boolean = true 14 | 15 | override suspend fun reverse(latitude: Double, longitude: Double): List { 16 | val platformLocation = CLLocation(latitude, longitude) 17 | return geocodeOperation { listener -> 18 | reverseGeocodeLocation(platformLocation, locale, listener) 19 | }.toPlaces() 20 | } 21 | 22 | override suspend fun placeFromAddress(address: String): List { 23 | return geocodeOperation { listener -> geocodeAddressString(address, null, locale, listener) }.toPlaces() 24 | } 25 | 26 | override suspend fun forward(address: String): List { 27 | return geocodeOperation { listener -> 28 | geocodeAddressString(address, null, locale, listener) 29 | }.toCoordinates() 30 | } 31 | } 32 | 33 | internal actual fun createPlatformGeocoder(locale: String?): MobilePlatformGeocoder { 34 | return IosPlatformGeocoder(locale?.let(::NSLocale)) 35 | } -------------------------------------------------------------------------------- /compass-permissions-mobile/src/iosMain/kotlin/dev/jordond/compass/permissions/mobile/internal/LocationPermissionManagerDelegate.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.mobile.internal 2 | 3 | import platform.CoreLocation.CLAuthorizationStatus 4 | import platform.CoreLocation.CLLocationManager 5 | import platform.CoreLocation.CLLocationManagerDelegateProtocol 6 | import platform.darwin.NSObject 7 | 8 | internal class LocationPermissionManagerDelegate : NSObject(), CLLocationManagerDelegateProtocol { 9 | 10 | internal val manager = CLLocationManager() 11 | 12 | private var permissionCallback: ((CLAuthorizationStatus) -> Unit)? = null 13 | private var requestPermissionCallback: ((CLAuthorizationStatus) -> Unit)? = null 14 | 15 | init { 16 | manager.delegate = this 17 | } 18 | 19 | fun currentPermissionStatus(): CLAuthorizationStatus { 20 | return manager.authorizationStatus 21 | } 22 | 23 | fun monitorPermission(callback: (CLAuthorizationStatus) -> Unit) { 24 | permissionCallback = callback 25 | callback(manager.authorizationStatus) 26 | } 27 | 28 | override fun locationManagerDidChangeAuthorization(manager: CLLocationManager) { 29 | val status = manager.authorizationStatus 30 | permissionCallback?.invoke(status) 31 | requestPermissionCallback?.invoke(status) 32 | } 33 | 34 | fun requestPermission(callback: (CLAuthorizationStatus) -> Unit) { 35 | requestPermissionCallback = { status -> 36 | callback(status) 37 | requestPermissionCallback = null 38 | } 39 | 40 | manager.requestWhenInUseAuthorization() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /compass-geocoder-web-googlemaps/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/google/internal/ResultResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.google.internal 2 | 3 | import dev.jordond.compass.InternalCompassApi 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @InternalCompassApi 8 | @Serializable 9 | public data class ResultResponse( 10 | @SerialName("address_components") 11 | public val addressComponents: List = emptyList(), 12 | 13 | @SerialName("formatted_address") 14 | public val formattedAddress: String? = null, 15 | 16 | @SerialName("geometry") 17 | public val geometry: GeometryResponse? = null, 18 | ) 19 | 20 | @InternalCompassApi 21 | @Serializable 22 | public data class AddressComponentResponse( 23 | @SerialName("long_name") 24 | val long: String, 25 | 26 | @SerialName("short_name") 27 | val short: String, 28 | 29 | @SerialName("types") 30 | val types: List = emptyList(), 31 | ) 32 | 33 | internal enum class AddressComponentType(val value: String) { 34 | Name("route"), 35 | Thoroughfare("route"), 36 | Neighborhood("neighborhood"), 37 | Locality("locality"), 38 | Country("country"), 39 | AdministrativeAreaLevel1("administrative_area_level_1"), 40 | AdministrativeAreaLevel2("administrative_area_level_2"), 41 | AdministrativeAreaLevel3("administrative_area_level_3"), 42 | PostalCode("postal_code"), 43 | } 44 | 45 | internal fun List.find( 46 | component: AddressComponentType, 47 | ): AddressComponentResponse? { 48 | return find { it.types.contains(component.value) } 49 | } -------------------------------------------------------------------------------- /compass-geocoder-web-template/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/parameter/TemplateParameters.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.parameter 2 | 3 | import dev.drewhamilton.poko.Poko 4 | import dev.jordond.compass.tools.web.parameter.QueryParameters 5 | import dev.jordond.compass.tools.web.parameter.QueryParametersBuilder 6 | import dev.jordond.compass.tools.web.parameter.parametersOf 7 | 8 | /** 9 | * Options for the TEMPLATE geocoder. 10 | * 11 | * See the []() 12 | * for more information. 13 | */ 14 | @Poko 15 | public class TemplateParameters( 16 | public val foo: String? = null, 17 | ) : QueryParameters { 18 | 19 | override val parameters: Map = parametersOf( 20 | "foo" to foo, 21 | ) 22 | } 23 | 24 | /** 25 | * A builder class to build [TemplateParameters]. 26 | */ 27 | @Suppress("MemberVisibilityCanBePrivate") 28 | public class TemplateParametersBuilder : QueryParametersBuilder { 29 | 30 | /** 31 | * @see TemplateParameters.locationType 32 | */ 33 | private var foo: String? = null 34 | 35 | /** 36 | * Build the instance of [TemplateParameters]. 37 | */ 38 | override fun build(): TemplateParameters = TemplateParameters( 39 | foo = foo, 40 | ) 41 | } 42 | 43 | /** 44 | * Create a [TemplateParameters] instance with the provided [block]. 45 | * 46 | * @param block The builder block to build the instance of [TemplateParameters]. 47 | * @return The instance of [TemplateParameters]. 48 | */ 49 | public fun templateParameters( 50 | block: TemplateParametersBuilder.() -> Unit, 51 | ): TemplateParameters = TemplateParametersBuilder().apply(block).build() -------------------------------------------------------------------------------- /compass-geocoder/src/commonMain/kotlin/dev/jordond/compass/geocoder/PlatformGeocoder.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | 6 | /** 7 | * Wrapper for platform-specific geocoding services. 8 | */ 9 | public interface PlatformGeocoder { 10 | 11 | /** 12 | * Returns true if the geocoder is available on the current platform. 13 | * 14 | * @return `true` if the geocoder is available, false otherwise. 15 | */ 16 | public fun isAvailable(): Boolean 17 | 18 | /** 19 | * Get a list of [Coordinates]s from an address. 20 | */ 21 | public suspend fun forward(address: String): List 22 | 23 | /** 24 | * Get a list of [Place]s from latitude and longitude coordinates. 25 | * 26 | * @param latitude The latitude of the coordinates. 27 | * @param longitude The longitude of the coordinates. 28 | * @return The address of the coordinates or empty list if the address could not be found. 29 | */ 30 | public suspend fun reverse(latitude: Double, longitude: Double): List 31 | 32 | public companion object 33 | } 34 | 35 | /** 36 | * A no-op [PlatformGeocoder] that is used when the platform does not support geocoding. 37 | * 38 | * This can be used as a fallback when the platform does not support geocoding. 39 | */ 40 | public object NotSupportedPlatformGeocoder : PlatformGeocoder { 41 | 42 | override fun isAvailable(): Boolean = false 43 | override suspend fun forward(address: String): List = emptyList() 44 | override suspend fun reverse(latitude: Double, longitude: Double): List = emptyList() 45 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | google() 7 | mavenCentral() 8 | } 9 | 10 | includeBuild("buildLogic") 11 | } 12 | 13 | dependencyResolutionManagement { 14 | @Suppress("UnstableApiUsage") 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | plugins { 22 | id("com.gradle.develocity") version "4.2.2" 23 | } 24 | 25 | develocity { 26 | buildScan { 27 | termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use") 28 | termsOfUseAgree.set("yes") 29 | 30 | publishing.onlyIf { context -> 31 | context.buildResult.failures.isNotEmpty() && !System.getenv("CI").isNullOrEmpty() 32 | } 33 | } 34 | } 35 | 36 | rootProject.name = "compass" 37 | 38 | include( 39 | ":compass-core", 40 | ":compass-autocomplete", 41 | ":compass-autocomplete-geocoder-googlemaps", 42 | ":compass-autocomplete-geocoder-mapbox", 43 | ":compass-autocomplete-mobile", 44 | ":compass-autocomplete-web", 45 | ":compass-geocoder", 46 | ":compass-geocoder-mobile", 47 | ":compass-geocoder-web", 48 | ":compass-geocoder-web-googlemaps", 49 | ":compass-geocoder-web-mapbox", 50 | ":compass-geocoder-web-opencage", 51 | ":compass-geocoder-web-template", 52 | ":compass-geolocation", 53 | ":compass-geolocation-mobile", 54 | ":compass-geolocation-browser", 55 | ":compass-permissions", 56 | ":compass-permissions-mobile", 57 | ":compass-tools-android", 58 | ":compass-tools-web", 59 | ) 60 | 61 | include(":demo:composeApp") 62 | -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/androidMain/kotlin/dev/jordond/compass/geocoder/mobile/internal/Operation.android.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.mobile.internal 2 | 3 | import android.annotation.TargetApi 4 | import android.location.Address 5 | import android.location.Geocoder 6 | import android.os.Build 7 | import dev.jordond.compass.geocoder.exception.GeocodeException 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | import kotlin.coroutines.resume 11 | import kotlin.coroutines.resumeWithException 12 | import kotlin.coroutines.suspendCoroutine 13 | 14 | internal suspend fun syncOperation( 15 | block: () -> MutableList
?, 16 | ): List
{ 17 | val result: Result?> = withContext(Dispatchers.IO) { 18 | runCatching { 19 | block() 20 | } 21 | } 22 | 23 | val exception = result.exceptionOrNull() 24 | if (exception != null) throw GeocodeException(exception.message) 25 | 26 | return result.getOrNull() ?: emptyList() 27 | } 28 | 29 | @TargetApi(Build.VERSION_CODES.TIRAMISU) 30 | internal suspend fun asyncOperation( 31 | block: (listener: Geocoder.GeocodeListener) -> Unit, 32 | ): List
{ 33 | return suspendCoroutine { continuation -> 34 | val listener = object : Geocoder.GeocodeListener { 35 | override fun onGeocode(addresses: MutableList
) { 36 | continuation.resume(addresses.toList()) 37 | } 38 | 39 | override fun onError(errorMessage: String?) { 40 | continuation.resumeWithException(GeocodeException(errorMessage)) 41 | } 42 | } 43 | 44 | block(listener) 45 | } 46 | } -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/commonMain/kotlin/dev/jordond/compass/geocoder/mobile/MobilePlatformGeocoder.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.mobile 2 | 3 | import dev.jordond.compass.Place 4 | import dev.jordond.compass.geocoder.GeocoderResult 5 | import dev.jordond.compass.geocoder.PlatformGeocoder 6 | 7 | public interface MobilePlatformGeocoder : PlatformGeocoder { 8 | 9 | /** 10 | * Geocode an address to a list of [Place]. 11 | * 12 | * In most cases, the list will contain a single [Place]. However, in some cases, it may 13 | * return multiple [Place]s. 14 | * 15 | * @param address The address to geocode. 16 | * @return A [GeocoderResult] containing the list of [Place]s or an error. 17 | */ 18 | public suspend fun placeFromAddress(address: String): List 19 | } 20 | 21 | /** 22 | * Create an Android/iOS [PlatformGeocoder] instance for geocoding operations. 23 | * 24 | * @param locale The locale string used for reverse geocoding, null will use device default 25 | * @return A new Android/iOS [PlatformGeocoder] instance. 26 | */ 27 | public fun MobilePlatformGeocoder(locale: String? = null): MobilePlatformGeocoder = 28 | createPlatformGeocoder(locale) 29 | 30 | /** 31 | * Create an Android/iOS [PlatformGeocoder] instance for geocoding operations. 32 | * 33 | * @param locale The locale string used for reverse geocoding, null will use device default 34 | * @return A new Android/iOS [PlatformGeocoder] instance. 35 | */ 36 | public fun PlatformGeocoder.Companion.mobile(locale: String? = null): MobilePlatformGeocoder = 37 | MobilePlatformGeocoder(locale) 38 | 39 | internal expect fun createPlatformGeocoder(locale: String? = null): MobilePlatformGeocoder -------------------------------------------------------------------------------- /docs/geolocation/browser.md: -------------------------------------------------------------------------------- 1 | # 🖥️ Browser 2 | 3 | Compass supports [HTML5 Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation\_API) with the `compass-geolocation-browser` artifact. The artifact supports JS and WASM targets. 4 | 5 | Geolocation in the browser requires the user to provide permission. This is handled automatically when you start tracking or attempt to get the current location. 6 | 7 | {% hint style="info" %} 8 | If your project supports both Browser and other targets, you need to configure your setup to provide a `Geolocator` for each platform. Check out [mixed-platforms.md](../usage/mixed-platforms.md "mention")for more information. 9 | {% endhint %} 10 | 11 | ## Usage 12 | 13 | Follow these steps to create a `Geolocator` 14 | 15 | ### Create Locator 16 | 17 | Geolocator is powered by the `Locator` object: 18 | 19 | ```kotlin 20 | val locator: Locator = createBrowserLocator() 21 | val locator: Locator = Locator.browser() 22 | ``` 23 | 24 | ### Create Geolocator 25 | 26 | Then you can use that to create the `Geolocator` object: 27 | 28 | ```kotlin 29 | val geolocator: Geolocator = Geolocator(locator) 30 | ``` 31 | 32 | Or you can use an extension function to skip the `Locator` step: 33 | 34 | ```kotlin 35 | val geolocator: Geolocator = Geolocator() 36 | val geolocator: Geolocator = BrowserGeolocator() 37 | val geolocator: Geolocator = Geolocator.browser() 38 | ``` 39 | 40 | ## Permissions 41 | 42 | Nothing extra is required to setup permissions for the browser. 43 | 44 | When you attempt to get the location, the browser will show a prompt to the user. They can allow, deny, or do one of those permanently. 45 | 46 | ## Get location 47 | 48 | Now you can follow the steps in [geolocator.md](geolocator.md "mention") 49 | -------------------------------------------------------------------------------- /compass-tools-web/src/commonMain/kotlin/dev/jordond/compass/tools/web/Request.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.tools.web 2 | 3 | import co.touchlab.kermit.Logger 4 | import dev.jordond.compass.InternalCompassApi 5 | import dev.jordond.compass.tools.web.exception.WebException 6 | import io.ktor.client.HttpClient 7 | import io.ktor.client.request.get 8 | import io.ktor.client.request.url 9 | import io.ktor.client.statement.HttpResponse 10 | import kotlinx.coroutines.CancellationException 11 | 12 | @InternalCompassApi 13 | /** 14 | * Makes a web request to the specified URL and returns the result of the request. 15 | * 16 | * Internal API. 17 | * 18 | * @param Result The type of the result of the request. 19 | * @param url The URL to make the request to. 20 | * @param resultMapper A function that maps the HTTP response to the result type. 21 | * @return The result of the request. 22 | * @throws WebException If the request fails. 23 | */ 24 | public suspend fun HttpClient.makeRequest( 25 | url: String, 26 | resultMapper: suspend (HttpResponse) -> Result, 27 | ): Result { 28 | try { 29 | val response = get { url(url) } 30 | if (response.status.value in 200..299) { 31 | return resultMapper(response) 32 | } else { 33 | throw WebException( 34 | statusCode = response.status, 35 | message = "HTTP request failed with status code ${response.status.value}", 36 | ) 37 | } 38 | } catch (cancellation: CancellationException) { 39 | throw cancellation 40 | } catch (webException: WebException) { 41 | throw webException 42 | } catch (cause: Throwable) { 43 | Logger.e(cause) { "Unable to make web request" } 44 | throw cause 45 | } 46 | } -------------------------------------------------------------------------------- /docs/autocomplete/overview.md: -------------------------------------------------------------------------------- 1 | # 🔍 Overview 2 | 3 | Autocomplete is used to provide real-time suggestions for locations as the user types in a query. 4 | 5 | Compass currently provides an interface for interacting with a geocoding service, such as Mapbox. 6 | The 7 | autocomplete feature provides real-time suggestions to the user as they type an address or location 8 | into a search field. 9 | 10 | The library is structured around several key components: 11 | 12 | 1. `AutocompleteService`: This is an interface that represents a service providing autocomplete 13 | suggestions. It has a `search(query: String)` function, which searches for autocomplete 14 | suggestions based on the provided 15 | query. 16 | 17 | 2. `AutocompleteResult`: This is a sealed class that represents the result of an autocomplete 18 | operation. It can either be a `Success` with a list of data or an `Error` with a specific error 19 | message. 20 | 21 | 3. `Autocomplete`: This is a function that creates a new instance of `AutocompleteService` that uses 22 | a HTTP service to provide autocomplete suggestions. 23 | 24 | {% hint style="info" %} 25 | The current implementations of the `AutocompleteService` use the Geocoding artifacts from Compass 26 | and perform a forward geocode operation. That means that the results may not be the highest of 27 | quality. 28 | {% endhint %} 29 | 30 | ### Quick start 31 | 32 | ```kotlin 33 | suspend fun autocomplete(query: String): List { 34 | val autocomplete = AutoComplete.mobile() 35 | return autocomplete.search("London").getOrNull() 36 | } 37 | ``` 38 | 39 | In this quick example the `AutoComplete.mobile()()` function is a convenience function for creating 40 | a `AutoComplete` object. Each of the `autocomplete-*` artifacts provide one. 41 | -------------------------------------------------------------------------------- /compass-geocoder-web/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/ReverseHttpApiPlatformGeocoder.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import dev.jordond.compass.exception.NotSupportedException 6 | import dev.jordond.compass.tools.web.HttpApiEndpoint 7 | import dev.jordond.compass.tools.web.makeRequest 8 | import io.ktor.client.HttpClient 9 | import io.ktor.client.statement.HttpResponse 10 | 11 | /** 12 | * A [HttpApiPlatformGeocoder] for reverse geocoding that uses the provided [ReverseEndpoint]. 13 | * 14 | * @see HttpApiPlatformGeocoder 15 | * @property endpoint The endpoint to use for reverse geocoding. 16 | * @property client The [HttpClient] to use for making requests. 17 | */ 18 | public class ReverseHttpApiPlatformGeocoder( 19 | private val endpoint: ReverseEndpoint, 20 | private val client: HttpClient, 21 | ) : HttpApiPlatformGeocoder { 22 | 23 | override fun isAvailable(): Boolean = true 24 | 25 | override suspend fun forward(address: String): List { 26 | throw NotSupportedException() 27 | } 28 | 29 | override suspend fun reverse(latitude: Double, longitude: Double): List { 30 | val url = endpoint.url(Coordinates(latitude, longitude)) 31 | return client.makeRequest(url, endpoint::mapResponse) 32 | } 33 | } 34 | 35 | /** 36 | * An endpoint for reverse geocoding. 37 | * 38 | * @param url The URL to use for reverse geocoding. 39 | * @param mapResponse A function to map the response to a list of [Coordinates]. 40 | */ 41 | public fun ReverseEndpoint( 42 | url: (Coordinates) -> String, 43 | mapResponse: suspend (HttpResponse) -> List, 44 | ): ReverseEndpoint = HttpApiEndpoint(url, mapResponse) -------------------------------------------------------------------------------- /compass-geocoder-web-opencage/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/opencage/internal/GeocodeResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.opencage.internal 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import kotlinx.serialization.SerialName 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | internal data class GeocodeResponse( 10 | @SerialName("results") 11 | val results: List, 12 | ) 13 | 14 | internal fun GeocodeResponse.toCoordinates(): List { 15 | return results.map { result -> result.toCoordinates() } 16 | } 17 | 18 | internal fun GeocodeResponse.toPlaces(): List = results.mapNotNull { result -> 19 | val components = result.components 20 | Place( 21 | coordinates = result.toCoordinates(), 22 | name = components.restaurant?.takeIf { it.isNotBlank() }, 23 | street = components.road?.takeIf { it.isNotBlank() }, 24 | isoCountryCode = components.countryCode?.takeIf { it.isNotBlank() }, 25 | country = components.country?.takeIf { it.isNotBlank() }, 26 | postalCode = components.postcode?.takeIf { it.isNotBlank() }, 27 | administrativeArea = components.state?.takeIf { it.isNotBlank() }, 28 | subAdministrativeArea = null, 29 | locality = components.normalizedCity?.takeIf { it.isNotBlank() }, 30 | subLocality = components.suburb?.takeIf { it.isNotBlank() }, 31 | thoroughfare = components.road?.takeIf { it.isNotBlank() }, 32 | subThoroughfare = null, 33 | ).takeIf { it.isEmpty.not() } 34 | } 35 | 36 | private fun ResultResponse.toCoordinates(): Coordinates { 37 | return Coordinates( 38 | latitude = geometry.lat, 39 | longitude = geometry.lng, 40 | ) 41 | } -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/commonMain/kotlin/dev/jordond/compass/geocoder/MobileGeocoder.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder 2 | 3 | import dev.jordond.compass.geocoder.mobile.MobilePlatformGeocoder 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.Dispatchers 6 | 7 | /** 8 | * Create a new [Geocoder] instance for geocoding operations. 9 | * 10 | * @param dispatcher The [CoroutineDispatcher] to use for geocoding operations. 11 | * @param locale The locale string used for reverse geocoding, null will use device default 12 | * @return A new [Geocoder] instance. 13 | */ 14 | public fun Geocoder( 15 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 16 | locale: String? = null, 17 | ): Geocoder = MobileGeocoder(dispatcher, locale) 18 | 19 | /** 20 | * Create a new [Geocoder] instance for geocoding operations. 21 | * 22 | * @param dispatcher The [CoroutineDispatcher] to use for geocoding operations. 23 | * @param locale The locale string used for reverse geocoding, null will use device default 24 | * @return A new [Geocoder] instance. 25 | */ 26 | @Suppress("FunctionName") 27 | public fun MobileGeocoder( 28 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 29 | locale: String? = null, 30 | ): Geocoder = Geocoder(MobilePlatformGeocoder(locale), dispatcher) 31 | 32 | /** 33 | * Create a new [Geocoder] instance for geocoding operations. 34 | * 35 | * @param dispatcher The [CoroutineDispatcher] to use for geocoding operations. 36 | * @param locale The locale string used for reverse geocoding, null will use device default 37 | * @return A new [Geocoder] instance. 38 | */ 39 | public fun Geocoder.Companion.mobile( 40 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 41 | locale: String? = null, 42 | ): Geocoder = MobileGeocoder(dispatcher, locale) -------------------------------------------------------------------------------- /compass-geocoder-web/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/ForwardHttpApiPlatformGeocoder.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import dev.jordond.compass.exception.NotSupportedException 6 | import dev.jordond.compass.tools.web.HttpApiEndpoint 7 | import dev.jordond.compass.tools.web.makeRequest 8 | import io.ktor.client.HttpClient 9 | import io.ktor.client.statement.HttpResponse 10 | import io.ktor.http.encodeURLParameter 11 | 12 | /** 13 | * A [HttpApiPlatformGeocoder] for forward geocoding that uses the provided [ForwardEndpoint]. 14 | * 15 | * @see HttpApiPlatformGeocoder 16 | * @property endpoint The endpoint to use for forward geocoding. 17 | * @property client The [HttpClient] to use for making requests. 18 | */ 19 | public class ForwardHttpApiPlatformGeocoder( 20 | private val endpoint: ForwardEndpoint, 21 | private val client: HttpClient, 22 | ) : HttpApiPlatformGeocoder { 23 | 24 | override fun isAvailable(): Boolean = true 25 | 26 | override suspend fun forward(address: String): List { 27 | val url = endpoint.url(address.encodeURLParameter()) 28 | return client.makeRequest(url, endpoint::mapResponse) 29 | } 30 | 31 | override suspend fun reverse(latitude: Double, longitude: Double): List { 32 | throw NotSupportedException() 33 | } 34 | } 35 | 36 | /** 37 | * An endpoint for forward geocoding. 38 | * 39 | * @param url The URL to use for forward geocoding. 40 | * @param mapResponse A function to map the response to a list of [Coordinates]. 41 | */ 42 | public fun ForwardEndpoint( 43 | url: (String) -> String, 44 | mapResponse: suspend (HttpResponse) -> List, 45 | ): ForwardEndpoint = HttpApiEndpoint(url, mapResponse) -------------------------------------------------------------------------------- /compass-geocoder-mobile/src/iosMain/kotlin/dev/jordond/compass/geocoder/mobile/internal/PlacemarkMapper.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.mobile.internal 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import kotlinx.cinterop.ExperimentalForeignApi 6 | import kotlinx.cinterop.useContents 7 | import platform.Contacts.CNPostalAddress 8 | import platform.CoreLocation.CLPlacemark 9 | import platform.CoreLocation.postalAddress 10 | 11 | /** 12 | * Map a [CLPlacemark] to a [Place]. 13 | */ 14 | @Suppress("CAST_NEVER_SUCCEEDS") 15 | @OptIn(ExperimentalForeignApi::class) 16 | internal fun CLPlacemark.toPlace(): Place? { 17 | val street = runCatching { (postalAddress as? CNPostalAddress)?.street }.getOrNull() 18 | return Place( 19 | coordinates = toCoordinates() ?: return null, 20 | name = name, 21 | street = street, 22 | isoCountryCode = ISOcountryCode, 23 | country = country, 24 | postalCode = postalCode, 25 | administrativeArea = administrativeArea, 26 | subAdministrativeArea = subAdministrativeArea, 27 | locality = locality, 28 | subLocality = subLocality, 29 | thoroughfare = thoroughfare, 30 | subThoroughfare = subThoroughfare 31 | ) 32 | } 33 | 34 | internal fun List.toPlaces(): List = mapNotNull { it.toPlace() } 35 | 36 | @OptIn(ExperimentalForeignApi::class) 37 | internal fun CLPlacemark.toCoordinates(): Coordinates? { 38 | val (latitude, longitude) = location?.coordinate?.useContents { latitude to longitude } 39 | ?: return null 40 | 41 | return Coordinates( 42 | latitude = latitude, 43 | longitude = longitude, 44 | ) 45 | } 46 | 47 | internal fun List.toCoordinates(): List = 48 | mapNotNull { it.toCoordinates() } -------------------------------------------------------------------------------- /compass-geocoder-web-opencage/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/opencage/internal/ResultResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.opencage.internal 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | internal class ResultResponse( 8 | @SerialName("components") 9 | val components: Components, 10 | 11 | @SerialName("formatted") 12 | val formatted: String, 13 | 14 | @SerialName("geometry") 15 | val geometry: Geometry, 16 | ) 17 | 18 | @Serializable 19 | internal class Coordinate( 20 | @SerialName("lat") 21 | val lat: Double, 22 | 23 | @SerialName("lng") 24 | val lng: Double, 25 | ) 26 | 27 | @Serializable 28 | internal class Components( 29 | @SerialName("_category") 30 | val category: String? = null, 31 | 32 | @SerialName("_normalized_city") 33 | val normalizedCity: String? = null, 34 | 35 | @SerialName("_type") 36 | val type: String? = null, 37 | 38 | @SerialName("city") 39 | val city: String? = null, 40 | 41 | @SerialName("continent") 42 | val continent: String? = null, 43 | 44 | @SerialName("country") 45 | val country: String? = null, 46 | 47 | @SerialName("country_code") 48 | val countryCode: String? = null, 49 | 50 | @SerialName("postcode") 51 | val postcode: String? = null, 52 | 53 | @SerialName("restaurant") 54 | val restaurant: String? = null, 55 | 56 | @SerialName("road") 57 | val road: String? = null, 58 | 59 | @SerialName("state") 60 | val state: String? = null, 61 | 62 | @SerialName("suburb") 63 | val suburb: String? = null, 64 | ) 65 | 66 | @Serializable 67 | internal class Geometry( 68 | @SerialName("lat") 69 | val lat: Double, 70 | 71 | @SerialName("lng") 72 | val lng: Double, 73 | ) -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/browser/api/model/CreateGeolocationOptions.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api.model 2 | 3 | import dev.jordond.compass.geolocation.browser.api.Geolocation 4 | import dev.jordond.compass.geolocation.browser.internal.Object 5 | 6 | /** 7 | * Represents the options that can be passed to the [Geolocation] methods. 8 | * 9 | * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition#options) 10 | * 11 | * @param enableHighAccuracy A boolean value that indicates the application would like to receive 12 | * the best possible results. If true and if the device is able to provide a more accurate position, 13 | * it will do so. Note that this can result in slower response times or increased power consumption 14 | * (with a GPS chip on a mobile device for example). On the other hand, if false, the device can 15 | * take the liberty to save resources by responding more quickly and/or using less power. 16 | * @param timeout A positive long value representing the maximum length of time (in milliseconds) 17 | * the device is allowed to take in order to return a position. The default value is Infinity, 18 | * meaning that getCurrentPosition() won't return until the position is available. 19 | * @param maximumAge A positive long value indicating the maximum age in milliseconds of a possible cached 20 | * position that is acceptable to return. If set to 0, it means that the device cannot use a cached 21 | * position and must attempt to retrieve the real current position. If set to Infinity the device 22 | * must return a cached position regardless of its age. Default: 0. 23 | */ 24 | public expect fun createGeolocationOptions( 25 | enableHighAccuracy: Boolean, 26 | timeout: Double, 27 | maximumAge: Double, 28 | ): Object -------------------------------------------------------------------------------- /compass-geocoder/src/commonMain/kotlin/dev/jordond/compass/geocoder/ForwardGeocoder.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.geocoder.internal.DefaultGeocoder 5 | import kotlinx.coroutines.CoroutineDispatcher 6 | import kotlinx.coroutines.Dispatchers 7 | 8 | public interface ForwardGeocoder { 9 | 10 | /** 11 | * Get a list of [Coordinates]s for a given address. 12 | * 13 | * In most cases, the list will contain a single [Coordinates]. However, in some cases, it may 14 | * return multiple [Coordinates]s. 15 | * 16 | * @param address The address to geocode. 17 | * @return A [GeocoderResult] containing the list of [Coordinates]s or an error. 18 | */ 19 | public suspend fun forward(address: String): GeocoderResult 20 | 21 | /** 22 | * Get a list of [Coordinates]s for a given address. 23 | * 24 | * In most cases, the list will contain a single [Coordinates]. However, in some cases, it may 25 | * return multiple [Coordinates]s. 26 | * 27 | * @param address The address to geocode. 28 | * @return A [GeocoderResult] containing the list of [Coordinates]s or an error. 29 | */ 30 | public suspend fun locations(address: String): GeocoderResult = forward(address) 31 | } 32 | 33 | /** 34 | * Create a new [ForwardGeocoder] using the provided [PlatformGeocoder]. 35 | * 36 | * @param platformGeocoder The [PlatformGeocoder] to use for geocoding. 37 | * @param dispatcher The [CoroutineDispatcher] to use for running the geocoding operations. 38 | * @return A new [ForwardGeocoder]. 39 | */ 40 | public fun ForwardGeocoder( 41 | platformGeocoder: PlatformGeocoder, 42 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 43 | ): ForwardGeocoder = DefaultGeocoder(platformGeocoder, dispatcher) 44 | -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/BrowserGeolocator.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation 2 | 3 | import dev.jordond.compass.geolocation.browser.BrowserLocator 4 | import dev.jordond.compass.geolocation.browser.createBrowserLocator 5 | import kotlinx.coroutines.CoroutineDispatcher 6 | import kotlinx.coroutines.Dispatchers 7 | 8 | /** 9 | * Create a new [Geolocator] that uses the [BrowserLocator] for geolocation operations. 10 | * 11 | * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) 12 | * 13 | * @param dispatcher The [CoroutineDispatcher] to use for geolocation operations. 14 | * @return A new [Geolocator] instance. 15 | */ 16 | public fun Geolocator( 17 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 18 | ): Geolocator = BrowserGeolocator(dispatcher) 19 | 20 | /** 21 | * Create a new [Geolocator] that uses the [BrowserLocator] for geolocation operations. 22 | * 23 | * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) 24 | * 25 | * @param dispatcher The [CoroutineDispatcher] to use for geolocation operations. 26 | * @return A new [Geolocator] instance. 27 | */ 28 | @Suppress("FunctionName") 29 | public fun BrowserGeolocator( 30 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 31 | ): Geolocator = Geolocator(createBrowserLocator(), dispatcher) 32 | 33 | /** 34 | * Create a new [Geolocator] that uses the [BrowserLocator] for geolocation operations. 35 | * 36 | * [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) 37 | * 38 | * @param dispatcher The [CoroutineDispatcher] to use for geolocation operations. 39 | * @return A new [Geolocator] instance. 40 | */ 41 | public fun Geolocator.Companion.browser( 42 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 43 | ): Geolocator = BrowserGeolocator(dispatcher) -------------------------------------------------------------------------------- /demo/iosApp/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | NSLocationWhenInUseUsageDescription 50 | Its a demo about location 51 | 52 | 53 | -------------------------------------------------------------------------------- /demo/composeApp/src/commonMain/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalMaterial3Api::class) 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.automirrored.filled.ArrowBack 7 | import androidx.compose.material3.ExperimentalMaterial3Api 8 | import androidx.compose.material3.Icon 9 | import androidx.compose.material3.IconButton 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.Scaffold 12 | import androidx.compose.material3.Text 13 | import androidx.compose.material3.TopAppBar 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import cafe.adriel.voyager.navigator.CurrentScreen 17 | import cafe.adriel.voyager.navigator.Navigator 18 | 19 | @Composable 20 | fun App(isBrowser: Boolean = false) { 21 | MaterialTheme { 22 | Navigator(HomeScreen(isBrowser)) { navigator -> 23 | Scaffold( 24 | topBar = { 25 | TopAppBar( 26 | title = { Text("Compass Demo") }, 27 | navigationIcon = { 28 | if (navigator.canPop) { 29 | IconButton(onClick = { navigator.pop() }) { 30 | Icon( 31 | imageVector = Icons.AutoMirrored.Filled.ArrowBack, 32 | contentDescription = "Back", 33 | ) 34 | } 35 | } 36 | }, 37 | ) 38 | } 39 | ) { innerPadding -> 40 | Box(Modifier.padding(innerPadding)) { 41 | CurrentScreen() 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/parameter/MapboxBoundingBox.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.parameter 2 | 3 | import dev.drewhamilton.poko.Poko 4 | import dev.jordond.compass.tools.web.parameter.QueryParamValue 5 | 6 | /** 7 | * A bounding box to limit search results to a specific area. 8 | * 9 | * See the [Mapbox API documentation](https://docs.mapbox.com/api/search/geocoding-v6/#forward-geocoding-with-structured-input) for more information. 10 | * 11 | * @property minLongitude The minimum longitude of the bounding box. 12 | * @property minLatitude The minimum latitude of the bounding box. 13 | * @property maxLongitude The maximum longitude of the bounding box. 14 | * @property maxLatitude The maximum latitude of the bounding box. 15 | */ 16 | @Poko 17 | @Suppress("MemberVisibilityCanBePrivate") 18 | public class MapboxBoundingBox( 19 | public val minLongitude: Double, 20 | public val minLatitude: Double, 21 | public val maxLongitude: Double, 22 | public val maxLatitude: Double, 23 | ) : QueryParamValue { 24 | 25 | public override val value: String = "$minLongitude,$minLatitude,$maxLongitude,$maxLatitude" 26 | 27 | /** 28 | * Create a copy of [MapboxBoundingBox] with the given or current values. 29 | * 30 | * @param minLongitude The minimum longitude of the bounding box. 31 | * @param minLatitude The minimum latitude of the bounding box. 32 | * @param maxLongitude The maximum longitude of the bounding box. 33 | * @param maxLatitude The maximum latitude of the bounding box. 34 | */ 35 | public fun copy( 36 | minLongitude: Double = this.minLongitude, 37 | minLatitude: Double = this.minLatitude, 38 | maxLongitude: Double = this.maxLongitude, 39 | maxLatitude: Double = this.maxLatitude, 40 | ): MapboxBoundingBox = MapboxBoundingBox(minLongitude, minLatitude, maxLongitude, maxLatitude) 41 | } -------------------------------------------------------------------------------- /compass-permissions-mobile/src/androidMain/kotlin/dev/jordond/compass/permissions/mobile/internal/activity/ActivityProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions.mobile.internal.activity 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Application 5 | import android.content.Context 6 | import androidx.activity.ComponentActivity 7 | import dev.jordond.compass.permissions.mobile.internal.PermissionRequester 8 | import java.lang.ref.WeakReference 9 | 10 | internal class ActivityProvider( 11 | private val context: Context, 12 | ) { 13 | 14 | private var requester: WeakReference? = null 15 | 16 | val permissionRequester: PermissionRequester 17 | get() = requester?.get() ?: throw IllegalStateException("PermissionRequester is not available") 18 | 19 | private val lifecycleObserver = createActivityLifecycleObserver { activity -> 20 | this.requester = WeakReference(PermissionRequester(activity)) 21 | } 22 | 23 | init { 24 | val application = context.applicationContext as? Application 25 | application?.registerActivityLifecycleCallbacks(lifecycleObserver) ?: run { 26 | val activity = context.applicationContext as? ComponentActivity 27 | if (activity != null) { 28 | this.requester = WeakReference(PermissionRequester(activity)) 29 | } 30 | } 31 | } 32 | 33 | companion object { 34 | 35 | @SuppressLint("StaticFieldLeak") 36 | private var instance: ActivityProvider? = null 37 | 38 | fun create(context: Context): ActivityProvider { 39 | if (instance == null) { 40 | instance = ActivityProvider(context) 41 | } 42 | 43 | return instance!! 44 | } 45 | 46 | fun getInstance(): ActivityProvider = instance 47 | ?: throw IllegalStateException("ActivityProvider has not been initialized") 48 | } 49 | } -------------------------------------------------------------------------------- /docs/geolocation/overview.md: -------------------------------------------------------------------------------- 1 | # 📍 Overview 2 | 3 | Geolocation includes the following features: 4 | 5 | * Track a user's location 6 | * Get the current location 7 | * Customize the accuracy of the tracking 8 | * Android and iOS support, using built-in services. 9 | * Built-in Location permission handling 10 | 11 | {% hint style="info" %} 12 | Built-in services are used for Android and iOS. That means that there is restrictions to its usage. 13 | {% endhint %} 14 | 15 | ## Quickstart 16 | 17 | Include the required dependencies for your targets, see [add-dependencies.md](../setup/add-dependencies.md "mention"). 18 | 19 | #### Create the Geolocator 20 | 21 | Creating a geolocator depends on your platform, but in the simplest use-case of an Android & iOS only targets. You can do something like this: 22 | 23 | ```kotlin 24 | val geolocator: Geolocator = Geolocator.mobile() 25 | ``` 26 | 27 | #### Get current location 28 | 29 | Getting the current location is simple, just call: 30 | 31 | ```kotlin 32 | val result: GeolocatorResult = geolocator.current() 33 | // Handle the result: 34 | 35 | when (result) { 36 | is GeolocatorResult.Success -> { 37 | // Do something with result.location 38 | } 39 | is GeolocatorResult.Error -> when(result) { 40 | is GeolocatorResult.NotSupported -> TODO() 41 | is GeolocatorResult.NotFound -> TODO() 42 | is GeolocatorResult.PermissionError -> TODO() 43 | is GeolocatorResult.GeolocationFailed -> TODO() 44 | } 45 | } 46 | ``` 47 | 48 | Or if you only care about the result: 49 | 50 | ```kotlin 51 | val location: Location? = geolocator.currentOrNull() 52 | ``` 53 | 54 | {% hint style="info" %} 55 | Compass will handle asking the user for while-in-use location permissions. If they deny the permission then the result will be `PermissionError.` If the user has denied the permission permanently, you will need to prompt the user to open the settings. 56 | {% endhint %} 57 | 58 | -------------------------------------------------------------------------------- /.github/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CWD="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 4 | PARENT="$(cd "$CWD"/.. >/dev/null 2>&1 && pwd)" 5 | 6 | set -e 7 | 8 | SEMVER_REG="([[:digit:]]+(\.[[:digit:]]+)+)" 9 | 10 | README_FILE="$PARENT/README.md" 11 | VERSION_FILE="$PARENT/gradle/libs.versions.toml" 12 | 13 | NEW_VERSION="$ORG_GRADLE_PROJECT_VERSION_NAME" 14 | if [ -z "$NEW_VERSION" ]; then 15 | NEW_VERSION="$1" 16 | if [ -n "$NEW_VERSION" ]; then 17 | echo "Update README with version: '$NEW_VERSION'" 18 | 19 | if [[ "$OSTYPE" == "darwin"* ]]; then 20 | # Update artifact versions in README.md 21 | sed -i '' -E "s/\:$SEMVER_REG\"\)/\:$NEW_VERSION\"\)/" "$README_FILE" 22 | 23 | # Update version catalog in README.md 24 | sed -i '' -E "s/compass = \"$SEMVER_REG\"/compass = \"$NEW_VERSION\"/" "$README_FILE" 25 | else 26 | sed -i -E "s/\:$SEMVER_REG\"/\:$NEW_VERSION\"/g" "$README_FILE" 27 | sed -i -E "s/compass = \"$SEMVER_REG\"/compass = \"$NEW_VERSION\"/g" "$README_FILE" 28 | fi 29 | fi 30 | fi 31 | 32 | # Update Kotlin badge in README.md 33 | LIBS_KOTLIN_VERSION=$(grep -m 1 "^kotlin = " "$VERSION_FILE" | cut -d= -f2 | tr -d ' "' | tr -d '\n') 34 | if [ -z "$LIBS_KOTLIN_VERSION" ]; then 35 | echo "Unable to find Kotlin version in '$VERSION_FILE'" 36 | else 37 | echo "Updating Kotlin version: '$LIBS_KOTLIN_VERSION'" 38 | sed -i '' -E "s/kotlin-v$SEMVER_REG/kotlin-v$LIBS_KOTLIN_VERSION/" "$README_FILE" 39 | fi 40 | 41 | # Update Compose Multiplatform badge in README.md 42 | LIBS_COMPOSE_VERSION=$(grep "compose-multiplatform = " "$VERSION_FILE" | cut -d= -f2 | tr -d ' "') 43 | if [ -z "$LIBS_COMPOSE_VERSION" ]; then 44 | echo "Unable to find Compose Multiplatform version in '$VERSION_FILE'" 45 | else 46 | echo "Updating Compose version: '$LIBS_COMPOSE_VERSION'" 47 | sed -i '' -E "s/Compose%20Multiplatform-v$SEMVER_REG/Compose%20Multiplatform-v$LIBS_COMPOSE_VERSION/" "$README_FILE" 48 | fi 49 | -------------------------------------------------------------------------------- /demo/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 18 | 22 | 26 | 27 | 28 | 29 | 36 | -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/MapboxForwardEndpoint.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.geocoder.web.mapbox.internal.GeocodeResponse 5 | import dev.jordond.compass.geocoder.web.mapbox.internal.toCoordinates 6 | import dev.jordond.compass.geocoder.web.parameter.MapboxParameters 7 | import dev.jordond.compass.geocoder.web.parameter.MapboxParametersBuilder 8 | import dev.jordond.compass.geocoder.web.parameter.mapboxParameters 9 | import io.ktor.client.call.body 10 | import io.ktor.client.statement.HttpResponse 11 | import io.ktor.http.encodeURLQueryComponent 12 | 13 | /** 14 | * A [ForwardEndpoint] that uses the Mapbox Geocoding API. 15 | * 16 | * @property apiKey The API key to use for the Mapbox Geocoding API. 17 | * @property parameters The parameters to use for the Mapbox Geocoding API. 18 | */ 19 | public class MapboxForwardEndpoint( 20 | private val apiKey: String, 21 | private val parameters: MapboxParameters = MapboxParameters(), 22 | ) : ForwardEndpoint { 23 | 24 | /** 25 | * Creates a new [MapboxForwardEndpoint] with the given API key. 26 | * 27 | * @param apiKey The API key to use for the Mapbox Geocoding API. 28 | * @param block A block to configure the parameters for the Mapbox Geocoding API. 29 | */ 30 | public constructor( 31 | apiKey: String, 32 | block: MapboxParametersBuilder.() -> Unit, 33 | ) : this(apiKey, mapboxParameters(block)) 34 | 35 | override fun url(param: String): String { 36 | val encodedQuery = param.encodeURLQueryComponent() 37 | return MapboxPlatformGeocoder.forwardUrl(encodedQuery, apiKey, parameters) 38 | } 39 | 40 | override suspend fun mapResponse(response: HttpResponse): List { 41 | val forwardResponse = response.body() 42 | return forwardResponse.toCoordinates() 43 | } 44 | } -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/MapboxReverseEndpoint.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import dev.jordond.compass.geocoder.web.mapbox.internal.GeocodeResponse 6 | import dev.jordond.compass.geocoder.web.mapbox.internal.toPlaces 7 | import dev.jordond.compass.geocoder.web.parameter.MapboxParameters 8 | import dev.jordond.compass.geocoder.web.parameter.MapboxParametersBuilder 9 | import dev.jordond.compass.geocoder.web.parameter.mapboxParameters 10 | import io.ktor.client.call.body 11 | import io.ktor.client.statement.HttpResponse 12 | 13 | /** 14 | * A [ReverseEndpoint] that uses the Mapbox Geocoding API. 15 | * 16 | * @property apiKey The API key to use for the Mapbox Geocoding API. 17 | * @property parameters The parameters to use for the Mapbox Geocoding API. 18 | */ 19 | public class MapboxReverseEndpoint( 20 | private val apiKey: String, 21 | private val parameters: MapboxParameters = MapboxParameters(), 22 | ) : ReverseEndpoint { 23 | 24 | /** 25 | * Creates a new [MapboxReverseEndpoint] with the given API key. 26 | * 27 | * @param apiKey The API key to use for the Mapbox Geocoding API. 28 | * @param block A block to configure the parameters for the Mapbox Geocoding API. 29 | */ 30 | public constructor( 31 | apiKey: String, 32 | block: MapboxParametersBuilder.() -> Unit, 33 | ) : this(apiKey, mapboxParameters(block)) 34 | 35 | override fun url(param: Coordinates): String { 36 | val (latitude, longitude) = param.run { latitude to longitude } 37 | return MapboxPlatformGeocoder.reverseUrl(latitude, longitude, apiKey, parameters) 38 | } 39 | 40 | override suspend fun mapResponse(response: HttpResponse): List { 41 | val forwardResponse = response.body() 42 | return forwardResponse.toPlaces() 43 | } 44 | } -------------------------------------------------------------------------------- /compass-geolocation/src/commonMain/kotlin/dev/jordond/compass/geolocation/Extensions.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation 2 | 3 | import dev.jordond.compass.Location 4 | import dev.jordond.compass.geolocation.internal.DefaultGeolocator 5 | 6 | /** 7 | * Gets the current location or null if the location is not available. 8 | * 9 | * @receiver The [Geolocator] to get the location from. 10 | * @return The current location or null if the location is not available. 11 | */ 12 | public suspend fun Geolocator.currentLocationOrNull(): Location? = current().getOrNull() 13 | 14 | /** 15 | * Check if the current [Geolocator] has the permissions required for geolocation. 16 | * 17 | * This will always return `true` if the [Locator] used by the [Geolocator] is not a [PermissionLocator]. 18 | * 19 | * @receiver The [Geolocator] to check the permissions of. 20 | * @return True if the [Geolocator] has the permissions required for geolocation, false otherwise. 21 | */ 22 | public fun Geolocator.hasPermission(): Boolean { 23 | val locator = (this as? DefaultGeolocator)?.locator 24 | return (locator as? PermissionLocator)?.hasPermission() ?: true 25 | } 26 | 27 | /** 28 | * Check if the current [GeolocatorResult] is a [GeolocatorResult.PermissionDenied]. 29 | * 30 | * @receiver The [GeolocatorResult] to check. 31 | * @return True if the [GeolocatorResult] is a [GeolocatorResult.PermissionDenied], false otherwise. 32 | */ 33 | public fun GeolocatorResult.Error.isPermissionDenied(): Boolean { 34 | return this is GeolocatorResult.PermissionDenied 35 | } 36 | 37 | /** 38 | * Check if the current [GeolocatorResult] is a [GeolocatorResult.PermissionDenied]. 39 | * 40 | * @receiver The [GeolocatorResult] to check. 41 | * @return True if the [GeolocatorResult] is a [GeolocatorResult.PermissionDenied], false otherwise. 42 | */ 43 | public fun GeolocatorResult.Error.isPermissionDeniedForever(): Boolean { 44 | return (this as? GeolocatorResult.PermissionDenied)?.forever ?: false 45 | } -------------------------------------------------------------------------------- /compass-autocomplete-web/src/commonMain/kotlin/dev/jordond/compass/autocomplete/web/HttpAutocompleteService.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.autocomplete.web 2 | 3 | import dev.jordond.compass.autocomplete.AutocompleteService 4 | import dev.jordond.compass.tools.web.HttpApiEndpoint 5 | import dev.jordond.compass.tools.web.makeRequest 6 | import io.ktor.client.HttpClient 7 | import kotlinx.serialization.json.Json 8 | 9 | /** 10 | * Represents an HTTP endpoint that can be used to search for autocomplete suggestions. 11 | * 12 | * @param T The type of the autocomplete suggestions. 13 | */ 14 | public typealias SearchEndpoint = HttpApiEndpoint> 15 | 16 | /** 17 | * Represents an autocomplete service that uses an HTTP endpoint to search for suggestions. 18 | * 19 | * @param T The type of the autocomplete suggestions. 20 | */ 21 | public interface HttpAutocompleteService : AutocompleteService { 22 | 23 | public companion object 24 | } 25 | 26 | /** 27 | * Creates an [HttpAutocompleteService] that uses the specified [searchEndpoint] to search for 28 | * autocomplete suggestions. 29 | * 30 | * @param T The type of the autocomplete suggestions. 31 | * @param searchEndpoint The HTTP endpoint to use for searching. 32 | * @param json The JSON serializer to use. 33 | * @param client The HTTP client to use. 34 | * @return An [HttpAutocompleteService] that uses the specified [searchEndpoint] to search for 35 | * autocomplete suggestions. 36 | */ 37 | public fun HttpAutocompleteService( 38 | searchEndpoint: SearchEndpoint, 39 | json: Json = HttpApiEndpoint.json(), 40 | client: HttpClient = HttpApiEndpoint.httpClient(json), 41 | ): HttpAutocompleteService = object : HttpAutocompleteService { 42 | 43 | override fun isAvailable(): Boolean = true 44 | 45 | override suspend fun search(query: String): List { 46 | val url = searchEndpoint.url(query) 47 | return client.makeRequest(url, searchEndpoint::mapResponse) 48 | } 49 | } -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/mapbox/internal/GeocodeResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.mapbox.internal 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.InternalCompassApi 5 | import dev.jordond.compass.Place 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | 9 | @InternalCompassApi 10 | @Serializable 11 | public data class GeocodeResponse( 12 | @SerialName("features") 13 | val features: List, 14 | ) 15 | 16 | internal fun GeocodeResponse.toCoordinates(): List { 17 | return features.mapNotNull { response -> 18 | val coordinates = response.properties?.coordinates ?: return@mapNotNull null 19 | Coordinates( 20 | latitude = coordinates.latitude, 21 | longitude = coordinates.longitude, 22 | ) 23 | } 24 | } 25 | 26 | @InternalCompassApi 27 | public fun GeocodeResponse.toPlaces(): List { 28 | return features.mapNotNull { response -> 29 | val data = response.properties?.context ?: return@mapNotNull null 30 | val coordinates = response.properties.coordinates?.run { 31 | Coordinates(latitude = latitude, longitude = longitude) 32 | } ?: return@mapNotNull null 33 | 34 | Place( 35 | coordinates = coordinates, 36 | name = response.properties.name, 37 | street = data.street?.name, 38 | isoCountryCode = data.country?.countryCode, 39 | country = data.country?.name, 40 | postalCode = data.postcode?.name, 41 | administrativeArea = data.region?.name, 42 | subAdministrativeArea = data.district?.name, 43 | locality = data.locality?.name ?: data.place?.name, 44 | subLocality = data.neighborhood?.name, 45 | thoroughfare = data.street?.name, 46 | subThoroughfare = null, 47 | ) 48 | } 49 | } -------------------------------------------------------------------------------- /compass-geocoder-web-opencage/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/OpenCageForwardEndpoint.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.geocoder.web.opencage.internal.GeocodeResponse 5 | import dev.jordond.compass.geocoder.web.opencage.internal.toCoordinates 6 | import dev.jordond.compass.geocoder.web.parameter.OpenCageParameters 7 | import dev.jordond.compass.geocoder.web.parameter.OpenCageParametersBuilder 8 | import dev.jordond.compass.geocoder.web.parameter.openCageParameters 9 | import io.ktor.client.call.body 10 | import io.ktor.client.statement.HttpResponse 11 | import io.ktor.http.encodeURLQueryComponent 12 | 13 | /** 14 | * A [ForwardEndpoint] that uses the OpenCage Geocoding API. 15 | * 16 | * @property apiKey The API key to use for the OpenCage Geocoding API. 17 | * @property parameters The parameters to use for the OpenCage Geocoding API. 18 | */ 19 | public class OpenCageForwardEndpoint( 20 | private val apiKey: String, 21 | private val parameters: OpenCageParameters = OpenCageParameters(), 22 | ) : ForwardEndpoint { 23 | 24 | /** 25 | * Creates a new [OpenCageForwardEndpoint] with the given API key. 26 | * 27 | * @param apiKey The API key to use for the OpenCage Geocoding API. 28 | * @param block A block to configure the parameters for the OpenCage Geocoding API. 29 | */ 30 | public constructor( 31 | apiKey: String, 32 | block: OpenCageParametersBuilder.() -> Unit, 33 | ) : this(apiKey, openCageParameters(block)) 34 | 35 | override fun url(param: String): String { 36 | val encodedQuery = param.encodeURLQueryComponent() 37 | return OpenCagePlatformGeocoder.forwardUrl(encodedQuery, apiKey, parameters) 38 | } 39 | 40 | override suspend fun mapResponse(response: HttpResponse): List { 41 | val result = response.body() 42 | return result.toCoordinates() 43 | } 44 | } -------------------------------------------------------------------------------- /compass-geocoder-web-opencage/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/OpenCageReverseEndpoint.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import dev.jordond.compass.geocoder.web.opencage.internal.GeocodeResponse 6 | import dev.jordond.compass.geocoder.web.opencage.internal.toPlaces 7 | import dev.jordond.compass.geocoder.web.parameter.OpenCageParameters 8 | import dev.jordond.compass.geocoder.web.parameter.OpenCageParametersBuilder 9 | import dev.jordond.compass.geocoder.web.parameter.openCageParameters 10 | import io.ktor.client.call.body 11 | import io.ktor.client.statement.HttpResponse 12 | 13 | /** 14 | * A [ReverseEndpoint] that uses the OpenCage Geocoding API. 15 | * 16 | * @property apiKey The API key to use for the OpenCage Geocoding API. 17 | * @property parameters The parameters to use for the OpenCage Geocoding API. 18 | */ 19 | public class OpenCageReverseEndpoint( 20 | private val apiKey: String, 21 | private val parameters: OpenCageParameters = OpenCageParameters(), 22 | ) : ReverseEndpoint { 23 | 24 | /** 25 | * Creates a new [OpenCageReverseEndpoint] with the given API key. 26 | * 27 | * @param apiKey The API key to use for the OpenCage Geocoding API. 28 | * @param block A block to configure the parameters for the OpenCage Geocoding API. 29 | */ 30 | public constructor( 31 | apiKey: String, 32 | block: OpenCageParametersBuilder.() -> Unit, 33 | ) : this(apiKey, openCageParameters(block)) 34 | 35 | override fun url(param: Coordinates): String { 36 | val (latitude, longitude) = param.run { latitude to longitude } 37 | return OpenCagePlatformGeocoder.reverseUrl(latitude, longitude, apiKey, parameters) 38 | } 39 | 40 | override suspend fun mapResponse(response: HttpResponse): List { 41 | val result = response.body() 42 | return result.toPlaces() 43 | } 44 | } -------------------------------------------------------------------------------- /compass-geocoder-web-template/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/TemplateForwardEndpoint.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.geocoder.web.parameter.TemplateParameters 5 | import dev.jordond.compass.geocoder.web.parameter.TemplateParametersBuilder 6 | import dev.jordond.compass.geocoder.web.parameter.templateParameters 7 | import dev.jordond.compass.geocoder.web.template.internal.GeocodeResponse 8 | import dev.jordond.compass.geocoder.web.template.internal.toCoordinates 9 | import io.ktor.client.call.body 10 | import io.ktor.client.statement.HttpResponse 11 | import io.ktor.http.encodeURLQueryComponent 12 | 13 | /** 14 | * A [ForwardEndpoint] that uses the TEMPLATE Geocoding API. 15 | * 16 | * @property apiKey The API key to use for the TEMPLATE Geocoding API. 17 | * @property parameters The parameters to use for the TEMPLATE Geocoding API. 18 | */ 19 | public class TemplateForwardEndpoint( 20 | private val apiKey: String, 21 | private val parameters: TemplateParameters = TemplateParameters(), 22 | ) : ForwardEndpoint { 23 | 24 | /** 25 | * Creates a new [TemplateForwardEndpoint] with the given API key. 26 | * 27 | * @param apiKey The API key to use for the TEMPLATE Geocoding API. 28 | * @param block A block to configure the parameters for the TEMPLATE Geocoding API. 29 | */ 30 | public constructor( 31 | apiKey: String, 32 | block: TemplateParametersBuilder.() -> Unit, 33 | ) : this(apiKey, templateParameters(block)) 34 | 35 | override fun url(param: String): String { 36 | val encodedQuery = param.encodeURLQueryComponent() 37 | return TemplatePlatformGeocoder.forwardUrl(encodedQuery, apiKey, parameters) 38 | } 39 | 40 | override suspend fun mapResponse(response: HttpResponse): List { 41 | val result = response.body() 42 | return result.toCoordinates() 43 | } 44 | } -------------------------------------------------------------------------------- /compass-geocoder-web-template/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/TemplateReverseEndpoint.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import dev.jordond.compass.geocoder.web.parameter.TemplateParameters 6 | import dev.jordond.compass.geocoder.web.parameter.TemplateParametersBuilder 7 | import dev.jordond.compass.geocoder.web.parameter.templateParameters 8 | import dev.jordond.compass.geocoder.web.template.internal.GeocodeResponse 9 | import dev.jordond.compass.geocoder.web.template.internal.toPlaces 10 | import io.ktor.client.call.body 11 | import io.ktor.client.statement.HttpResponse 12 | 13 | /** 14 | * A [ReverseEndpoint] that uses the TEMPLATE Geocoding API. 15 | * 16 | * @property apiKey The API key to use for the TEMPLATE Geocoding API. 17 | * @property parameters The parameters to use for the TEMPLATE Geocoding API. 18 | */ 19 | public class TemplateReverseEndpoint( 20 | private val apiKey: String, 21 | private val parameters: TemplateParameters = TemplateParameters(), 22 | ) : ReverseEndpoint { 23 | 24 | /** 25 | * Creates a new [TemplateReverseEndpoint] with the given API key. 26 | * 27 | * @param apiKey The API key to use for the TEMPLATE Geocoding API. 28 | * @param block A block to configure the parameters for the TEMPLATE Geocoding API. 29 | */ 30 | public constructor( 31 | apiKey: String, 32 | block: TemplateParametersBuilder.() -> Unit, 33 | ) : this(apiKey, templateParameters(block)) 34 | 35 | override fun url(param: Coordinates): String { 36 | val (latitude, longitude) = param.run { latitude to longitude } 37 | return TemplatePlatformGeocoder.reverseUrl(latitude, longitude, apiKey, parameters) 38 | } 39 | 40 | override suspend fun mapResponse(response: HttpResponse): List { 41 | val result = response.body() 42 | return result.toPlaces() 43 | } 44 | } -------------------------------------------------------------------------------- /compass-permissions-mobile/src/iosMain/kotlin/dev/jordond/compass/permissions/PermissionController.ios.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.permissions 2 | 3 | import dev.jordond.compass.Priority 4 | import dev.jordond.compass.permissions.mobile.internal.LocationPermissionManagerDelegate 5 | import dev.jordond.compass.permissions.mobile.internal.toPermissionState 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.update 8 | import kotlin.coroutines.resume 9 | import kotlin.coroutines.suspendCoroutine 10 | 11 | internal actual fun createPermissionController(): LocationPermissionController { 12 | return IosLocationPermissionController(LocationPermissionManagerDelegate()) 13 | } 14 | 15 | internal class IosLocationPermissionController( 16 | private val locationDelegate: LocationPermissionManagerDelegate, 17 | ) : LocationPermissionController { 18 | 19 | private val _permissionsStatus = MutableStateFlow( 20 | value = locationDelegate.currentPermissionStatus().toPermissionState, 21 | ) 22 | 23 | init { 24 | locationDelegate.monitorPermission { permissionStatus -> 25 | _permissionsStatus.update { permissionStatus.toPermissionState } 26 | } 27 | } 28 | 29 | override fun hasPermission(): Boolean { 30 | return _permissionsStatus.value == PermissionState.Granted 31 | } 32 | 33 | override suspend fun requirePermissionFor(priority: Priority): PermissionState { 34 | val currentState = locationDelegate.currentPermissionStatus().toPermissionState 35 | return when { 36 | currentState == PermissionState.Granted || 37 | currentState == PermissionState.DeniedForever -> currentState 38 | else -> { 39 | val result = suspendCoroutine { continuation -> 40 | locationDelegate.requestPermission { continuation.resume(it) } 41 | } 42 | 43 | result.toPermissionState 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /docs/geolocation/android-ios.md: -------------------------------------------------------------------------------- 1 | # 📱 Android / iOS 2 | 3 | Compass supports Geolocation on Android and iOS by using the built in location services. 4 | 5 | To use the Geolocator, it requires the user to provide permission. This is handled automatically when you start tracking or attempt to get the current location. 6 | 7 | Make sure you read the [Android](https://developer.android.com/develop/sensors-and-location/location) and [iOS](https://developer.apple.com/documentation/corelocation) documentation to fully understand how the location services work. 8 | 9 | {% hint style="info" %} 10 | If your project supports both Mobile and other targets, you need to configure your setup to provide a `Geolocator` for each platform. Check out [mixed-platforms.md](../usage/mixed-platforms.md "mention")for more information. 11 | {% endhint %} 12 | 13 | ## Usage 14 | 15 | Follow these steps to create a `Geolocator` 16 | 17 | ### Create Locator 18 | 19 | Geolocator is powered by the `Locator` object: 20 | 21 | ```kotlin 22 | val locator: Locator = MobileLocator() 23 | val locator: Locator = Locator.mobile() 24 | ``` 25 | 26 | ### Create Geolocator 27 | 28 | Then you can use that to create the `Geolocator` object: 29 | 30 | ```kotlin 31 | val geolocator: Geolocator = Geolocator(locator) 32 | ``` 33 | 34 | Or you can use an extension function to skip the `Locator` step: 35 | 36 | ```kotlin 37 | val geolocator: Geolocator = Geolocator() 38 | val geolocator: Geolocator = MobileGeolocator() 39 | val geolocator: Geolocator = Geolocator.mobile() 40 | ``` 41 | 42 | ## Permissions 43 | 44 | The user needs to grant permission in order to get location data. 45 | 46 | On Android there are no further steps required, but on iOS you need to edit your `info.plist`, see [android-ios.md](../setup/android-ios.md "mention") to learn more. 47 | 48 | When you attempt to access the location, Compass will automatically ask the user for permission. 49 | 50 | ## Get location 51 | 52 | Now you can follow the steps in [geolocator.md](geolocator.md "mention") 53 | -------------------------------------------------------------------------------- /compass-autocomplete-mobile/api/compass-autocomplete-mobile.api: -------------------------------------------------------------------------------- 1 | public final class dev/jordond/compass/autocomplete/MobileAutocompleteKt { 2 | public static final fun Autocomplete (Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/jordond/compass/autocomplete/Autocomplete; 3 | public static synthetic fun Autocomplete$default (Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/Autocomplete; 4 | public static final fun MobileAutocomplete (Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/jordond/compass/autocomplete/Autocomplete; 5 | public static synthetic fun MobileAutocomplete$default (Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/Autocomplete; 6 | public static final fun mobile (Ldev/jordond/compass/autocomplete/Autocomplete$Companion;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/jordond/compass/autocomplete/Autocomplete; 7 | public static synthetic fun mobile$default (Ldev/jordond/compass/autocomplete/Autocomplete$Companion;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/Autocomplete; 8 | } 9 | 10 | public final class dev/jordond/compass/autocomplete/mobile/MobileAutocompleteServiceKt { 11 | public static final fun MobileAutocompleteService (Ljava/lang/String;Ldev/jordond/compass/geocoder/mobile/MobilePlatformGeocoder;)Ldev/jordond/compass/autocomplete/AutocompleteService; 12 | public static synthetic fun MobileAutocompleteService$default (Ljava/lang/String;Ldev/jordond/compass/geocoder/mobile/MobilePlatformGeocoder;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/AutocompleteService; 13 | public static final fun mobile (Ldev/jordond/compass/autocomplete/AutocompleteService$Companion;)Ldev/jordond/compass/autocomplete/AutocompleteService; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /compass-geocoder-web-googlemaps/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/GoogleMapsReverseEndpoint.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.Place 5 | import dev.jordond.compass.geocoder.web.google.internal.GeocodeResponse 6 | import dev.jordond.compass.geocoder.web.google.internal.resultsOrThrow 7 | import dev.jordond.compass.geocoder.web.google.internal.toPlaces 8 | import dev.jordond.compass.geocoder.web.parameter.GoogleMapsParameters 9 | import dev.jordond.compass.geocoder.web.parameter.GoogleMapsParametersBuilder 10 | import dev.jordond.compass.geocoder.web.parameter.googleMapsParameters 11 | import io.ktor.client.call.body 12 | import io.ktor.client.statement.HttpResponse 13 | 14 | /** 15 | * A [ReverseEndpoint] that uses the Google Maps Geocoding API. 16 | * 17 | * @property apiKey The API key to use for the Google Maps Geocoding API. 18 | * @property parameters The parameters to use for the Google Maps Geocoding API. 19 | */ 20 | public class GoogleMapsReverseEndpoint( 21 | private val apiKey: String, 22 | private val parameters: GoogleMapsParameters = GoogleMapsParameters(), 23 | ) : ReverseEndpoint { 24 | 25 | /** 26 | * Creates a new [GoogleMapsReverseEndpoint] with the given API key. 27 | * 28 | * @param apiKey The API key to use for the Google Maps Geocoding API. 29 | * @param block A block to configure the parameters for the Google Maps Geocoding API. 30 | */ 31 | public constructor( 32 | apiKey: String, 33 | block: GoogleMapsParametersBuilder.() -> Unit, 34 | ) : this(apiKey, googleMapsParameters(block)) 35 | 36 | override fun url(param: Coordinates): String { 37 | val (latitude, longitude) = param.run { latitude to longitude } 38 | return GoogleMapsPlatformGeocoder.reverseUrl(latitude, longitude, apiKey, parameters) 39 | } 40 | 41 | override suspend fun mapResponse(response: HttpResponse): List { 42 | val result = response.body().resultsOrThrow() 43 | return result.toPlaces() 44 | } 45 | } -------------------------------------------------------------------------------- /compass-geolocation-mobile/src/commonMain/kotlin/dev/jordond/compass/geolocation/mobile/MobileLocator.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.mobile 2 | 3 | import dev.jordond.compass.geolocation.Locator 4 | import dev.jordond.compass.geolocation.PermissionLocator 5 | import dev.jordond.compass.permissions.LocationPermissionController 6 | import dev.jordond.compass.permissions.mobile 7 | 8 | /** 9 | * A locator that provides geolocation services on Android and iOS. 10 | */ 11 | public interface MobileLocator : PermissionLocator 12 | 13 | /** 14 | * Create an Android/iOS [MobileLocator] instance for geolocation operations. 15 | * 16 | * Make sure you read the [Android documentation](https://developer.android.com/develop/sensors-and-location/location) 17 | * as well as the [iOS documentation](https://developer.apple.com/documentation/corelocation) 18 | * to understand the permissions and accuracy. 19 | * 20 | * @param permissionController The permission controller to use for handling location permissions. 21 | * @return A new Android/iOS [MobileLocator] instance. 22 | */ 23 | public fun MobileLocator( 24 | permissionController: LocationPermissionController = LocationPermissionController.mobile(), 25 | ): MobileLocator = createLocator(permissionController) 26 | 27 | /** 28 | * Create an Android/iOS [MobileLocator] instance for geolocation operations. 29 | * 30 | * Make sure you read the [Android documentation](https://developer.android.com/develop/sensors-and-location/location) 31 | * as well as the [iOS documentation](https://developer.apple.com/documentation/corelocation) 32 | * to understand the permissions and accuracy. 33 | * 34 | * @param permissionController The permission controller to use for handling location permissions. 35 | * @return A new Android/iOS [MobileLocator] instance. 36 | */ 37 | public fun Locator.Companion.mobile( 38 | permissionController: LocationPermissionController = LocationPermissionController.mobile(), 39 | ): MobileLocator = MobileLocator(permissionController) 40 | 41 | internal expect fun createLocator(permissionController: LocationPermissionController): MobileLocator -------------------------------------------------------------------------------- /compass-geocoder-web-googlemaps/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/GoogleMapsForwardEndpoint.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web 2 | 3 | import dev.jordond.compass.Coordinates 4 | import dev.jordond.compass.geocoder.web.google.internal.GeocodeResponse 5 | import dev.jordond.compass.geocoder.web.google.internal.resultsOrThrow 6 | import dev.jordond.compass.geocoder.web.google.internal.toCoordinates 7 | import dev.jordond.compass.geocoder.web.parameter.GoogleMapsParameters 8 | import dev.jordond.compass.geocoder.web.parameter.GoogleMapsParametersBuilder 9 | import dev.jordond.compass.geocoder.web.parameter.googleMapsParameters 10 | import io.ktor.client.call.body 11 | import io.ktor.client.statement.HttpResponse 12 | import io.ktor.http.encodeURLQueryComponent 13 | 14 | /** 15 | * A [ForwardEndpoint] that uses the Google Maps Geocoding API. 16 | * 17 | * @property apiKey The API key to use for the Google Maps Geocoding API. 18 | * @property parameters The parameters to use for the Google Maps Geocoding API. 19 | */ 20 | public class GoogleMapsForwardEndpoint( 21 | private val apiKey: String, 22 | private val parameters: GoogleMapsParameters = GoogleMapsParameters(), 23 | ) : ForwardEndpoint { 24 | 25 | /** 26 | * Creates a new [GoogleMapsForwardEndpoint] with the given API key. 27 | * 28 | * @param apiKey The API key to use for the Google Maps Geocoding API. 29 | * @param block A block to configure the parameters for the Google Maps Geocoding API. 30 | */ 31 | public constructor( 32 | apiKey: String, 33 | block: GoogleMapsParametersBuilder.() -> Unit, 34 | ) : this(apiKey, googleMapsParameters(block)) 35 | 36 | override fun url(param: String): String { 37 | val encodedQuery = param.encodeURLQueryComponent() 38 | return GoogleMapsPlatformGeocoder.forwardUrl(encodedQuery, apiKey, parameters) 39 | } 40 | 41 | override suspend fun mapResponse(response: HttpResponse): List { 42 | val result = response.body().resultsOrThrow() 43 | return result.toCoordinates() 44 | } 45 | } -------------------------------------------------------------------------------- /compass-tools-web/src/commonMain/kotlin/dev/jordond/compass/tools/web/parameter/QueryParameters.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.tools.web.parameter 2 | 3 | import io.ktor.http.encodeURLQueryComponent 4 | 5 | /** 6 | * Represents a set of query parameters for a URL request. 7 | */ 8 | public interface QueryParameters { 9 | 10 | /** 11 | * Key-value pairs of query parameters. 12 | */ 13 | public val parameters: Map 14 | 15 | /** 16 | * Encodes the query parameters into a URL-encoded string. 17 | * 18 | * @return The URL-encoded query parameters. 19 | */ 20 | public fun encode(): String = parameters 21 | .map { (key, value) -> "$key=$value" } 22 | .joinToString("&") 23 | .encodeURLQueryComponent(encodeFull = true) 24 | 25 | /** 26 | * Convert a boolean value to an integer. 27 | */ 28 | public fun Boolean.toInt(): Int = if (this) 1 else 0 29 | } 30 | 31 | /** 32 | * Create a query parameter map from the given pairs. 33 | * 34 | * @param parameters The key-value pairs of query parameters. 35 | * @return The query parameter map. 36 | */ 37 | @Suppress("UnusedReceiverParameter") 38 | public fun QueryParameters.parametersOf( 39 | vararg parameters: Pair, 40 | ): Map = internalParametersOf(*parameters) 41 | 42 | internal fun internalParametersOf( 43 | vararg parameters: Pair, 44 | ): Map = parameters 45 | .mapNotNull { (key, value) -> 46 | value?.let { nonNullValue -> 47 | when (nonNullValue) { 48 | is QueryParamListValue<*> -> key to nonNullValue.value 49 | is QueryParamValue -> key to nonNullValue.value 50 | is List<*> -> { 51 | key to nonNullValue 52 | .map { if (it is QueryParamValue) it.value else it.toString() } 53 | .toQueryParameterValue() 54 | } 55 | else -> key to nonNullValue.toString() 56 | } 57 | } 58 | } 59 | .toMap() 60 | 61 | private fun List.toQueryParameterValue(): String = joinToString(",") -------------------------------------------------------------------------------- /compass-autocomplete/src/commonMain/kotlin/dev/jordond/compass/autocomplete/Autocomplete.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("FunctionName") 2 | 3 | package dev.jordond.compass.autocomplete 4 | 5 | import dev.jordond.compass.Place 6 | import dev.jordond.compass.autocomplete.internal.DefaultAutocomplete 7 | import kotlinx.coroutines.CoroutineDispatcher 8 | import kotlinx.coroutines.Dispatchers 9 | 10 | /** 11 | * Represents an autocomplete service. 12 | * 13 | * @param T The type of the autocomplete results. 14 | */ 15 | public interface Autocomplete { 16 | 17 | /** 18 | * The options for the autocomplete. 19 | */ 20 | public val options: AutocompleteOptions 21 | 22 | /** 23 | * Performs an autocomplete search using the provided [query]. 24 | * 25 | * @param query The query to search for. 26 | * @return The autocomplete result. 27 | */ 28 | public suspend fun search(query: String): AutocompleteResult 29 | 30 | public companion object 31 | } 32 | 33 | /** 34 | * Creates a new [Autocomplete] instance. 35 | * 36 | * @param T The type of the autocomplete results. 37 | * @param service The autocomplete service to use. 38 | * @param options The options for the autocomplete. 39 | * @param dispatcher The coroutine dispatcher to use. 40 | * @return A new [Autocomplete] instance. 41 | */ 42 | public fun Autocomplete( 43 | service: AutocompleteService, 44 | options: AutocompleteOptions = AutocompleteOptions(), 45 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 46 | ): Autocomplete = DefaultAutocomplete(service, options, dispatcher) 47 | 48 | /** 49 | * Creates a new [Autocomplete] instance for [Place]s. 50 | * 51 | * @param service The autocomplete service to use. 52 | * @param options The options for the autocomplete. 53 | * @param dispatcher The coroutine dispatcher to use. 54 | * @return A new [Autocomplete] instance. 55 | */ 56 | public fun PlaceAutocomplete( 57 | service: AutocompleteService, 58 | options: AutocompleteOptions = AutocompleteOptions(), 59 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 60 | ): Autocomplete = DefaultAutocomplete(service, options, dispatcher) -------------------------------------------------------------------------------- /compass-geocoder-web-mapbox/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/mapbox/internal/ContextResponse.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.mapbox.internal 2 | 3 | import dev.jordond.compass.InternalCompassApi 4 | import kotlinx.serialization.SerialName 5 | import kotlinx.serialization.Serializable 6 | 7 | @InternalCompassApi 8 | @Serializable 9 | public data class ContextResponse( 10 | @SerialName("address") 11 | public val address: AddressDataPoint? = null, 12 | 13 | @SerialName("street") 14 | public val street: DataPoint? = null, 15 | 16 | @SerialName("neighbourhood") 17 | public val neighborhood: DataPoint? = null, 18 | 19 | @SerialName("postcode") 20 | public val postcode: DataPoint? = null, 21 | 22 | @SerialName("locality") 23 | public val locality: DataPoint? = null, 24 | 25 | @SerialName("place") 26 | public val place: DataPoint? = null, 27 | 28 | @SerialName("district") 29 | public val district: DataPoint? = null, 30 | 31 | @SerialName("region") 32 | public val region: RegionDataPoint? = null, 33 | 34 | @SerialName("country") 35 | public val country: CountryDataPoint? = null, 36 | ) 37 | 38 | @InternalCompassApi 39 | @Serializable 40 | public data class DataPoint( 41 | @SerialName("name") 42 | public val name: String? = null, 43 | ) 44 | 45 | @InternalCompassApi 46 | @Serializable 47 | public data class AddressDataPoint( 48 | @SerialName("name") 49 | public val name: String? = null, 50 | 51 | @SerialName("street_name") 52 | public val streetName: String? = null, 53 | 54 | @SerialName("address_number") 55 | public val addressNumber: String? = null, 56 | ) 57 | 58 | @InternalCompassApi 59 | @Serializable 60 | public data class RegionDataPoint( 61 | @SerialName("name") 62 | public val name: String? = null, 63 | 64 | @SerialName("region_code") 65 | public val regionCode: String? = null, 66 | ) 67 | 68 | @InternalCompassApi 69 | @Serializable 70 | public data class CountryDataPoint( 71 | @SerialName("name") 72 | public val name: String? = null, 73 | 74 | @SerialName("country_code") 75 | public val countryCode: String? = null, 76 | ) -------------------------------------------------------------------------------- /compass-autocomplete-web/api/android/compass-autocomplete-web.api: -------------------------------------------------------------------------------- 1 | public final class dev/jordond/compass/autocomplete/HttpAutocompleteKt { 2 | public static final fun Autocomplete (Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/jordond/compass/autocomplete/Autocomplete; 3 | public static final fun Autocomplete (Ldev/jordond/compass/tools/web/HttpApiEndpoint;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/jordond/compass/autocomplete/Autocomplete; 4 | public static synthetic fun Autocomplete$default (Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/Autocomplete; 5 | public static synthetic fun Autocomplete$default (Ldev/jordond/compass/tools/web/HttpApiEndpoint;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/Autocomplete; 6 | } 7 | 8 | public abstract interface class dev/jordond/compass/autocomplete/web/HttpAutocompleteService : dev/jordond/compass/autocomplete/AutocompleteService { 9 | public static final field Companion Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService$Companion; 10 | } 11 | 12 | public final class dev/jordond/compass/autocomplete/web/HttpAutocompleteService$Companion { 13 | } 14 | 15 | public final class dev/jordond/compass/autocomplete/web/HttpAutocompleteServiceKt { 16 | public static final fun HttpAutocompleteService (Ldev/jordond/compass/tools/web/HttpApiEndpoint;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;)Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService; 17 | public static synthetic fun HttpAutocompleteService$default (Ldev/jordond/compass/tools/web/HttpApiEndpoint;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /compass-autocomplete-web/api/jvm/compass-autocomplete-web.api: -------------------------------------------------------------------------------- 1 | public final class dev/jordond/compass/autocomplete/HttpAutocompleteKt { 2 | public static final fun Autocomplete (Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/jordond/compass/autocomplete/Autocomplete; 3 | public static final fun Autocomplete (Ldev/jordond/compass/tools/web/HttpApiEndpoint;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;Lkotlinx/coroutines/CoroutineDispatcher;)Ldev/jordond/compass/autocomplete/Autocomplete; 4 | public static synthetic fun Autocomplete$default (Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/Autocomplete; 5 | public static synthetic fun Autocomplete$default (Ldev/jordond/compass/tools/web/HttpApiEndpoint;Ldev/jordond/compass/autocomplete/AutocompleteOptions;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;Lkotlinx/coroutines/CoroutineDispatcher;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/Autocomplete; 6 | } 7 | 8 | public abstract interface class dev/jordond/compass/autocomplete/web/HttpAutocompleteService : dev/jordond/compass/autocomplete/AutocompleteService { 9 | public static final field Companion Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService$Companion; 10 | } 11 | 12 | public final class dev/jordond/compass/autocomplete/web/HttpAutocompleteService$Companion { 13 | } 14 | 15 | public final class dev/jordond/compass/autocomplete/web/HttpAutocompleteServiceKt { 16 | public static final fun HttpAutocompleteService (Ldev/jordond/compass/tools/web/HttpApiEndpoint;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;)Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService; 17 | public static synthetic fun HttpAutocompleteService$default (Ldev/jordond/compass/tools/web/HttpApiEndpoint;Lkotlinx/serialization/json/Json;Lio/ktor/client/HttpClient;ILjava/lang/Object;)Ldev/jordond/compass/autocomplete/web/HttpAutocompleteService; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /compass-geolocation-browser/src/commonMain/kotlin/dev/jordond/compass/geolocation/browser/api/model/GeolocationCoordinates.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.browser.api.model 2 | 3 | /** 4 | * The GeolocationCoordinates interface represents the position and altitude of the device on Earth, 5 | * as well as the accuracy with which these properties are calculated. The geographic position 6 | * information is provided in terms of World Geodetic System coordinates (WGS84). 7 | * 8 | * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates). 9 | * 10 | * @property latitude Returns a double representing the position's latitude in decimal degrees. 11 | * @property longitude Returns a double representing the position's longitude in decimal degrees. 12 | * @property accuracy Returns a double representing the accuracy of the latitude and longitude 13 | * properties, expressed in meters. 14 | * @property altitude Returns a double representing the position's altitude in meters, relative to 15 | * nominal sea level. This value can be null if the implementation cannot provide the data. 16 | * @property altitudeAccuracy Returns a double representing the accuracy of the altitude expressed 17 | * in meters. This value can be null if the implementation cannot provide the data. 18 | * @property heading Returns a double representing the direction towards which the device is facing. 19 | * This value, specified in degrees, indicates how far off from heading true north the device is. 20 | * 0 degrees represents true north, and the direction is determined clockwise (which means that east 21 | * is 90 degrees and west is 270 degrees). If speed is 0, heading is NaN. If the device is unable to 22 | * provide heading information, this value is null. 23 | * @property speed Returns a double representing the velocity of the device in meters per second. 24 | * This value can be null. 25 | */ 26 | public external class GeolocationCoordinates { 27 | public val latitude: Double 28 | public val longitude: Double 29 | public val accuracy: Double 30 | public val altitude: Double? 31 | public val altitudeAccuracy: Double? 32 | public val heading: Double? 33 | public val speed: Double? 34 | } 35 | -------------------------------------------------------------------------------- /compass-geolocation-mobile/src/androidMain/kotlin/dev/jordond/compass/geolocation/mobile/internal/Mapper.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geolocation.mobile.internal 2 | 3 | import android.location.Location 4 | import android.os.Build.VERSION 5 | import android.os.Build.VERSION_CODES 6 | import com.google.android.gms.location.LocationRequest 7 | import dev.jordond.compass.Altitude 8 | import dev.jordond.compass.Azimuth 9 | import dev.jordond.compass.Coordinates 10 | import dev.jordond.compass.Priority 11 | import dev.jordond.compass.Speed 12 | import dev.jordond.compass.geolocation.LocationRequest as CompassLocationRequest 13 | 14 | /** 15 | * Converts a [Location] to a [dev.jordond.compass.Location]. 16 | */ 17 | internal fun Location.toModel(): dev.jordond.compass.Location = dev.jordond.compass.Location( 18 | coordinates = Coordinates(latitude = latitude, longitude = longitude), 19 | accuracy = accuracy.toDouble(), 20 | azimuth = Azimuth( 21 | degrees = bearing, 22 | accuracy = if (VERSION.SDK_INT < VERSION_CODES.O) null else bearingAccuracyDegrees, 23 | ), 24 | speed = Speed( 25 | mps = speed, 26 | accuracy = if (VERSION.SDK_INT < VERSION_CODES.O) null else speedAccuracyMetersPerSecond, 27 | ), 28 | altitude = Altitude( 29 | meters = altitude, 30 | accuracy = if (VERSION.SDK_INT < VERSION_CODES.O) null else verticalAccuracyMeters, 31 | ), 32 | timestampMillis = time, 33 | ) 34 | 35 | internal val Priority.toAndroidPriority: Int 36 | get() = when (this) { 37 | Priority.Balanced -> com.google.android.gms.location.Priority.PRIORITY_BALANCED_POWER_ACCURACY 38 | Priority.HighAccuracy -> com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY 39 | Priority.LowPower -> com.google.android.gms.location.Priority.PRIORITY_LOW_POWER 40 | Priority.Passive -> com.google.android.gms.location.Priority.PRIORITY_PASSIVE 41 | } 42 | 43 | internal fun CompassLocationRequest.toAndroidLocationRequest(): LocationRequest { 44 | return LocationRequest 45 | .Builder(priority.toAndroidPriority, interval) 46 | .setGranularity(com.google.android.gms.location.Granularity.GRANULARITY_PERMISSION_LEVEL) 47 | .build() 48 | } 49 | -------------------------------------------------------------------------------- /compass-autocomplete-web/src/commonMain/kotlin/dev/jordond/compass/autocomplete/HttpAutocomplete.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.autocomplete 2 | 3 | import dev.jordond.compass.autocomplete.web.HttpAutocompleteService 4 | import dev.jordond.compass.autocomplete.web.SearchEndpoint 5 | import dev.jordond.compass.tools.web.HttpApiEndpoint 6 | import io.ktor.client.HttpClient 7 | import kotlinx.coroutines.CoroutineDispatcher 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.serialization.json.Json 10 | 11 | /** 12 | * Creates a new [Autocomplete] instance that uses a HTTP service to provide autocomplete 13 | * suggestions. 14 | * 15 | * @param T The type of the autocomplete suggestions results. 16 | * @param httpService The HTTP service to use for autocomplete suggestions. 17 | * @param options The options to use for the autocomplete. 18 | * @param dispatcher The coroutine dispatcher to use for the autocomplete. 19 | * @return A new [Autocomplete] instance. 20 | */ 21 | public fun Autocomplete( 22 | httpService: HttpAutocompleteService, 23 | options: AutocompleteOptions = AutocompleteOptions(), 24 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 25 | ): Autocomplete = Autocomplete(httpService, options, dispatcher) 26 | 27 | /** 28 | * Creates a new [Autocomplete] instance that uses a HTTP service to provide autocomplete 29 | * suggestions. 30 | * 31 | * @param T The type of the autocomplete suggestions results. 32 | * @param endpoint The search endpoint to use for autocomplete suggestions. 33 | * @param options The options to use for the autocomplete. 34 | * @param json The JSON serializer to use for the autocomplete. 35 | * @param httpClient The HTTP client to use for the autocomplete. 36 | * @param dispatcher The coroutine dispatcher to use for the autocomplete. 37 | * @return A new [Autocomplete] instance. 38 | */ 39 | public fun Autocomplete( 40 | endpoint: SearchEndpoint, 41 | options: AutocompleteOptions = AutocompleteOptions(), 42 | json: Json = HttpApiEndpoint.json(), 43 | httpClient: HttpClient = HttpApiEndpoint.httpClient(json), 44 | dispatcher: CoroutineDispatcher = Dispatchers.Default, 45 | ): Autocomplete { 46 | val service = HttpAutocompleteService(endpoint, json, httpClient) 47 | return Autocomplete(service, options, dispatcher) 48 | } -------------------------------------------------------------------------------- /compass-geocoder-web-googlemaps/src/commonMain/kotlin/dev/jordond/compass/geocoder/web/parameter/GoogleMapsLocationType.kt: -------------------------------------------------------------------------------- 1 | package dev.jordond.compass.geocoder.web.parameter 2 | 3 | import dev.jordond.compass.tools.web.parameter.QueryParamListValue 4 | import dev.jordond.compass.tools.web.parameter.QueryParamValue 5 | 6 | /** 7 | * A filter of one or more location types, separated by a pipe (|). If the parameter contains 8 | * multiple location types, the API returns all addresses that match any of the types. 9 | * A note about processing: The location_type parameter does not restrict the search to the 10 | * specified location types. Rather, the location_type acts as a post-search filter: the API fetches 11 | * all results for the specified latlng, then discards those results that do not match the specified 12 | * location types. 13 | * 14 | * See [Google Maps Geocoding API](https://developers.google.com/maps/documentation/geocoding/requests-reverse-geocoding#optional_parameters) 15 | */ 16 | public enum class GoogleMapsLocationType : QueryParamValue { 17 | 18 | /** 19 | * returns only the addresses for which Google has location information accurate down to street 20 | * address precision. 21 | */ 22 | Rooftop, 23 | 24 | /** 25 | * returns only the addresses that reflect an approximation (usually on a road) interpolated 26 | * between two precise points (such as intersections). An interpolated range generally indicates 27 | * that rooftop geocodes are unavailable for a street address. 28 | */ 29 | RangeInterpolated, 30 | 31 | /** 32 | * returns only geometric centers of a location such as a polyline (for example, a street) or 33 | * polygon (region). 34 | */ 35 | GeometricCenter, 36 | 37 | /** 38 | * returns only the addresses that are characterized as approximate. 39 | */ 40 | Approximate; 41 | 42 | override val value: String = name.uppercase() 43 | } 44 | 45 | /** 46 | * A list of [GoogleMapsLocationType] to be used as a query parameter. 47 | */ 48 | public class GoogleMapsLocationTypeList( 49 | override val values: List, 50 | ) : QueryParamListValue { 51 | 52 | override val separator: String = "|" 53 | 54 | public constructor(vararg values: GoogleMapsLocationType) : this(values.toList()) 55 | } --------------------------------------------------------------------------------