map improvements and GTFS vehicle tracking bases
This commit is contained in:
Generated
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2026-09-01T18:25:26.823607018Z">
|
<DropdownSelection timestamp="2026-09-02T15:28:00.985067432Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=/home/lukas/.config/.android/avd/Medium_Phone_OSS.avd" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=/home/lukas/.config/.android/avd/Medium_Phone_OSS.avd" />
|
||||||
|
|||||||
@@ -59,4 +59,5 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
implementation(libs.gtfs.realtime.bindings)
|
||||||
}
|
}
|
||||||
@@ -4,18 +4,11 @@ import android.app.Activity
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.layout.size
|
import com.google.transit.realtime.GtfsRealtime
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import ovh.plrapps.mapcompose.api.addMarker
|
|
||||||
import ovh.plrapps.mapcompose.api.removeMarker
|
import ovh.plrapps.mapcompose.api.removeMarker
|
||||||
import ovh.plrapps.mapcompose.ui.state.markers.model.RenderingStrategy
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -82,32 +75,36 @@ private val GTFSMainHandler = Handler(Looper.getMainLooper())
|
|||||||
private var GTFSTrackingThread: Thread? = null
|
private var GTFSTrackingThread: Thread? = null
|
||||||
private var GTFSIsTracking = false
|
private var GTFSIsTracking = false
|
||||||
|
|
||||||
private fun fetchGTFSPosition(gtfsUrl: URL,api_key: String, callee: Activity) {
|
private fun fetchGTFSPositions(gtfsUrl: URL, api_key: String, callee: Activity) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${"/"}")
|
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.combined_feed_path)}")
|
||||||
.header("User-Agent", "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)")
|
.header("User-Agent", "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)")
|
||||||
|
.header("X-Access-Token", api_key)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
gtfshttpClient.newCall(request).execute().use { response ->
|
gtfshttpClient.newCall(request).execute().use { response ->
|
||||||
var message = if (response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
response.body?.string() ?: "Empty response"
|
throw Exception("Error: ${response.code} ${response.message}")
|
||||||
} else {
|
|
||||||
"Error: ${response.code} ${response.message}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle response
|
val bytes = response.body?.bytes() ?: throw Exception("Empty response")
|
||||||
|
val feed = GtfsRealtime.FeedMessage.parseFrom(bytes)
|
||||||
|
|
||||||
|
feed.entityList.forEach { entity ->
|
||||||
|
if (entity.hasVehicle()) {
|
||||||
|
|
||||||
|
// use vehicle
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Post UI update to main thread
|
|
||||||
GTFSMainHandler.post {
|
GTFSMainHandler.post {
|
||||||
Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_SHORT).show()
|
Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Start the timer with a URL:
|
// Start the timer with a URL:
|
||||||
fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
|
fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
|
||||||
if (GTFSIsTracking) return
|
if (GTFSIsTracking) return
|
||||||
@@ -197,7 +194,7 @@ fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
|
|||||||
return@Thread
|
return@Thread
|
||||||
}
|
}
|
||||||
while (GTFSIsTracking) {
|
while (GTFSIsTracking) {
|
||||||
fetchGTFSPosition(gtfsUrl,api_key, callee)
|
fetchGTFSPositions(gtfsUrl,api_key, callee)
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1000) // Wait 1 second between requests
|
Thread.sleep(1000) // Wait 1 second between requests
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
|
|||||||
@@ -4,10 +4,15 @@ import android.app.Activity
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import ovh.plrapps.mapcompose.api.addPath
|
||||||
import ovh.plrapps.mapcompose.api.moveMarker
|
import ovh.plrapps.mapcompose.api.moveMarker
|
||||||
|
import ovh.plrapps.mapcompose.api.removePath
|
||||||
|
import ovh.plrapps.mapcompose.api.updatePath
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -28,8 +33,9 @@ val haukhttpClient = OkHttpClient.Builder()
|
|||||||
|
|
||||||
private val HaukMainHandler = Handler(Looper.getMainLooper())
|
private val HaukMainHandler = Handler(Looper.getMainLooper())
|
||||||
private var HaukTrackingThread: Thread? = null
|
private var HaukTrackingThread: Thread? = null
|
||||||
private var HaukIsTracking = false
|
var HaukIsTracking = false
|
||||||
|
|
||||||
|
private var PathCreated = false
|
||||||
private fun fetchHaukPosition(haukUrl: String, callee: Activity) {
|
private fun fetchHaukPosition(haukUrl: String, callee: Activity) {
|
||||||
try {
|
try {
|
||||||
val url = URL(haukUrl)
|
val url = URL(haukUrl)
|
||||||
@@ -76,6 +82,18 @@ private fun fetchHaukPosition(haukUrl: String, callee: Activity) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Marker might not exist yet, ignore
|
// Marker might not exist yet, ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pathDataBuilder.addPoint(longitudeToXNormalized(lon), latitudeToYNormalized(lat));
|
||||||
|
if (!PathCreated) {
|
||||||
|
mapState.addPath(
|
||||||
|
"target_path", color = Color(0xFF0000FF),
|
||||||
|
pathData = pathDataBuilder.build()?: return@post,
|
||||||
|
width = 1.dp
|
||||||
|
);
|
||||||
|
PathCreated = true
|
||||||
|
}
|
||||||
|
mapState.updatePath("target_path", pathData = pathDataBuilder.build())
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
HaukMainHandler.post {
|
HaukMainHandler.post {
|
||||||
@@ -95,6 +113,10 @@ fun startHaukTracking(url: String, callee: Activity) {
|
|||||||
if (HaukIsTracking) return
|
if (HaukIsTracking) return
|
||||||
|
|
||||||
HaukIsTracking = true
|
HaukIsTracking = true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HaukTrackingThread = Thread {
|
HaukTrackingThread = Thread {
|
||||||
while (HaukIsTracking) {
|
while (HaukIsTracking) {
|
||||||
fetchHaukPosition(url, callee)
|
fetchHaukPosition(url, callee)
|
||||||
@@ -111,4 +133,6 @@ fun stopHaukTracking() {
|
|||||||
HaukIsTracking = false
|
HaukIsTracking = false
|
||||||
HaukTrackingThread?.interrupt()
|
HaukTrackingThread?.interrupt()
|
||||||
HaukTrackingThread = null
|
HaukTrackingThread = null
|
||||||
|
mapState.removePath("target_path");
|
||||||
|
PathCreated = false;
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -52,9 +51,18 @@ import java.io.FileInputStream
|
|||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import ovh.plrapps.mapcompose.api.getMarkerInfo
|
import ovh.plrapps.mapcompose.api.getMarkerInfo
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import ovh.plrapps.mapcompose.api.addPath
|
||||||
|
import ovh.plrapps.mapcompose.api.makePathDataBuilder
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
|
||||||
// MapState configuration for OpenStreetMap
|
// MapState configuration for OpenStreetMap
|
||||||
@@ -67,15 +75,42 @@ val mapState = MapState(
|
|||||||
fullHeight = 67108864,
|
fullHeight = 67108864,
|
||||||
tileSize = 256
|
tileSize = 256
|
||||||
)
|
)
|
||||||
|
val pathDataBuilder = mapState.makePathDataBuilder();
|
||||||
|
|
||||||
|
|
||||||
var target_speed by mutableStateOf("0.0km/h");
|
var target_speed by mutableStateOf("0.0km/h");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a cached tile stream provider for OpenStreetMap tiles
|
* Derives a stable, filesystem-safe cache namespace for a tile provider from its URL.
|
||||||
* Tiles are cached in the app's cache directory for offline access and faster loading
|
* Query parameters are ignored: same path with a different apikey/lang/etc. maps to
|
||||||
* Cache expires after 30 days to ensure map updates are fetched
|
* the same namespace. A different path (e.g. "outdoor" vs "basic") maps to a different one.
|
||||||
*/
|
*/
|
||||||
fun createCachedTileStreamProvider(context: Context, cacheExpiryDays: Int = 30): TileStreamProvider {
|
fun providerCacheNamespace(urlNoQuery: String): String {
|
||||||
val cacheDir = File(context.cacheDir, "map_tiles")
|
val digest = MessageDigest.getInstance("SHA-256")
|
||||||
|
.digest(urlNoQuery.toByteArray(Charsets.UTF_8))
|
||||||
|
return digest.joinToString("") { "%02x".format(it) }.take(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a cached tile stream provider for a given map tile URL template.
|
||||||
|
* Tiles are cached in the app's cache directory, namespaced per provider so different
|
||||||
|
* providers/layers never collide even if they share col/row/zoomLvl coordinates.
|
||||||
|
* Cache expires after 30 days to ensure map updates are fetched.
|
||||||
|
*
|
||||||
|
* @param urlBuilder builds the request URL for a given (row, col, zoomLvl). Everything
|
||||||
|
* after '?' (GET parameters) is ignored for cache namespacing purposes.
|
||||||
|
*/
|
||||||
|
fun createCachedTileStreamProvider(
|
||||||
|
context: Context,
|
||||||
|
cacheExpiryDays: Int = 30,
|
||||||
|
urlBuilder: (row: Int, col: Int, zoomLvl: Int) -> String
|
||||||
|
): TileStreamProvider {
|
||||||
|
// Namespace derived from the URL path (query params stripped), sampled once,
|
||||||
|
// so tiles from different providers/layers land in separate cache directories.
|
||||||
|
val sampleUrlNoQuery = urlBuilder(0, 0, 0).substringBefore('?')
|
||||||
|
val namespace = providerCacheNamespace(sampleUrlNoQuery)
|
||||||
|
|
||||||
|
val cacheDir = File(File(context.cacheDir, "map_tiles"), namespace)
|
||||||
if (!cacheDir.exists()) {
|
if (!cacheDir.exists()) {
|
||||||
cacheDir.mkdirs()
|
cacheDir.mkdirs()
|
||||||
}
|
}
|
||||||
@@ -95,7 +130,7 @@ fun createCachedTileStreamProvider(context: Context, cacheExpiryDays: Int = 30):
|
|||||||
|
|
||||||
// Since mapcompose runs this on a background thread, we can do networking here.
|
// Since mapcompose runs this on a background thread, we can do networking here.
|
||||||
try {
|
try {
|
||||||
val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png")
|
val url = URL(urlBuilder(row, col, zoomLvl))
|
||||||
val connection = url.openConnection()
|
val connection = url.openConnection()
|
||||||
// Set User-Agent header as required by OSM tile usage policy
|
// Set User-Agent header as required by OSM tile usage policy
|
||||||
val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android) (Contact: poliecho@pupes.org)"
|
val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android) (Contact: poliecho@pupes.org)"
|
||||||
@@ -115,7 +150,9 @@ fun createCachedTileStreamProvider(context: Context, cacheExpiryDays: Int = 30):
|
|||||||
if (tileFile.exists()) {
|
if (tileFile.exists()) {
|
||||||
FileInputStream(tileFile)
|
FileInputStream(tileFile)
|
||||||
} else {
|
} else {
|
||||||
|
Log.e("TileStreamProvider", "Failed to download tile: ${e.message} from: ")
|
||||||
null
|
null
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -177,6 +214,8 @@ fun MainScreen(callee: Activity) {
|
|||||||
var haukState by remember { mutableStateOf(false) }
|
var haukState by remember { mutableStateOf(false) }
|
||||||
var golemioState by remember { mutableStateOf(golemioAPIkey.isNotEmpty())}
|
var golemioState by remember { mutableStateOf(golemioAPIkey.isNotEmpty())}
|
||||||
|
|
||||||
|
var showMapProviderDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (golemioState) {
|
if (golemioState) {
|
||||||
startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee)
|
startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee)
|
||||||
@@ -219,6 +258,44 @@ fun MainScreen(callee: Activity) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box(modifier = Modifier.align(Alignment.BottomEnd).navigationBarsPadding()) {
|
||||||
|
IconButton( // change map provider
|
||||||
|
onClick = { showMapProviderDialog = true },
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 5.dp, bottom = 5.dp),
|
||||||
|
IconID = R.drawable.outline_layers_24
|
||||||
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showMapProviderDialog,
|
||||||
|
onDismissRequest = { showMapProviderDialog = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Map provider 1") },
|
||||||
|
onClick = { /* Do something... */ },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_launcher_foreground),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color(0xFF000000)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text("Add new provider") },
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.outline_add_24),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = Color(0xFF000000)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = { /* Do something... */ }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showSettings) {
|
if (showSettings) {
|
||||||
ShowSettingsMenu(
|
ShowSettingsMenu(
|
||||||
onDismiss = { showSettings = false },
|
onDismiss = { showSettings = false },
|
||||||
@@ -350,7 +427,9 @@ fun OpenStreetMapScreen(context: Context? = null) {
|
|||||||
// Create tile stream provider for OpenStreetMap with caching
|
// Create tile stream provider for OpenStreetMap with caching
|
||||||
val tileStreamProvider = remember(context) {
|
val tileStreamProvider = remember(context) {
|
||||||
context?.let {
|
context?.let {
|
||||||
createCachedTileStreamProvider(it)
|
createCachedTileStreamProvider(it) { row, col, zoomLvl ->
|
||||||
|
"https://api.mapy.com/v1/maptiles/outdoor/256/$zoomLvl/$col/$row?lang=cs&apikey=jv6a1-RP0jvLuMAL8jUUVw5Jaw1gAVC2jttMzdsOqyw"
|
||||||
|
}
|
||||||
} ?: TileStreamProvider { row, col, zoomLvl ->
|
} ?: TileStreamProvider { row, col, zoomLvl ->
|
||||||
// Fallback for preview mode without context
|
// Fallback for preview mode without context
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M440,520L200,520L200,440L440,440L440,200L520,200L520,440L760,440L760,520L520,520L520,760L440,760L440,520Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright (C) 2026 The Android Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M480,842L120,562L186,512L480,740L774,512L840,562L480,842ZM480,640L120,360L480,80L840,360L480,640ZM480,360L480,360L480,360L480,360ZM480,538L710,360L480,182L250,360L480,538Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
<string name="app_name">MHD run Pathfinder</string>
|
<string name="app_name">MHD run Pathfinder</string>
|
||||||
<string name="routes_path">/v2/gtfs/routes</string>
|
<string name="routes_path">/v2/gtfs/routes</string>
|
||||||
<string name="stops_path">/v2/gtfs/stops</string>
|
<string name="stops_path">/v2/gtfs/stops</string>
|
||||||
|
<string name="combined_feed_path">/v2/vehiclepositions/gtfsrt/pid_feed.pb</string>"
|
||||||
<string name="golemio_base_url">https://api.golemio.cz</string>
|
<string name="golemio_base_url">https://api.golemio.cz</string>
|
||||||
<string name="golemio_realtime_path"></string>
|
<string name="golemio_realtime_path"></string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "9.0.0"
|
agp = "9.0.0"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
|
gtfsRealtimeBindings = "0.2.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.1.5"
|
junitVersion = "1.1.5"
|
||||||
espressoCore = "3.5.1"
|
espressoCore = "3.5.1"
|
||||||
@@ -14,6 +15,7 @@ appcompat = "1.7.1"
|
|||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
gtfs-realtime-bindings = { module = "org.mobilitydata:gtfs-realtime-bindings", version.ref = "gtfsRealtimeBindings" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
|
|||||||
Reference in New Issue
Block a user