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 var gtfsRoutes: GTFSRoutes = mutableListOf() @Serializable data class GTFSStopGeometry( val coordinates: List, 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, val type: String? ) var gtfsStops: GTFSStops = GTFSStops(mutableListOf(), "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(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(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 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 }