diff --git a/Package.swift b/Package.swift
index 3e587626..5a7897de 100644
--- a/Package.swift
+++ b/Package.swift
@@ -16,7 +16,7 @@ if useLocalFramework {
path: "./common/target/ios/libferrostar-rs.xcframework"
)
} else {
- let releaseTag = "0.11.0"
+ let releaseTag = "0.12.0"
let releaseChecksum = "32a28d66933b6dc7f9b206c79e265767fc51339a9a81f81304c3f15cd7722d19"
binaryTarget = .binaryTarget(
name: "FerrostarCoreRS",
diff --git a/android/build.gradle b/android/build.gradle
index ab97c9f9..ee71caef 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -12,5 +12,5 @@ plugins {
allprojects {
group = "com.stadiamaps.ferrostar"
- version = "0.11.0"
+ version = "0.12.0"
}
diff --git a/android/google-play-services/.gitignore b/android/google-play-services/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/android/google-play-services/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/google-play-services/build.gradle b/android/google-play-services/build.gradle
new file mode 100644
index 00000000..323b3285
--- /dev/null
+++ b/android/google-play-services/build.gradle
@@ -0,0 +1,67 @@
+import com.vanniktech.maven.publish.AndroidMultiVariantLibrary
+import com.vanniktech.maven.publish.SonatypeHost
+
+plugins {
+ alias libs.plugins.androidLibrary
+ alias libs.plugins.jetbrainsKotlinAndroid
+ alias libs.plugins.ktfmt
+ alias libs.plugins.mavenPublish
+}
+
+android {
+ namespace 'com.stadiamaps.ferrostar.googleplayservices'
+ compileSdk 34
+
+ defaultConfig {
+ minSdk 25
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ coreLibraryDesugaringEnabled true
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+ // For as long as we support API 25; once we can raise support to 26, all is fine
+ coreLibraryDesugaring libs.desugar.jdk.libs
+
+ implementation libs.androidx.ktx
+ implementation libs.androidx.appcompat
+ implementation libs.material
+
+ implementation project(':core')
+ implementation libs.play.services.location
+
+ testImplementation libs.junit
+ androidTestImplementation libs.androidx.test.junit
+ androidTestImplementation libs.androidx.test.espresso
+}
+
+mavenPublishing {
+ publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
+ signAllPublications()
+
+ configure(new AndroidMultiVariantLibrary(true, true))
+
+ apply from: "${rootProject.projectDir}/common-pom.gradle"
+
+ pom {
+ name = "Ferrostar Google Play Services"
+ description = "Ferrostar components that rely on Google Play Services"
+ commonPomConfig(it, true)
+ }
+}
diff --git a/android/google-play-services/consumer-rules.pro b/android/google-play-services/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/google-play-services/proguard-rules.pro b/android/google-play-services/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/google-play-services/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android/google-play-services/src/androidTest/java/com/stadiamaps/ferrostar/googleplayservices/ExampleInstrumentedTest.kt b/android/google-play-services/src/androidTest/java/com/stadiamaps/ferrostar/googleplayservices/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..8fa2eec0
--- /dev/null
+++ b/android/google-play-services/src/androidTest/java/com/stadiamaps/ferrostar/googleplayservices/ExampleInstrumentedTest.kt
@@ -0,0 +1,22 @@
+package com.stadiamaps.ferrostar.googleplayservices
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.stadiamaps.ferrostar.googleplayservices.test", appContext.packageName)
+ }
+}
diff --git a/android/google-play-services/src/main/AndroidManifest.xml b/android/google-play-services/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/google-play-services/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/google-play-services/src/main/java/com/stadiamaps/ferrostar/googleplayservices/FusedLocationProvider.kt b/android/google-play-services/src/main/java/com/stadiamaps/ferrostar/googleplayservices/FusedLocationProvider.kt
new file mode 100644
index 00000000..566f9212
--- /dev/null
+++ b/android/google-play-services/src/main/java/com/stadiamaps/ferrostar/googleplayservices/FusedLocationProvider.kt
@@ -0,0 +1,64 @@
+package com.stadiamaps.ferrostar.googleplayservices
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Looper
+import android.util.Log
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.google.android.gms.location.LocationListener
+import com.google.android.gms.location.LocationRequest
+import com.google.android.gms.location.LocationServices
+import com.google.android.gms.location.Priority
+import com.stadiamaps.ferrostar.core.LocationProvider
+import com.stadiamaps.ferrostar.core.LocationUpdateListener
+import com.stadiamaps.ferrostar.core.toUserLocation
+import java.util.concurrent.Executor
+import uniffi.ferrostar.Heading
+import uniffi.ferrostar.UserLocation
+
+class FusedLocationProvider(
+ context: Context,
+ private val fusedLocationProviderClient: FusedLocationProviderClient =
+ LocationServices.getFusedLocationProviderClient(context)
+) : LocationProvider {
+
+ companion object {
+ private const val TAG = "FusedLocationProvider"
+ }
+
+ override var lastLocation: UserLocation? = null
+ private set
+
+ override var lastHeading: Heading? = null
+ private set
+
+ private val listeners: MutableMap = mutableMapOf()
+
+ @SuppressLint("MissingPermission")
+ override fun addListener(listener: LocationUpdateListener, executor: Executor) {
+ Log.d(TAG, "Adding listener")
+ if (listeners.contains(listener)) {
+ Log.d(TAG, "Listener already added")
+ return
+ }
+
+ val locationListener = LocationListener { newLocation ->
+ listener.onLocationUpdated(newLocation.toUserLocation())
+ }
+ listeners[listener] = locationListener
+
+ val locationRequest =
+ LocationRequest.Builder(1000L).setPriority(Priority.PRIORITY_HIGH_ACCURACY).build()
+
+ fusedLocationProviderClient.requestLocationUpdates(
+ locationRequest, locationListener, Looper.getMainLooper())
+ }
+
+ override fun removeListener(listener: LocationUpdateListener) {
+ val activeListener = listeners.remove(listener)
+
+ if (activeListener != null) {
+ fusedLocationProviderClient.removeLocationUpdates(activeListener)
+ }
+ }
+}
diff --git a/android/google-play-services/src/test/java/com/stadiamaps/ferrostar/googleplayservices/ExampleUnitTest.kt b/android/google-play-services/src/test/java/com/stadiamaps/ferrostar/googleplayservices/ExampleUnitTest.kt
new file mode 100644
index 00000000..1dfb1e72
--- /dev/null
+++ b/android/google-play-services/src/test/java/com/stadiamaps/ferrostar/googleplayservices/ExampleUnitTest.kt
@@ -0,0 +1,16 @@
+package com.stadiamaps.ferrostar.googleplayservices
+
+import org.junit.Assert.*
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml
index 6ca14df4..7acc1234 100644
--- a/android/gradle/libs.versions.toml
+++ b/android/gradle/libs.versions.toml
@@ -15,12 +15,14 @@ compose = "2024.09.01"
okhttp = "4.12.0"
moshi = "1.15.1"
maplibre-compose = "0.1.0"
+playServicesLocation = "21.3.0"
junit = "4.13.2"
junitVersion = "1.2.1"
junitCompose = "1.7.1"
espressoCore = "3.6.1"
okhttp-mock = "2.0.0"
mavenPublish = "0.29.0"
+material = "1.12.0"
[libraries]
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
@@ -53,11 +55,14 @@ moshi = { group = "com.squareup.moshi", name = "moshi", version.ref = "moshi" }
okhttp-mock = { group = "com.github.gmazzo", name = "okhttp-mock", version.ref = "okhttp-mock" }
# MapLibre
maplibre-compose = { group = "io.github.rallista", name = "maplibre-compose", version.ref = "maplibre-compose" }
+# Google Play Services (for Google module)
+play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" }
# Testing
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-test-espresso = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "junitCompose" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
diff --git a/android/settings.gradle b/android/settings.gradle
index 64905c23..e34d6ca0 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -17,3 +17,4 @@ include ':core'
include ':composeui'
include ':demo-app'
include ':maplibreui'
+include ':google-play-services'
diff --git a/common/Cargo.lock b/common/Cargo.lock
index 1279d5bd..3933ac31 100644
--- a/common/Cargo.lock
+++ b/common/Cargo.lock
@@ -337,7 +337,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "ferrostar"
-version = "0.11.0"
+version = "0.12.0"
dependencies = [
"assert-json-diff",
"geo",
diff --git a/common/ferrostar/Cargo.toml b/common/ferrostar/Cargo.toml
index a277132b..797d2211 100644
--- a/common/ferrostar/Cargo.toml
+++ b/common/ferrostar/Cargo.toml
@@ -2,7 +2,7 @@ lints.workspace = true
[package]
name = "ferrostar"
-version = "0.11.0"
+version = "0.12.0"
readme = "README.md"
description = "The core of modern turn-by-turn navigation."
keywords = ["navigation", "routing", "valhalla", "osrm"]
diff --git a/guide/src/android-getting-started.md b/guide/src/android-getting-started.md
index 993a6a87..4c826377 100644
--- a/guide/src/android-getting-started.md
+++ b/guide/src/android-getting-started.md
@@ -23,6 +23,9 @@ dependencies {
implementation "com.stadiamaps.ferrostar:core:${ferrostarVersion}"
implementation "com.stadiamaps.ferrostar:maplibreui:${ferrostarVersion}"
+ // Optional - if using Google Play Service's FusedLocation
+ implementation "com.stadiamaps.ferrostar:google-play-services:${ferrostarVersion}"
+
// okhttp3
implementation platform("com.squareup.okhttp3:okhttp-bom:4.11.0")
implementation 'com.squareup.okhttp3:okhttp'
@@ -129,7 +132,27 @@ In other cases, get a context using an appropriate method.
locationProvider = AndroidSystemLocationProvider(context = this)
```
-#### TODO: Google Play Fused Location Client
+#### Google Play Fused Location Client
+
+Alternatively, you can use the `FusedLocationProvider`
+if your app uses Google Play Services.
+This normally offers better device positioning than the default Android location provider
+on supported devices.
+To make use of it,
+you will need to include the optional `implementation "com.stadiamaps.ferrostar:google-play-services:${ferrostarVersion}"`
+in your Gradle dependencies block.
+
+Just as with the `AndroidSystemLocationProvider`,
+you probably need to declare it as a `lateinit var` instance variable first,
+and then initialize later once the `Context` is available.
+
+```kotlin
+// Instance variable definition
+private lateinit var locationProvider: FusedLocationProvider
+
+// Later when the activity loads and a context is avaialable
+locationProvider = FusedLocationProvider(context = this)
+```
#### `SimulatedLocationProvider`
diff --git a/web/package-lock.json b/web/package-lock.json
index c52c756e..e16d63ec 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@stadiamaps/ferrostar-webcomponents",
- "version": "0.11.0",
+ "version": "0.12.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@stadiamaps/ferrostar-webcomponents",
- "version": "0.11.0",
+ "version": "0.12.0",
"license": "BSD-3-Clause",
"dependencies": {
"@stadiamaps/ferrostar": "file:../common/ferrostar/pkg",
diff --git a/web/package.json b/web/package.json
index 0d585dec..013d9579 100644
--- a/web/package.json
+++ b/web/package.json
@@ -6,7 +6,7 @@
"CatMe0w (https://github.com/CatMe0w)",
"Luke Seelenbinder "
],
- "version": "0.11.0",
+ "version": "0.12.0",
"license": "BSD-3-Clause",
"type": "module",
"main": "./dist/ferrostar-webcomponents.js",