Compare commits

..

11 Commits

Author SHA1 Message Date
PoliEcho dbd29bf421 add working speed-o-meter 2026-02-13 16:41:05 +01:00
PoliEcho c3f8dd4066 revert GTFSUtils.kt from 67fea519 2026-02-13 14:37:17 +01:00
PoliEcho 5970dce0d2 works? 2026-02-13 14:20:30 +01:00
PoliEcho 0e2208d104 add more optimalization and speed mareker 2026-02-13 12:28:19 +01:00
PoliEcho 67fea51949 tweaks 2026-01-28 22:34:09 +01:00
PoliEcho 4263a7eff0 add more optimalization 2026-01-28 22:05:30 +01:00
PoliEcho d1314a1b32 fix ANR 2026-01-28 21:55:22 +01:00
PoliEcho 93148fcf36 make sure all stops are requested 2026-01-28 21:55:08 +01:00
PoliEcho ced8e8173d add stops 2026-01-28 19:08:35 +01:00
PoliEcho ca2c5fe385 add GTFS static data 2026-01-28 15:00:44 +01:00
PoliEcho 077c26eec7 add checkbox and golemio api 2026-01-22 11:55:46 +01:00
10 changed files with 656 additions and 281 deletions
+1
View File
@@ -13,3 +13,4 @@
.externalNativeBuild
.cxx
local.properties
test-data
+2 -2
View File
@@ -3,8 +3,8 @@
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DIALOG" />
<DropdownSelection timestamp="2026-01-19T17:27:16.298528612Z">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-02-13T13:44:07.200825088Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/home/lukas/.config/.android/avd/Medium_Phone_OSS.avd" />
@@ -0,0 +1,218 @@
package org.pupes.mhdrunpathfinder
import android.app.Activity
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.compose.foundation.layout.size
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 okhttp3.OkHttpClient
import okhttp3.Request
import ovh.plrapps.mapcompose.api.addMarker
import ovh.plrapps.mapcompose.api.removeMarker
import ovh.plrapps.mapcompose.ui.state.markers.model.RenderingStrategy
import java.net.URL
@Serializable
data class GTFSRoute(
val agency_id: String?,
val is_night: Boolean,
val route_color: String?,
val route_desc: String? = null,
val route_id: String?,
val route_long_name: String?,
val route_short_name: String?,
val route_text_color: String?,
val route_type: Int,
val route_url: String?,
val is_regional: Boolean,
val is_substitute_transport: Boolean
)
// Type alias for array of GTFS routes
typealias GTFSRoutes = MutableList<GTFSRoute>
var gtfsRoutes: GTFSRoutes = mutableListOf()
@Serializable
data class GTFSStopGeometry(
val coordinates: List<Double>,
val type: String?
)
@Serializable
data class GTFSStopProperties(
val location_type: Int,
val parent_station: String? = null,
val platform_code: String? = null,
val stop_id: String,
val stop_name: String?,
val wheelchair_boarding: Int,
val zone_id: String? = null,
val level_id: String? = null
)
@Serializable
data class GTFSStop(
val geometry: GTFSStopGeometry,
val properties: GTFSStopProperties,
val type: String?
)
@Serializable
data class GTFSStops(
val features: MutableList<GTFSStop>,
val type: String?
)
var gtfsStops: GTFSStops = GTFSStops(mutableListOf<GTFSStop>(), "FeatureCollection")
// OkHttpClient for making HTTP requests
val gtfshttpClient = OkHttpClient.Builder()
.build()
private val GTFSMainHandler = Handler(Looper.getMainLooper())
private var GTFSTrackingThread: Thread? = null
private var GTFSIsTracking = false
private fun fetchGTFSPosition(gtfsUrl: URL,api_key: String, callee: Activity) {
try {
val request = Request.Builder()
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${"/"}")
.header("User-Agent", "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)")
.build()
gtfshttpClient.newCall(request).execute().use { response ->
var message = if (response.isSuccessful) {
response.body?.string() ?: "Empty response"
} else {
"Error: ${response.code} ${response.message}"
}
// TODO: Handle response
}
} catch (e: Exception) {
// Post UI update to main thread
GTFSMainHandler.post {
Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_SHORT).show()
}
}
}
// Start the timer with a URL:
fun startGTFSTracking(url: String, api_key: String, callee: Activity) {
if (GTFSIsTracking) return
GTFSIsTracking = true
GTFSTrackingThread = Thread {
val gtfsUrl = URL(url);
try {
if(gtfsRoutes.isEmpty()) {
GTFSMainHandler.post {
Toast.makeText(callee, "Fetching routes...", Toast.LENGTH_SHORT).show()}
var messageParsed: GTFSRoutes;
var offset = 0;
do {
val request = Request.Builder()
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.routes_path)}?offset=${offset}")
.header(
"User-Agent",
"${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)"
)
.header("X-Access-Token", api_key).build();
gtfshttpClient.newCall(request).execute().use { response ->
var message = if (response.isSuccessful) {
response.body?.string() ?: "Empty response"
} else {
"Error: ${response.code} ${response.message}"
}
messageParsed = json.decodeFromString<GTFSRoutes>(message)
gtfsRoutes.addAll(messageParsed)
offset += messageParsed.size
}} while (messageParsed.isNotEmpty())
}
if(gtfsStops.features.isEmpty()) {
GTFSMainHandler.post {
Toast.makeText(callee, "Fetching stops...", Toast.LENGTH_SHORT).show()}
var messageParsed: GTFSStops;
var offset = 0;
do {
val request = Request.Builder()
.url("${gtfsUrl.protocol}://${gtfsUrl.host}${callee.getString(R.string.stops_path)}?offset=${offset}")
.header(
"User-Agent",
"${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)"
)
.header("X-Access-Token", api_key).build();
gtfshttpClient.newCall(request).execute().use { response ->
var message = if (response.isSuccessful) {
response.body?.string() ?: "Empty response"
} else {
"Error: ${response.code} ${response.message}"
}
messageParsed = json.decodeFromString<GTFSStops>(message)
gtfsStops.features.addAll(messageParsed.features)
}
offset += messageParsed.features.size;
}while(messageParsed.features.isNotEmpty())
}
// put all stops on the map
GTFSMainHandler.post {
Toast.makeText(callee, "Drawing stops...", Toast.LENGTH_SHORT).show()}
for (stop in gtfsStops!!.features) {
if (stop.properties.parent_station == null) {
mapState.addMarker(stop.properties.stop_id, x = longitudeToXNormalized(stop.geometry.coordinates[0]), y = latitudeToYNormalized(stop.geometry.coordinates[1]), renderingStrategy = RenderingStrategy.LazyLoading("default")) {
Icon(
painter = painterResource(id = R.drawable.outline_directions_bus_24),
contentDescription = null,
modifier = Modifier.size(12.dp),
tint = Color(0xFF0000FF)
)
}}
}
GTFSMainHandler.post {
Toast.makeText(callee, "Done drawing ${gtfsStops.features.size} stops", Toast.LENGTH_SHORT).show()}
} catch(e: Exception) {
GTFSMainHandler.post {
Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_SHORT).show()
}
GTFSMainHandler.post { stopGTFSTracking() }
return@Thread
}
while (GTFSIsTracking) {
fetchGTFSPosition(gtfsUrl,api_key, callee)
try {
Thread.sleep(1000) // Wait 1 second between requests
} catch (e: InterruptedException) {
break
}
}
}.apply { start() }
}
fun stopGTFSTracking() {
GTFSIsTracking = false
for (stop in gtfsStops!!.features) {
if (stop.properties.parent_station == null) {
mapState.removeMarker(stop.properties.stop_id);
}
}
GTFSTrackingThread?.interrupt()
GTFSTrackingThread = null
}
@@ -4,13 +4,26 @@ import android.app.Activity
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import ovh.plrapps.mapcompose.api.moveMarker
import java.net.URL
@Serializable
data class HaukResponse(
val type: Int,
val expire: Long,
val serverTime: Double,
val interval: Int,
val points: List<List<Double?>>,
val encrypted: Boolean,
val salt: String? = null
)
// OkHttpClient for making HTTP requests
val httpClient = OkHttpClient.Builder()
val haukhttpClient = OkHttpClient.Builder()
.build()
private val HaukMainHandler = Handler(Looper.getMainLooper())
@@ -27,7 +40,7 @@ private fun fetchHaukPosition(haukUrl: String, callee: Activity) {
.header("User-Agent", "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android)")
.build()
httpClient.newCall(request).execute().use { response ->
haukhttpClient.newCall(request).execute().use { response ->
var message = if (response.isSuccessful) {
response.body?.string() ?: "Empty response"
} else {
@@ -35,17 +48,44 @@ private fun fetchHaukPosition(haukUrl: String, callee: Activity) {
}
message = "{" + message.substringAfter("{");
val hauk_response = Json.decodeFromString<HaukResponse>(message)
val hauk_response = json.decodeFromString<HaukResponse>(message)
val lat = hauk_response.points.last().getOrNull(0)
val lon = hauk_response.points.last().getOrNull(1)
if (lat != null && lon != null) {
HaukMainHandler.post {
mapState.moveMarker("target_position", x = longitudeToXNormalized(hauk_response.points.last()[1]), y = latitudeToYNormalized(hauk_response.points.last()[0]))
}
}
val lastlat = hauk_response.points.getOrNull(hauk_response.points.size - 2)?.getOrNull(0)
val lastlon = hauk_response.points.getOrNull(hauk_response.points.size - 2)?.getOrNull(1)
val newspeed =
if (lastlat != null && lastlon != null) {
var fulldistance: Double = 0.0
for (i in 1 until hauk_response.points.size -1) {
try{
fulldistance += haversineDistance(hauk_response.points[i-1][0]!!, hauk_response.points[i-1][1]!!, hauk_response.points[i][0]!!, hauk_response.points[i][1]!!)
} catch (e: Exception) {
// Ignore
}
}
(fulldistance / (hauk_response.interval * hauk_response.points.size)*3600)
} else {
0.0
}
HaukMainHandler.post {
try {
mapState.moveMarker("target_position", x = longitudeToXNormalized(lon), y = latitudeToYNormalized(lat))
target_speed = "${"%.1f".format(newspeed)}km/h"
} catch (e: Exception) {
// Marker might not exist yet, ignore
}
}
} else {
HaukMainHandler.post {
Toast.makeText(callee, "TARGET LOCATION NOT AVAILABLE", Toast.LENGTH_SHORT).show()
}
}}
} catch (e: Exception) {
// Post UI update to main thread
HaukMainHandler.post {
Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_LONG).show()
Toast.makeText(callee, e.message ?: "Unknown error", Toast.LENGTH_SHORT).show()
}
}
}
@@ -1,9 +1,35 @@
package org.pupes.mhdrunpathfinder
import kotlinx.serialization.json.Json
import kotlin.math.PI
import kotlin.math.ln
import kotlin.math.tan
import kotlin.math.*
fun haversineDistance(
lat1: Double,
lon1: Double,
lat2: Double,
lon2: Double
): Double {
val R = 6371.0 // Earth radius in kilometers
val phi1 = lat1 * PI / 180
val phi2 = lat2 * PI / 180
val deltaPhi = (lat2 - lat1) * PI / 180
val deltaLambda = (lon2 - lon1) * PI / 180
val a = sin(deltaPhi / 2).pow(2) +
cos(phi1) * cos(phi2) * sin(deltaLambda / 2).pow(2)
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
return R * c
}
val json = Json {
coerceInputValues = true // Coerce nulls to default values if applicable
ignoreUnknownKeys = true // Ignore fields in JSON not present in the data class
}
fun longitudeToXNormalized(longitude: Double): Double {
return (longitude + 180.0) / 360.0
}
@@ -1,17 +1,21 @@
package org.pupes.mhdrunpathfinder
import android.app.Activity
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@@ -25,52 +29,122 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import kotlinx.coroutines.launch
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlinx.serialization.Serializable
import androidx.core.content.edit
import ovh.plrapps.mapcompose.api.addLayer
import ovh.plrapps.mapcompose.api.addLazyLoader
import ovh.plrapps.mapcompose.api.addMarker
import ovh.plrapps.mapcompose.api.scrollTo
import ovh.plrapps.mapcompose.core.TileStreamProvider
import ovh.plrapps.mapcompose.ui.MapUI
import ovh.plrapps.mapcompose.ui.state.MapState
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.URL
import android.os.StrictMode
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.sp
import ovh.plrapps.mapcompose.api.getMarkerInfo
@Serializable
data class HaukResponse(
val type: Int,
val expire: Long,
val serverTime: Double,
val interval: Int,
val points: List<List<Double>>,
val encrypted: Boolean,
val salt: String? = null
)
// MapState configuration for OpenStreetMap
// Max zoom level is 18, tile size is 256x256
// At zoom level 18, there are 2^18 = 262144 tiles in each dimension
// So fullWidth/Height = 256 * 262144
// So fullWidth/Height = 256 * 2^18
val mapState = MapState(
levelCount = 19, // zoom levels 0-18
fullWidth = 67108864, // 256 * 2^18
fullHeight = 67108864,
tileSize = 256
)
var target_speed by mutableStateOf("0.0km/h");
/**
* Creates a cached tile stream provider for OpenStreetMap tiles
* Tiles are cached in the app's cache directory for offline access and faster loading
* Cache expires after 30 days to ensure map updates are fetched
*/
fun createCachedTileStreamProvider(context: Context, cacheExpiryDays: Int = 30): TileStreamProvider {
val cacheDir = File(context.cacheDir, "map_tiles")
if (!cacheDir.exists()) {
cacheDir.mkdirs()
}
val cacheExpiryMillis = cacheExpiryDays * 24 * 60 * 60 * 1000L
return TileStreamProvider { row, col, zoomLvl ->
try {
val tileFile = File(cacheDir, "tile_${zoomLvl}_${col}_${row}.png")
val shouldUseCache = tileFile.exists() &&
(System.currentTimeMillis() - tileFile.lastModified()) < cacheExpiryMillis
if (shouldUseCache) {
return@TileStreamProvider FileInputStream(tileFile)
}
// Since mapcompose runs this on a background thread, we can do networking here.
try {
val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png")
val connection = url.openConnection()
// Set User-Agent header as required by OSM tile usage policy
val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android) (Contact: poliecho@pupes.org)"
connection.setRequestProperty("User-Agent", userAgent)
connection.connectTimeout = 5000
connection.readTimeout = 10000
connection.getInputStream().use { inputStream ->
FileOutputStream(tileFile).use { outputStream ->
inputStream.copyTo(outputStream)
}
}
FileInputStream(tileFile)
} catch (e: Exception) {
e.printStackTrace()
// If download fails, try to use an expired cached tile if it exists
if (tileFile.exists()) {
FileInputStream(tileFile)
} else {
null
}
}
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Configure StrictMode to detect threading violations (debug mode only)
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog() // Log violations instead of crashing
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
}
setContent {
MaterialTheme {
@@ -78,11 +152,10 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreen()
MainScreen(this)
}
}
}
startHaukTracking("https://hauk.limit6.eu/?M4SJ-SH7I",this)
startLocationTracking(this)
}
@@ -95,19 +168,107 @@ class MainActivity : ComponentActivity() {
}
@Composable
fun MainScreen() {
fun MainScreen(callee: Activity) {
val sharedPreferences = remember { callee.getSharedPreferences("PREFERENCES", Context.MODE_PRIVATE) }
val coroutineScope = rememberCoroutineScope()
var showSettings by remember { mutableStateOf(false) }
var haukUrl by remember { mutableStateOf("") }
var golemioAPIkey by remember { mutableStateOf(sharedPreferences.getString("golemioAPIkey", "")?: "" ) }
var haukState by remember { mutableStateOf(false) }
var golemioState by remember { mutableStateOf(golemioAPIkey.isNotEmpty())}
LaunchedEffect(Unit) {
if (golemioState) {
startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee)
}
}
Box(modifier = Modifier.fillMaxSize()) {
OpenStreetMapScreen()
SettingsButton(
OpenStreetMapScreen(callee)
IconButton( // settings button
onClick = { showSettings = true },
modifier = Modifier
.align(Alignment.TopEnd)
.padding(end = 5.dp, bottom = 5.dp)
.padding(end = 5.dp, bottom = 5.dp),
IconID = R.drawable.baseline_settings_24
)
Column(modifier = Modifier.align(Alignment.TopStart)) {
IconButton( // look at target button
onClick = {
coroutineScope.launch {
mapState.getMarkerInfo("target_position")?.let { marker ->
mapState.scrollTo(marker.x, marker.y, destScale = 0.1)
}
}
},
modifier = Modifier
.padding(start = 5.dp, bottom = 5.dp),
IconID = R.drawable.target
)
IconButton( // look at my location button
onClick = {
coroutineScope.launch {
mapState.getMarkerInfo("current_position")?.let { marker ->
mapState.scrollTo(marker.x, marker.y, destScale = 0.1)
}
}
},
modifier = Modifier
.padding(start = 5.dp, bottom = 5.dp),
IconID = R.drawable.user_location
)
}
if (showSettings) {
ShowSettingsMenu(onDismiss = { showSettings = false })
ShowSettingsMenu(
onDismiss = { showSettings = false },
haukUrl = haukUrl,
onHaukUrlChange = { haukUrl = it },
onHaukApply = {
if (haukUrl.isNotEmpty()) {
haukState = true
stopHaukTracking()
startHaukTracking(haukUrl, callee)
}
},
haukState = haukState,
onHaukStateChange = {
haukState = it
if (it) {
if (haukUrl.isNotEmpty()) {
stopHaukTracking()
startHaukTracking(haukUrl, callee)
}
} else {
stopHaukTracking()
}
},
golemioAPIkey = golemioAPIkey,
onGolemioAPIChange = { golemioAPIkey = it },
onGolemioApply = {
if (golemioAPIkey.isNotEmpty()) {
golemioState = true
sharedPreferences.edit {
putString("golemioAPIkey", golemioAPIkey)
}
startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee);
}
},
golemioState = golemioState,
onGolemioStateChange = {
golemioState = it
if (it) {
if (golemioAPIkey.isNotEmpty()) {
sharedPreferences.edit {
putString("golemioAPIkey", golemioAPIkey)
}
startGTFSTracking(callee.getString(R.string.golemio_base_url), golemioAPIkey, callee);
}
} else {
stopGTFSTracking();
}
}
)
}
}
}
@@ -116,7 +277,7 @@ fun MainScreen() {
@Composable
// When a composable with parameters is used with @Preview,
// a default value must be provided for the preview to render.
fun SettingsButton(modifier: Modifier = Modifier, onClick: () -> Unit = {}) {
fun IconButton(modifier: Modifier = Modifier, onClick: () -> Unit = {}, IconID: Int = R.drawable.baseline_settings_24) {
Button(
onClick = { onClick() },
colors = ButtonDefaults.buttonColors(containerColor = Color(0xCFFFFFFF)),
@@ -124,7 +285,7 @@ fun SettingsButton(modifier: Modifier = Modifier, onClick: () -> Unit = {}) {
) {
Icon(
painter = painterResource(id = R.drawable.baseline_settings_24),
painter = painterResource(id = IconID),
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = Color(0xFF000000)
@@ -134,75 +295,137 @@ fun SettingsButton(modifier: Modifier = Modifier, onClick: () -> Unit = {}) {
@Preview
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun ShowSettingsMenu(modifier: Modifier = Modifier, onDismiss: () -> Unit = {}) {
fun ShowSettingsMenu(
modifier: Modifier = Modifier,
onDismiss: () -> Unit = {},
haukUrl: String = "",
onHaukUrlChange: (String) -> Unit = {},
onHaukApply: () -> Unit = {},
haukState: Boolean = false,
onHaukStateChange: (Boolean) -> Unit = {},
golemioAPIkey: String = "",
onGolemioAPIChange: (String) -> Unit = {},
onGolemioApply: () -> Unit = {},
golemioState: Boolean = false,
onGolemioStateChange: (Boolean) -> Unit = {}
) {
ModalBottomSheet(onDismissRequest = onDismiss) {
Column() {
Column( verticalArrangement = Arrangement.spacedBy(10.dp)) {
Row() {
TextField(
state = rememberTextFieldState(),
label = { Text("Target Hauk URL") },
Checkbox(
checked = haukState,
onCheckedChange = { onHaukStateChange(it) }
)
ElevatedButton(onClick = { },modifier = Modifier.padding(start = 5.dp), shape = RectangleShape) {
TextField(
value = haukUrl,
onValueChange = onHaukUrlChange,
label = { Text("Target Hauk URL") },
singleLine = true,
modifier = Modifier
.width(250.dp)
)
ElevatedButton(onClick = onHaukApply, modifier = Modifier.padding(start = 5.dp), shape = RectangleShape) {
Text("Apply")
}
}
Row() {
Checkbox(
checked = golemioState,
onCheckedChange = { onGolemioStateChange(it) }
)
TextField(value = golemioAPIkey, onValueChange = onGolemioAPIChange, label = { Text("Golemio API Key") },
singleLine = true,
modifier = Modifier
.width(250.dp));
ElevatedButton(onClick = onGolemioApply, modifier = Modifier.padding(start = 5.dp), shape = RectangleShape) {
Text("Apply")
}
}
}
}
}}
@Preview
@Composable
fun OpenStreetMapScreen() {
// Create tile stream provider for OpenStreetMap with User-Agent
val tileStreamProvider = TileStreamProvider { row, col, zoomLvl ->
try {
val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png")
val connection = url.openConnection()
// Set User-Agent header as required by OSM tile usage policy
// Format: AppId/Version (Platform) (Contact: email)
val userAgent = "${BuildConfig.APPLICATION_ID}/${BuildConfig.VERSION_NAME} (Android) (Contact: poliecho@pupes.org)"
connection.setRequestProperty("User-Agent", userAgent)
connection.getInputStream()
} catch (e: Exception) {
e.printStackTrace()
null
fun OpenStreetMapScreen(context: Context? = null) {
// Create tile stream provider for OpenStreetMap with caching
val tileStreamProvider = remember(context) {
context?.let {
createCachedTileStreamProvider(it)
} ?: TileStreamProvider { row, col, zoomLvl ->
// Fallback for preview mode without context
try {
val url = URL("https://tile.openstreetmap.org/$zoomLvl/$col/$row.png")
val connection = url.openConnection()
val userAgent = "MHDrunPathfinder/Preview (Android)"
connection.setRequestProperty("User-Agent", userAgent)
connection.getInputStream()
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
// Track if map has been initialized to prevent duplicate operations
var mapInitialized by remember { mutableStateOf(false) }
/* Add a marker at the center of the map */
mapState.addMarker("target_position", x = longitudeToXNormalized(14.4058031), y = latitudeToYNormalized(50.0756083)) {
Icon(
painter = painterResource(id = R.drawable.target),
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = Color(0xFFFF0000)
)
}
/* Add a marker for current position */
mapState.addMarker("current_position", x = longitudeToXNormalized(14.4378), y = latitudeToYNormalized(50.0755)) {
Icon(
painter = painterResource(id = R.drawable.user_location),
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = Color(0xFF0000FF) // Blue color for current position
)
}
// Add the tile layer and set initial position
// Add the tile layer, markers, and set initial position - all in LaunchedEffect to avoid blocking main thread
LaunchedEffect(Unit) {
mapState.addLayer(tileStreamProvider)
if (!mapInitialized) {
// Add lazy loader first
mapState.addLazyLoader("default")
// Scroll to Prague, Czech Republic
// Prague coordinates: latitude 50.0755, longitude 14.4378
val normalizedX = longitudeToXNormalized(14.4378)
val normalizedY = latitudeToYNormalized(50.0755)
// Add tile layer
mapState.addLayer(tileStreamProvider)
// Use a higher scale to zoom in more (closer to 1.0 = more zoomed in)
mapState.scrollTo(normalizedX, normalizedY, destScale = 0.8)
// Add a marker for target position
mapState.addMarker("target_position", x = longitudeToXNormalized(14.0), y = latitudeToYNormalized(50.0), Offset(-0.5f,-0.25f)) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
painter = painterResource(id = R.drawable.target),
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = Color(0xFFFF0000)
)
Surface(
color = Color.Red,
shadowElevation = 4.dp,
shape = MaterialTheme.shapes.small,
modifier = Modifier.padding(1.dp)
) {
Text(
text = target_speed,
modifier = Modifier.padding(1.dp),
style = MaterialTheme.typography.bodyMedium,
fontSize = 8.sp,
color = Color.Blue
)
}
}
}
/* Add a marker for current position */
mapState.addMarker("current_position", x = longitudeToXNormalized(14.0), y = latitudeToYNormalized(50.0),
Offset(-0.5f,-0.5f)
) {
Icon(
painter = painterResource(id = R.drawable.user_location),
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = Color(0xFF004802)
)
}
// Scroll to Prague, Czech Republic
// Prague coordinates: latitude 50.0755, longitude 14.4378
val normalizedX = longitudeToXNormalized(14.4378)
val normalizedY = latitudeToYNormalized(50.0755)
// Use a higher scale to zoom in more (closer to 1.0 = more zoomed in)
mapState.scrollTo(normalizedX, normalizedY, destScale = 0.1)
mapInitialized = true
}
}
// Display the map
@@ -1,170 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
android:width="512dp"
android:height="512dp"
android:viewportWidth="135.47"
android:viewportHeight="135.47">
<path
android:pathData="M0,0h135.47v135.47h-135.47z"
android:strokeWidth="4.99999"
android:fillColor="#aa8800"/>
</vector>
@@ -1,30 +1,48 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
android:width="512dp"
android:height="512dp"
android:viewportWidth="135.47"
android:viewportHeight="135.47">
<path
android:pathData="m12.87,44.98c-1.1,-2.47 -1.39,-5.2 -1.61,-7.87 -0.26,-4.74 -0.15,-9.48 -0.02,-14.22 0.04,-1.85 0.14,-3.7 0.32,-5.54 0.07,-0.69 0.16,-1.33 0.35,-1.99 0.03,-0.05 0.04,-0.12 0.09,-0.16 0.06,-0.05 0.23,0.3 0.07,0.22 -1.1,-0.55 -2.12,-1.25 -3.22,-1.81 -0.13,-0.06 0.2,0.21 0.29,0.32 0.15,0.18 0.28,0.38 0.42,0.57 1.74,2.9 3.83,5.56 6.06,8.11 1,1.14 2.05,2.25 3.12,3.32 0.21,0.21 0.38,0.46 0.63,0.62 1.24,0.8 2.49,1.59 3.82,2.23 0.16,0.08 0.33,-0.09 0.5,-0.14 0.15,-0.27 0.32,-0.52 0.44,-0.81 0.51,-1.29 0.65,-2.74 1.04,-4.07 0.51,-1.76 0.7,-2.06 1.42,-3.77 0.77,-1.57 1.63,-3.11 2.67,-4.52 0.11,-0.15 1,-1.28 1.25,-1.48 0.11,-0.08 0.26,-0.09 0.38,-0.14 -1.07,-0.53 -2.03,-1.35 -3.2,-1.59 -0.33,-0.07 0.2,0.65 0.23,0.99 0.05,0.59 0.04,1.19 0.03,1.78 -0.06,2.64 -0.33,5.28 -0.59,7.91 -0.32,3.38 -0.73,6.76 -1.24,10.11 -0.23,1.15 -0.21,2.29 -0.11,3.45 0.09,0.87 0.14,1.75 0.16,2.63 0.01,0.42 0.01,0.84 0.01,1.27 0,0 3.53,1.79 3.53,1.79v0c-0,-0.43 -0.01,-0.86 -0.02,-1.28 -0.03,-0.89 -0.09,-1.77 -0.18,-2.66 -0.1,-1.14 -0.16,-2.26 0.02,-3.4 0.34,-3.42 0.7,-6.84 0.99,-10.26 0.43,-4.45 0.1,-0.96 0.51,-5.43 0.2,-2.14 0.25,-2.27 0.31,-4.21 0.01,-0.41 0.32,-0.96 0.01,-1.23 -1.07,-0.93 -2.5,-1.34 -3.75,-2.02 -0.7,0.5 -1.12,1.15 -1.6,1.87 -0.99,1.47 -1.84,3.03 -2.62,4.63 -0.62,1.44 -1.04,2.3 -1.51,3.78 -0.41,1.28 -0.59,2.66 -1.11,3.91 -0.07,0.17 -0.19,0.31 -0.28,0.47 -1.33,-0.15 -1.21,-0.18 2.46,1.64 0.28,0.14 -0.46,-0.42 -0.68,-0.63 -1.13,-1.07 -2.22,-2.18 -3.26,-3.35 -2.21,-2.47 -4.28,-5.06 -5.88,-7.98 -0.12,-0.19 -0.25,-0.39 -0.37,-0.58 -0.08,-0.12 -0.12,-0.27 -0.24,-0.35 -0.35,-0.23 -3.34,-3.47 -4.06,-1.63 -0.16,0.71 -0.21,1.4 -0.25,2.13 -0.09,1.88 -0.15,3.76 -0.16,5.64 -0.11,4.73 -0.33,9.47 -0.11,14.2 0.18,2.66 0.47,5.34 1.32,7.89z"
android:strokeWidth="0.529167"
android:fillColor="#000000"/>
<path
android:pathData="m41.24,45.04c0.17,-7.19 0.46,-14.39 0.64,-21.58 0.11,-4.72 0.2,-9.45 0.05,-14.17 0,0 -3.34,-1.68 -3.34,-1.68v0c0.4,4.77 0.41,9.57 0.38,14.35 -0.14,7.1 -0.39,14.21 -1.2,21.27z"
android:strokeWidth="0.529167"
android:fillColor="#ff0000"/>
<path
android:pathData="m42.31,31.69c3.85,-0.37 7.73,-0.29 11.6,-0.27 3.13,0.05 6.26,0.17 9.39,0.29 0,0 -2.87,-2.1 -2.87,-2.1v0c-3.11,-0.08 -6.22,-0.17 -9.34,-0.24 -4.01,-0.06 -8.03,-0.18 -12.03,0.04z"
android:strokeWidth="0.529167"
android:fillColor="#ff0000"/>
<path
android:pathData="m58.57,15.76c1.49,4.69 1.44,9.72 1.48,14.59 -0.06,5.45 -0.41,10.9 -1.12,16.31 -0.07,0.35 -0.13,0.7 -0.2,1.06 0,0 3.38,1.79 3.38,1.79v0c0.05,-0.37 0.09,-0.74 0.14,-1.11 0.45,-5.49 0.77,-10.99 0.85,-16.49 0.01,-4.54 0.08,-9.15 -0.76,-13.64 -0.05,-0.28 -0.15,-0.56 -0.23,-0.84z"
android:strokeWidth="0.529167"
android:fillColor="#ff0000"/>
<path
android:pathData="M77.39,47.99C76.9,41.81 76.72,35.62 76.54,29.43 76.42,25.33 76.27,21.22 76.14,17.12c0.1,0.06 -0.14,-1.27 0.08,-1.17 0.04,0.02 0.07,0.06 0.1,0.09 1.32,0.52 2.58,1.2 3.84,1.85 0.61,0.3 1.11,0.55 1.71,0.87 0.24,0.13 0.94,0.54 0.71,0.4 -0.92,-0.53 -3.67,-2.11 -2.75,-1.58 1.27,0.73 2.55,1.45 3.81,2.19 0.99,0.58 -1.99,-1.14 -2.98,-1.71 0.95,0.65 1.88,1.33 2.83,1.99 2.99,2.23 5.84,4.63 8.61,7.12 1.1,1.03 2.18,2.14 2.7,3.59 0.26,0.73 0.3,1.16 0.43,1.91 0.18,1.75 -0.37,3.3 -1.13,4.84 -1.15,1.98 -2.92,3.42 -4.73,4.77 -1.71,1.31 -3.54,2.43 -5.27,3.71 -1.4,1.2 -2.67,2.58 -4.34,3.41 -1.27,0.53 -2.65,0.19 -3.83,-0.41 -1.9,-1.09 1.73,0.99 2.43,1.37 0.12,0.06 -0.21,-0.17 -0.31,-0.27 -0.23,-0.22 -0.38,-0.46 -0.53,-0.73 -0.2,-0.35 -0.34,-0.71 -0.39,-1.1 -0,-0.47 -0.12,-0.93 -0.25,-1.38 0,0 -3.57,-1.68 -3.57,-1.68v0c0.15,0.42 0.3,0.84 0.27,1.29 0.01,0.45 0.11,0.86 0.34,1.25 0.09,0.19 0.17,0.38 0.28,0.56 1.02,1.65 3.14,2.2 4.79,3.11 1.3,0.51 2.72,0.79 4.03,0.13 1.63,-0.92 2.88,-2.33 4.33,-3.49 1.73,-1.27 3.55,-2.41 5.25,-3.73 1.84,-1.4 3.66,-2.89 4.87,-4.89 0.83,-1.67 1.36,-3.15 1.23,-5.06 -0.1,-0.63 -0.18,-1.39 -0.38,-2 -0.49,-1.54 -1.6,-2.71 -2.75,-3.79 -2.76,-2.46 -5.6,-4.84 -8.5,-7.12 -0.62,-0.45 -1.27,-0.86 -1.87,-1.33 -0.19,-0.14 -0.35,-0.32 -0.54,-0.45 -0.1,-0.07 -0.21,-0.13 -0.32,-0.19 -1.24,-0.73 -2.49,-1.44 -3.72,-2.18 -0.95,-0.57 3.83,2.21 2.87,1.66 -2.09,-1.18 -4.12,-2.46 -6.29,-3.48 -1.23,-0.63 -0.63,-0.32 -1.81,-0.92 -0.72,-0.36 -1.45,-0.82 -2.28,-0.83 -0.73,0.38 -0.44,0.93 -0.39,1.64 0.39,4.17 0.68,8.35 0.86,12.53 0.2,6.1 0.34,12.2 0.36,18.3z"
android:strokeWidth="0.529167"
android:fillColor="#ffff00"/>
<path
android:pathData="m13.25,113.41c-0.35,-2.84 -0.37,-5.72 -0.44,-8.58 -0.12,-6.06 0.06,-12.13 0.29,-18.19 0.08,-0.53 -0.01,-2.93 0.36,-3.4 0.13,-0.16 0.44,-0.01 0.55,0.02 1.86,1.05 -2.19,-1.23 -2.28,-1.28 -0.23,-0.12 0.43,0.3 0.62,0.46 0.6,0.51 1.05,1.15 1.52,1.77 1.29,1.77 2.54,3.56 3.57,5.5 0.64,1.27 1.16,2.64 0.81,4.07 -0.07,0.31 -0.22,0.59 -0.33,0.89 -0.17,0.27 -0.3,0.56 -0.5,0.8 -1.32,1.57 -2.98,1.32 -4.78,0.99 -1.12,-0.25 -2.22,-0.58 -3.26,-1.07 -0.08,-0.04 -0.23,-0.22 -0.22,-0.13 0.02,0.3 0.18,0.57 0.27,0.86 5.56,3.3 10,8.11 14.48,12.68 2.98,3.15 5.93,6.34 8.84,9.55 0,0 3.37,1.41 3.37,1.41v0c-3.07,-3.23 -6.14,-6.47 -9.2,-9.71 -2.5,-2.58 -3.71,-3.88 -6.25,-6.34 -2.35,-2.27 -4.79,-4.49 -7.6,-6.19 -0.27,0.07 -0.56,0.09 -0.81,0.21 -0.06,0.03 0.13,0.05 0.19,0.07 0.18,0.07 0.37,0.14 0.55,0.21 0.53,0.19 1.09,0.38 1.64,0.51 0.35,0.09 0.7,0.15 1.05,0.23 2,0.28 3.64,0.38 5.05,-1.35 0.21,-0.26 0.36,-0.57 0.53,-0.86 0.13,-0.32 0.3,-0.63 0.39,-0.96 0.42,-1.52 -0.04,-2.96 -0.74,-4.3 -1.02,-1.94 -2.24,-3.74 -3.5,-5.53 -0.18,-0.24 -0.75,-1.04 -0.94,-1.28 -1.38,-1.66 -3.67,-3 -5.66,-3.53 -0.37,0.08 -0.58,0.04 -0.8,0.4 -0.4,0.66 -0.13,2.88 -0.21,3.6 0.03,6.09 0.01,12.17 -0.17,18.26 -0.02,2.82 -0.12,5.64 0.06,8.45z"
android:strokeWidth="0.529167"
android:fillColor="#0000ff"/>
<path
android:pathData="m38.7,84.07c0.34,7.18 0.25,14.38 0.21,21.56 0.01,6.71 -0.32,13.41 -0.41,20.12 0.05,0.35 0.06,0.7 0.15,1.04 0.51,1.78 3.54,2.21 4.7,2.36 0.6,0.07 1.07,-0.58 1.52,-0.97 1.49,-1.31 3.13,-3.5 4.33,-5 3.1,-4.3 5.72,-8.94 7.48,-13.95 0.9,-2.56 1.33,-4.38 2.01,-6.98 0.81,-3.38 1.41,-6.8 1.83,-10.25 0,0 -3.38,-1.76 -3.38,-1.76v0c-0.23,3.49 -0.71,6.96 -1.48,10.37 -0.66,2.67 -1.01,4.35 -1.93,6.98 -1.73,4.98 -4.3,9.63 -7.47,13.84 -1.49,1.75 -2.68,3.35 -4.52,4.73 -0.46,0.35 -1.7,0.24 -1.54,0.8 0.27,1 1.52,1.4 2.33,2.05 0.44,0.36 -2.06,-2.07 -0.7,-1.56 -0.33,-6.79 -0.11,-13.6 -0.15,-20.39 0.1,-7.07 0.22,-14.14 0.51,-21.2z"
android:strokeWidth="0.529167"
android:fillColor="#55d400"/>
<path
android:pathData="m74.44,117.26c-0.56,-8.07 -1.22,-16.14 -1.81,-24.22 -0.23,-3.25 -0.45,-6.5 -0.62,-9.75 -0.08,-1.44 -0.15,-2.85 -0.05,-4.29 0.09,-0.54 0.71,0.32 0.52,0.28 -0.84,-0.18 -1.77,-1.46 -2.41,-0.89 -0.57,0.51 0.88,1.24 1.31,1.87 1.6,2.36 2.22,3.39 3.78,5.87 4.01,6.47 7.65,13.15 11.24,19.86 0.24,0.44 3,5.46 3.35,6.08 0.43,0.76 0.86,1.51 1.32,2.25 1.12,1.8 2.25,3.52 5.21,2.92 0.83,-0.17 0.5,-1.62 0.75,-2.43 1.27,-6.7 1.96,-13.5 2.67,-20.28 0,0 -3.22,-1.67 -3.22,-1.67v0c-0.42,6.78 -0.99,13.59 -2.62,20.2 -0.93,2.43 -1.67,2.47 1.63,4.21 0.56,0.29 -0.79,-0.98 -1.15,-1.51 -1.81,-2.67 -3.23,-5.6 -4.84,-8.39C85.9,100.67 82.27,93.98 78.27,87.5 77.17,85.69 75.85,83.54 74.72,81.72 74.33,81.1 74.08,80.37 73.54,79.88 72.29,78.72 70.85,77.76 69.42,76.83 69.25,76.72 69.01,76.72 68.83,76.81c-0.16,0.07 -0.2,0.29 -0.3,0.43 -0,1.49 0.13,2.96 0.27,4.44 0.31,3.31 0.64,6.61 0.94,9.91 0.7,7.93 1.47,15.9 1.19,23.88z"
android:strokeWidth="0.529167"
android:fillColor="#ff6600"/>
<path
android:pathData="M5.92,35.36a31.41,32.73 0,1 0,62.82 0a31.41,32.73 0,1 0,-62.82 0z"
android:strokeWidth="5"
android:fillColor="#00000000"
android:strokeColor="#aa8800"/>
<path
android:pathData="M56.24,59.53 L130.9,131.56"
android:strokeWidth="4.99999"
android:fillColor="#00000000"
android:strokeColor="#aa8800"/>
</vector>
@@ -0,0 +1,5 @@
<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="M240,840Q223,840 211.5,828.5Q200,817 200,800L200,718Q182,698 171,673.5Q160,649 160,620L160,240Q160,157 237,118.5Q314,80 480,80Q652,80 726,117Q800,154 800,240L800,620Q800,649 789,673.5Q778,698 760,718L760,800Q760,817 748.5,828.5Q737,840 720,840L680,840Q663,840 651.5,828.5Q640,817 640,800L640,760L320,760L320,800Q320,817 308.5,828.5Q297,840 280,840L240,840ZM482,200Q592,200 641.5,200Q691,200 706,200L258,200Q276,200 325.5,200Q375,200 482,200ZM640,480L320,480Q287,480 263.5,480Q240,480 240,480L240,480L720,480L720,480Q720,480 696.5,480Q673,480 640,480ZM240,400L720,400L720,280L240,280L240,400ZM340,640Q365,640 382.5,622.5Q400,605 400,580Q400,555 382.5,537.5Q365,520 340,520Q315,520 297.5,537.5Q280,555 280,580Q280,605 297.5,622.5Q315,640 340,640ZM620,640Q645,640 662.5,622.5Q680,605 680,580Q680,555 662.5,537.5Q645,520 620,520Q595,520 577.5,537.5Q560,555 560,580Q560,605 577.5,622.5Q595,640 620,640ZM258,200L706,200Q691,183 641.5,171.5Q592,160 482,160Q375,160 325.5,172.5Q276,185 258,200ZM320,680L640,680Q673,680 696.5,656.5Q720,633 720,600L720,480L240,480L240,600Q240,633 263.5,656.5Q287,680 320,680Z"/>
</vector>
+4
View File
@@ -1,3 +1,7 @@
<resources>
<string name="app_name">MHD run Pathfinder</string>
<string name="routes_path">/v2/gtfs/routes</string>
<string name="stops_path">/v2/gtfs/stops</string>
<string name="golemio_base_url">https://api.golemio.cz</string>
<string name="golemio_realtime_path"></string>
</resources>