Page Menu
Home
GitPull.it
Search
Configure Global Search
Log In
Files
F6994975
D205.1765053881.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
64 KB
Referenced Files
None
Subscribers
None
D205.1765053881.diff
View Options
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/LivePositionsServiceStatus.kt b/app/src/main/java/it/reyboz/bustorino/backend/LivePositionsServiceStatus.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/backend/LivePositionsServiceStatus.kt
@@ -0,0 +1,5 @@
+package it.reyboz.bustorino.backend
+
+enum class LivePositionsServiceStatus {
+ OK,CONNECTING, NO_POSITIONS, ERROR_CONNECTION, ERROR_PARSING_RESPONSE, ERROR_NETWORK_RESPONSE
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsRtPositionsRequest.kt b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsRtPositionsRequest.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsRtPositionsRequest.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsRtPositionsRequest.kt
@@ -24,19 +24,23 @@
import com.android.volley.toolbox.HttpHeaderParser
import com.google.transit.realtime.GtfsRealtime
+import it.reyboz.bustorino.backend.Fetcher
class GtfsRtPositionsRequest(
- errorListener: Response.ErrorListener?,
+ errorListener: ErrorListener,
val listener: RequestListener) :
Request<ArrayList<LivePositionUpdate>>(Method.GET, URL_POSITION, errorListener) {
override fun parseNetworkResponse(response: NetworkResponse?): Response<ArrayList<LivePositionUpdate>> {
if (response == null){
- return Response.error(VolleyError("Null response"))
+ return Response.error(RequestError(Fetcher.Result.PARSER_ERROR))
+ }
+ if (response.statusCode == 404){
+ return Response.error(RequestError(Fetcher.Result.SERVER_ERROR_404))
+ }
+ else if (response.statusCode != 200){
+ return Response.error(RequestError(Fetcher.Result.SERVER_ERROR))
}
-
- if (response.statusCode != 200)
- return Response.error(VolleyError("Error code is ${response.statusCode}"))
val gtfsreq = GtfsRealtime.FeedMessage.parseFrom(response.data)
@@ -58,16 +62,22 @@
listener.onResponse(response)
}
+ override fun parseNetworkError(volleyError: VolleyError?): VolleyError? {
+ return super.parseNetworkError(volleyError)
+ }
+
companion object{
const val URL_POSITION = "http://percorsieorari.gtt.to.it/das_gtfsrt/vehicle_position.aspx"
const val URL_TRIP_UPDATES ="http://percorsieorari.gtt.to.it/das_gtfsrt/trip_update.aspx"
const val URL_ALERTS = "http://percorsieorari.gtt.to.it/das_gtfsrt/alerts.aspx"
- public interface RequestListener{
+ interface RequestListener{
fun onResponse(response: ArrayList<LivePositionUpdate>?)
}
+ fun interface ErrorListener: Response.ErrorListener
}
+ class RequestError(val result: Fetcher.Result): VolleyError()
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
@@ -10,6 +10,7 @@
import com.hivemq.client.mqtt.mqtt3.Mqtt3ClientBuilder
import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish
import it.reyboz.bustorino.BuildConfig
+import it.reyboz.bustorino.backend.LivePositionsServiceStatus
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import org.json.JSONArray
import org.json.JSONException
@@ -198,7 +199,9 @@
responders.remove(wrD)
}
else {
- wrD.get()!!.onUpdateReceived(currentPositions)
+ val resp = wrD.get()!!
+ resp.onStatusUpdate(LivePositionsServiceStatus.OK)
+ resp.onUpdateReceived(currentPositions)
//sent = true
count++
}
@@ -206,6 +209,22 @@
return count
}
+ private fun sendStatusToResponders(status: LivePositionsServiceStatus){
+ val responders = respondersMap
+ for (els in respondersMap.values)
+ for (wrD in els) {
+
+ if (wrD.get() == null) {
+ Log.d(DEBUG_TAG, "Removing weak reference")
+ els.remove(wrD)
+ }
+ else {
+ wrD.get()!!.onStatusUpdate(status)
+ //sent = true
+ }
+ }
+
+ }
/*override fun connectionLost(cause: Throwable?) {
var doReconnect = false
@@ -269,7 +288,7 @@
val vals = topic.split("/")
val lineId = vals[1]
val vehicleId = vals[2]
- val timestamp = (System.currentTimeMillis() / 1000 ) as Long
+ val timestamp = makeUnixTimestamp()
val messString = String(message.payloadAsBytes)
@@ -280,6 +299,7 @@
if(jsonList.get(4)==null){
Log.d(DEBUG_TAG, "We have null tripId: line $lineId veh $vehicleId: $jsonList")
+ sendStatusToResponders(LivePositionsServiceStatus.NO_POSITIONS)
return
}
val posUpdate = LivePositionUpdate(
@@ -334,10 +354,30 @@
//Log.d(DEBUG_TAG, "We have update on line $lineId, vehicle $vehicleId")
} catch (e: JSONException){
Log.w(DEBUG_TAG,"Cannot decipher message on topic $topic, line $lineId, veh $vehicleId (bad JSON)")
+ sendStatusToResponders(LivePositionsServiceStatus.ERROR_PARSING_RESPONSE)
+
} catch (e: Exception){
Log.e(DEBUG_TAG, "Exception occurred", e)
+ sendStatusToResponders(LivePositionsServiceStatus.ERROR_PARSING_RESPONSE)
+ }
+ }
+
+ /**
+ * Remove positions older than `timeMins` minutes
+ */
+ fun clearOldPositions(timeMins: Int){
+ val currentTimeStamp = makeUnixTimestamp()
+ var c = 0
+ for((k, manyp) in currentPositions.entries){
+ for ((t, p) in manyp.entries){
+ if (currentTimeStamp - p.timestamp > timeMins*60){
+ manyp.remove(t)
+ c+=1
+ }
+ }
}
+ Log.d(DEBUG_TAG, "Removed $c positions older than $timeMins minutes")
}
@@ -382,9 +422,11 @@
}
- fun interface MQTTMatoListener{
+ interface MQTTMatoListener{
//positionsMap is a dict with line -> vehicle -> Update
fun onUpdateReceived(posUpdates: PositionsMap)
+
+ fun onStatusUpdate(status: LivePositionsServiceStatus)
}
fun getHTTPHeaders(): HashMap<String,String> {
val headers = HashMap<String,String>()
@@ -412,6 +454,10 @@
//.webSocketWithDefaultConfig()
return r
}
+ private fun makeUnixTimestamp(): Long{
+ val timestamp = (System.currentTimeMillis() / 1000 )
+ return timestamp
+ }
}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/MatoRepository.kt b/app/src/main/java/it/reyboz/bustorino/data/MatoRepository.kt
--- a/app/src/main/java/it/reyboz/bustorino/data/MatoRepository.kt
+++ b/app/src/main/java/it/reyboz/bustorino/data/MatoRepository.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.util.Log
import com.android.volley.Response
+import com.android.volley.toolbox.ClearCacheRequest
import it.reyboz.bustorino.backend.NetworkVolleyManager
import it.reyboz.bustorino.backend.Result
import it.reyboz.bustorino.backend.mato.MatoQueries
@@ -50,6 +51,13 @@
))
}
+ fun clearVolleyCache(){
+ val clearReq = ClearCacheRequest(netVolleyManager.requestQueue.cache){
+ Log.d(DEBUG_TAG, "Volley cache is cleared")
+ }
+ netVolleyManager.addToRequestQueue(clearReq)
+ }
+
fun interface Callback<T> {
fun onResultAvailable(result: Result<T>)
}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt
--- a/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt
+++ b/app/src/main/java/it/reyboz/bustorino/data/MatoTripsDownloadWorker.kt
@@ -21,6 +21,7 @@
import android.content.Context
import android.util.Log
import androidx.work.*
+import com.android.volley.toolbox.ClearCacheRequest
import it.reyboz.bustorino.backend.Notifications
import it.reyboz.bustorino.data.gtfs.GtfsTrip
import java.util.concurrent.CountDownLatch
@@ -30,19 +31,20 @@
override suspend fun doWork(): Result {
- return downloadGtfsTrips()
- }
-
- /**
- * Download GTFS Trips from Mato
- */
- private fun downloadGtfsTrips():Result{
val tripsList = inputData.getStringArray(TRIPS_KEYS)
if (tripsList== null){
Log.e(DEBUG_TAG,"trips list given is null")
return Result.failure()
}
+ return downloadGtfsTrips(tripsList!!)
+ }
+
+ /**
+ * Download GTFS Trips from Mato
+ */
+ private fun downloadGtfsTrips(tripsList: Array<String>):Result{
+
val gtfsRepository = GtfsRepository(applicationContext)
val matoRepository = MatoRepository(applicationContext)
//clear the matoTrips
@@ -52,7 +54,7 @@
val failedMatoTripsDownload = HashSet<String>()
- Log.i(DEBUG_TAG, "Requesting download for the trips")
+ Log.i(DEBUG_TAG, "Requesting download for ${tripsList.size} trips")
val requestCountDown = CountDownLatch(tripsList.size);
for(trip in tripsList){
queriedMatoTrips.add(trip)
@@ -82,6 +84,8 @@
}
requestCountDown.await()
val tripsIDsCompleted = downloadedMatoTrips.map { trip-> trip.tripID }
+ //clear cache to avoid tripping memory
+ matoRepository.clearVolleyCache()
if (tripsIDsCompleted.isEmpty()){
Log.d(DEBUG_TAG, "No trips have been downloaded, set work to fail")
return Result.failure()
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
@@ -40,6 +40,7 @@
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.ViewCompat
+import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
@@ -244,7 +245,7 @@
private val tripMarkersAnimators = HashMap<String, ObjectAnimator>()
- private val liveBusViewModel: LivePositionsViewModel by viewModels()
+ private val liveBusViewModel: LivePositionsViewModel by activityViewModels()
//extra items to use the LibreMap
private lateinit var symbolManager : SymbolManager
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LivePositionsDialogFragment.kt
@@ -0,0 +1,87 @@
+package it.reyboz.bustorino.fragments
+
+import it.reyboz.bustorino.R
+
+import android.app.Dialog
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.ImageButton
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.fragment.app.activityViewModels
+import it.reyboz.bustorino.backend.LivePositionsServiceStatus
+import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
+
+class LivePositionsDialogFragment : DialogFragment() {
+ private val viewModel: LivePositionsViewModel by activityViewModels()
+ private lateinit var providerNameTextView: TextView
+ private lateinit var statusMessageTextView: TextView
+
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ //return super.onCreateDialog(savedInstanceState)
+ val builder = AlertDialog.Builder(requireContext())
+ val view = layoutInflater.inflate(R.layout.fragment_dialog_buspositions, null)
+
+ providerNameTextView = view.findViewById<TextView>(R.id.providerNameTextView)
+ statusMessageTextView = view.findViewById<TextView>(R.id.statusMessageTextView)
+ val btnSwitch = view.findViewById<Button>(R.id.btnSwitch)
+ val btnClose = view.findViewById<ImageButton>(R.id.btnClose)
+
+ // OBSERVE VIEWMODEL
+ /*sharedViewModel.variableTitle.observe(this) {
+ tvTitleVariable.text = it
+ }
+
+ sharedViewModel.statusMessage.observe(this) {
+ tvStatusMessage.text = it
+ }
+
+
+
+ */
+
+ btnSwitch.setOnClickListener {
+ viewModel.switchPositionsSource()
+ }
+
+ btnClose.setOnClickListener { dismiss() }
+
+ builder.setView(view)
+ val res = builder.create()
+ res.window?.setBackgroundDrawableResource(R.color.grey_100)
+ return res
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+
+ viewModel.serviceStatus.observe(this) { serviceStatus ->
+ val message = when (serviceStatus) {
+ LivePositionsServiceStatus.OK -> getString(R.string.live_positions_msg_ok)
+ LivePositionsServiceStatus.NO_POSITIONS -> getString(R.string.live_positions_msg_no_positions)
+ LivePositionsServiceStatus.ERROR_CONNECTION -> getString(R.string.live_positions_msg_connection_error)
+ LivePositionsServiceStatus.ERROR_NETWORK_RESPONSE -> getString(R.string.live_positions_msg_network_error)
+ LivePositionsServiceStatus.ERROR_PARSING_RESPONSE -> getString(R.string.live_positions_msg_parsing_error)
+ LivePositionsServiceStatus.CONNECTING -> getString(R.string.live_positions_msg_connecting)
+
+ }
+ statusMessageTextView.text = message
+ }
+ viewModel.useMQTTPositionsLiveData.observe(this) { useMQTT ->
+ val message = if (useMQTT) {
+ getString(R.string.positions_source_mato_short)
+ } else getString(R.string.positions_source_gtfsrt_short)
+ providerNameTextView.text = message
+ }
+
+ return super.onCreateView(inflater, container, savedInstanceState)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapLibreFragment.kt
@@ -27,23 +27,27 @@
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.gson.JsonObject
import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.LivePositionsServiceStatus
import it.reyboz.bustorino.backend.Stop
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.backend.mato.MQTTMatoClient
import it.reyboz.bustorino.data.PreferencesHolder
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
-import it.reyboz.bustorino.fragments.SettingsFragment.LIVE_POSITIONS_PREF_MQTT_VALUE
import it.reyboz.bustorino.map.MapLibreUtils
import it.reyboz.bustorino.map.MapLibreStyles
import it.reyboz.bustorino.util.Permissions
import it.reyboz.bustorino.util.ViewUtils
import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
import it.reyboz.bustorino.viewmodels.StopsMapViewModel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.camera.CameraUpdateFactory
import org.maplibre.android.geometry.LatLng
@@ -163,8 +167,9 @@
})
//BUS POSITIONS
- private var useMQTTViewModel = true
- private val livePositionsViewModel : LivePositionsViewModel by viewModels()
+ private var usingMQTTPositions = true // THIS IS INSIDE VIEW MODEL NOW
+ private val livePositionsViewModel : LivePositionsViewModel by activityViewModels()
+ private lateinit var busPositionsIconButton: ImageButton
private val positionsByVehDict = HashMap<String, LivePositionUpdate>(5)
private val animatorsByVeh = HashMap<String, ValueAnimator>()
@@ -226,6 +231,10 @@
showUserPositionButton.setOnClickListener(this::switchUserLocationStatus)
followUserButton = rootView.findViewById(R.id.followUserImageButton)
centerUserButton = rootView.findViewById(R.id.centerMapImageButton)
+ busPositionsIconButton = rootView.findViewById(R.id.busPositionsImageButton)
+ busPositionsIconButton.setOnClickListener {
+ LivePositionsDialogFragment().show(parentFragmentManager, "LivePositionsDialog")
+ }
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet)
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
@@ -273,6 +282,43 @@
rootView.findViewById<View>(R.id.btnClose).setOnClickListener {
hideStopBottomSheet()
}
+ livePositionsViewModel.serviceStatus.observe(viewLifecycleOwner){ status ->
+ //if service is active, update the bus positions icon
+ when(status) {
+ LivePositionsServiceStatus.OK ->
+ setBusPositionsIcon(true, error = false)
+
+ LivePositionsServiceStatus.NO_POSITIONS -> setBusPositionsIcon(true, error = true)
+
+ else -> setBusPositionsIcon(true, error = true)
+ }
+ }
+ livePositionsViewModel.useMQTTPositionsLiveData.observe(viewLifecycleOwner){ useMQTT->
+ if (showBusLayer && isResumed) {
+ if(useMQTT!=usingMQTTPositions){
+ // we have to switch
+ val clearPos = PreferenceManager.getDefaultSharedPreferences(requireContext()).getBoolean("positions_clear_on_switch_pref", true)
+ livePositionsViewModel.clearOldPositionsUpdates()
+ if(useMQTT){
+ //switching to MQTT, the GTFS positions are disabled automatically
+ livePositionsViewModel.requestMatoPosUpdates(MQTTMatoClient.LINES_ALL)
+ } else{
+ //switching to GTFS RT: stop Mato, launch first request
+ livePositionsViewModel.stopMatoUpdates()
+ livePositionsViewModel.requestGTFSUpdates()
+ }
+ if (clearPos) {
+ livePositionsViewModel.clearAllPositions()
+ //force clear of the viewed data
+ positionsByVehDict.clear()
+ updatePositionsIcons()
+ }
+
+ }
+ }
+ usingMQTTPositions = useMQTT
+
+ }
Log.d(DEBUG_TAG, "Fragment View Created!")
@@ -342,7 +388,7 @@
mapInitCompleted = true
// we start requesting the bus positions now
- startRequestingPositions()
+ observeBusPositionUpdates()
//Restoring data
var boundsRestored = false
@@ -584,14 +630,19 @@
super.onResume()
mapView.onResume()
- val keySourcePositions = getString(R.string.pref_positions_source)
+ //val keySourcePositions = getString(R.string.pref_positions_source)
if(showBusLayer) {
- useMQTTViewModel = PreferenceManager.getDefaultSharedPreferences(requireContext())
- .getString(keySourcePositions, LIVE_POSITIONS_PREF_MQTT_VALUE)
- .contentEquals(LIVE_POSITIONS_PREF_MQTT_VALUE)
+ //first, clean up all the old positions
+ livePositionsViewModel.clearOldPositionsUpdates()
- if (useMQTTViewModel) livePositionsViewModel.requestMatoPosUpdates(MQTTMatoClient.LINES_ALL)
- else livePositionsViewModel.requestGTFSUpdates()
+ if (livePositionsViewModel.useMQTTPositionsLiveData.value!!){
+ livePositionsViewModel.requestMatoPosUpdates(MQTTMatoClient.LINES_ALL)
+ usingMQTTPositions = true
+ }
+ else {
+ livePositionsViewModel.requestGTFSUpdates()
+ usingMQTTPositions = false
+ }
//mapViewModel.testCascade();
livePositionsViewModel.isLastWorkResultGood.observe(this) { d: Boolean ->
Log.d(
@@ -615,7 +666,7 @@
Log.d(DEBUG_TAG, "Fragment paused")
savedMapStateOnPause = saveMapStateInBundle()
- if (useMQTTViewModel) livePositionsViewModel.stopMatoUpdates()
+ if (livePositionsViewModel.useMQTTPositionsLiveData.value!!) livePositionsViewModel.stopMatoUpdates()
}
@@ -736,14 +787,14 @@
/**
* Start requesting position updates
*/
- private fun startRequestingPositions() {
+ private fun observeBusPositionUpdates() {
livePositionsViewModel.updatesWithTripAndPatterns.observe(viewLifecycleOwner) { data: HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>> ->
Log.d(
DEBUG_TAG,
"Have " + data.size + " trip updates, has Map start finished: " + mapInitCompleted
)
if (mapInitCompleted) updateBusPositionsInMap(data)
- if (!isDetached && !useMQTTViewModel) livePositionsViewModel.requestDelayedGTFSUpdates(
+ if (!isDetached && !livePositionsViewModel.useMQTTPositionsLiveData.value!!) livePositionsViewModel.requestDelayedGTFSUpdates(
3000
)
}
@@ -944,8 +995,11 @@
//throttle updates when user is moving camera
val interval = if(isUserMovingCamera) 150 else 60
if(currentTime - lastUpdateTime < interval){
- //DO NOT UPDATE THE MAP
- return
+ //Defer map update
+ viewLifecycleOwner.lifecycleScope.launch {
+ delay(100)
+ updatePositionsIcons()
+ }
}
val features = ArrayList<Feature>()//stops.mapNotNull { stop ->
//stop.latitude?.let { lat ->
@@ -953,17 +1007,17 @@
for (pos in positionsByVehDict.values){
//if (s.latitude!=null && s.longitude!=null)
val point = Point.fromLngLat(pos.longitude, pos.latitude)
- features.add(
- Feature.fromGeometry(
- point,
- JsonObject().apply {
- addProperty("veh", pos.vehicle)
- addProperty("trip", pos.tripID)
- addProperty("bearing", pos.bearing ?:0.0f)
- addProperty("line", pos.routeID.substringBeforeLast('U'))
- }
- )
+ features.add(
+ Feature.fromGeometry(
+ point,
+ JsonObject().apply {
+ addProperty("veh", pos.vehicle)
+ addProperty("trip", pos.tripID)
+ addProperty("bearing", pos.bearing ?:0.0f)
+ addProperty("line", pos.routeID.substringBeforeLast('U'))
+ }
)
+ )
/*busLabelSymbolsByVeh[pos.vehicle]?.let {
it.latLng = LatLng(pos.latitude, pos.longitude)
symbolsToUpdate.add(it)
@@ -1054,6 +1108,7 @@
setLocationIconEnabled(true)
if (fromClick) Toast.makeText(context, R.string.location_enabled, Toast.LENGTH_SHORT).show()
pendingLocationActivation =false
+ //locationComponent.locationEngine.requestLocationUpdates()
} else {
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
//TODO: show dialog for permission rationale
@@ -1087,6 +1142,17 @@
/**
* Helper method for GUI
*/
+ private fun setBusPositionsIcon(enabled: Boolean, error: Boolean){
+ val ctx = requireContext()
+ if(!enabled)
+ busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_inactive))
+ else if(error)
+ busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_notworking))
+ else
+ busPositionsIconButton.setImageDrawable(ContextCompat.getDrawable(ctx, R.drawable.bus_pos_circle_active))
+
+ }
+
private fun updateFollowingIcon(enabled: Boolean){
if(enabled)
followUserButton.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.walk_circle_active))
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/TestRealtimeGtfsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/TestRealtimeGtfsFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/TestRealtimeGtfsFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/TestRealtimeGtfsFragment.kt
@@ -11,7 +11,9 @@
import android.widget.TextView
import androidx.fragment.app.viewModels
import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.LivePositionsServiceStatus
import it.reyboz.bustorino.backend.mato.MQTTMatoClient
+import it.reyboz.bustorino.backend.mato.PositionsMap
import it.reyboz.bustorino.viewmodels.LivePositionsViewModel
@@ -49,10 +51,16 @@
}
*/
- private val listener = MQTTMatoClient.Companion.MQTTMatoListener{
+ private val listener = object : MQTTMatoClient.Companion.MQTTMatoListener{
+ override fun onUpdateReceived(it: PositionsMap) {
+ messageTextView.text = "Update: ${it}"
+ Log.d("BUSTO-TestMQTT", "Received update $it") }
+
+ override fun onStatusUpdate(status: LivePositionsServiceStatus) {
+ Log.d(DEBUG_TAG, "Status changed into $status")
+ }
+
- messageTextView.text = "Update: ${it}"
- Log.d("BUSTO-TestMQTT", "Received update $it")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -120,5 +128,7 @@
fun newInstance() =
TestRealtimeGtfsFragment().apply {
}
+
+ private const val DEBUG_TAG = "BusTO-TestGTFSRTPos"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/GtfsPositionsViewModel.kt
@@ -22,6 +22,8 @@
import androidx.lifecycle.*
import androidx.work.OneTimeWorkRequest
import com.android.volley.Response
+import it.reyboz.bustorino.backend.Fetcher
+import it.reyboz.bustorino.backend.LivePositionsServiceStatus
import it.reyboz.bustorino.backend.NetworkVolleyManager
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.backend.gtfs.GtfsRtPositionsRequest
@@ -62,10 +64,13 @@
}
}
- private val positionRequestErrorListener = Response.ErrorListener {
- Log.e(DEBUG_TI, "Could not download the update, error:\n"+it.stackTrace)
- }
+ /**
+ * Listener for the errors in downloading positions from GTFS RT
+ */
+ private val positionRequestErrorListener = GtfsRtPositionsRequest.Companion.ErrorListener {
+ Log.e(DEBUG_TI, "Could not download the update", it)
+ }
fun requestUpdates(){
if(positionsRequestRunning.value == null || !positionsRequestRunning.value!!) {
val request = GtfsRtPositionsRequest(positionRequestErrorListener, positionRequestListener)
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LivePositionsViewModel.kt
@@ -20,15 +20,19 @@
import android.app.Application
import android.util.Log
import androidx.lifecycle.*
+import androidx.preference.PreferenceManager
import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.android.volley.DefaultRetryPolicy
-import com.android.volley.Response
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.backend.Fetcher
+import it.reyboz.bustorino.backend.LivePositionsServiceStatus
import it.reyboz.bustorino.backend.NetworkVolleyManager
import it.reyboz.bustorino.backend.gtfs.GtfsRtPositionsRequest
import it.reyboz.bustorino.backend.gtfs.GtfsUtils
import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.backend.mato.MQTTMatoClient
+import it.reyboz.bustorino.backend.mato.PositionsMap
import it.reyboz.bustorino.data.GtfsRepository
import it.reyboz.bustorino.data.MatoPatternsDownloadWorker
import it.reyboz.bustorino.data.MatoTripsDownloadWorker
@@ -40,6 +44,7 @@
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.HashSet
+import androidx.core.content.edit
typealias FullPositionUpdatesMap = HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>
typealias FullPositionUpdate = Pair<LivePositionUpdate, TripAndPatternWithStops?>
@@ -48,7 +53,8 @@
private val gtfsRepo = GtfsRepository(application)
- //private val updates = UpdatesMap()
+ //chain of LiveData objects: raw positions -> tripsIDs -> tripsAndPatternsInDB -> positions with patterns
+ //this contains the raw positions updates received from the service
private val positionsToBeMatchedLiveData = MutableLiveData<ArrayList<LivePositionUpdate>>()
private val netVolleyManager = NetworkVolleyManager.getInstance(application)
@@ -56,7 +62,7 @@
private var mqttClient = MQTTMatoClient()
private var lineListening = ""
- private var lastTimeReceived: Long = 0
+ private var lastTimeMQTTUpdatedPositions: Long = 0
private val gtfsRtRequestRunning = MutableLiveData<Boolean>(false)
@@ -68,6 +74,47 @@
//INPUT FILTER FOR LINE
private var gtfsLineToFilterPos = MutableLiveData<Pair<String,MatoPattern?>>()
+ var serviceStatus = MutableLiveData(LivePositionsServiceStatus.CONNECTING)
+
+ private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(application)
+ private val keySourcePositions = application.getString(R.string.pref_positions_source)
+ val useMQTTPositionsLiveData: MutableLiveData<Boolean> = MutableLiveData(isMQTTPositionsSelected())//= MutableLiveData(false)
+
+ init {
+ sharedPrefs.registerOnSharedPreferenceChangeListener { shp, key ->
+ if(key == keySourcePositions) {
+ val newV = shp.getString(keySourcePositions, LIVE_POS_PREF_MQTT)
+ useMQTTPositionsLiveData.postValue(newV.contentEquals(LIVE_POS_PREF_MQTT))
+ Log.d(DEBUG_TI, "Changed position source to: $newV")
+ }
+
+ }
+ }
+ private val LIVE_POS_PREF_MQTT = application.getString(R.string.positions_source_mqtt)
+ private val LIVE_POS_PREF_GTFSRT = application.getString(R.string.positions_source_gtfsrt)
+ private fun isMQTTPositionsSelected(): Boolean{
+ val useMQTT = sharedPrefs.getString(keySourcePositions, LIVE_POS_PREF_MQTT)
+ .contentEquals(LIVE_POS_PREF_MQTT)
+ Log.d(DEBUG_TI, "Init positions, using MQTT: $useMQTT")
+ return useMQTT
+ }
+
+ /**
+ * Switch provider of live positions from MQTT to GTFSRT and viceversa
+ */
+ fun switchPositionsSource(){
+ val usingMQTT = useMQTTPositionsLiveData.value!!
+ //code that was in the MapLibreFragment
+ useMQTTPositionsLiveData.value = !usingMQTT
+ sharedPrefs.edit(commit = true) {
+ putString(
+ keySourcePositions,
+ if (usingMQTT) LIVE_POS_PREF_GTFSRT else LIVE_POS_PREF_MQTT
+ )
+ }
+ Log.d(DEBUG_TI, "Switched positions source in ViewModel, using MQTT: ${!usingMQTT}")
+ serviceStatus.value = LivePositionsServiceStatus.CONNECTING
+ }
fun setGtfsLineToFilterPos(line: String, pattern: MatoPattern?){
gtfsLineToFilterPos.value = Pair(line, pattern)
}
@@ -91,27 +138,36 @@
/**
* Responder to the MQTT Client
*/
- private val matoPositionListener = MQTTMatoClient.Companion.MQTTMatoListener{
-
- val mupds = ArrayList<LivePositionUpdate>()
- if(lineListening==MQTTMatoClient.LINES_ALL){
- for(sdic in it.values){
- for(update in sdic.values){
- mupds.add(update)
+ private val matoPositionListener = object: MQTTMatoClient.Companion.MQTTMatoListener{
+
+ override fun onUpdateReceived(it: PositionsMap) {
+ val mupds = ArrayList<LivePositionUpdate>()
+ if(lineListening==MQTTMatoClient.LINES_ALL){
+ for(sdic in it.values){
+ for(update in sdic.values){
+ mupds.add(update)
+ }
}
- }
- } else{
- //we're listening to one
- if (it.containsKey(lineListening.trim()) ){
- for(up in it[lineListening]?.values!!){
- mupds.add(up)
+ } else{
+ //we're listening to one
+ if (it.containsKey(lineListening.trim()) ){
+ for(up in it[lineListening]?.values!!){
+ mupds.add(up)
+ }
}
}
+ //avoid updating the positions too often (limit to 0.5 seconds)
+ val time = System.currentTimeMillis()
+ if(lastTimeMQTTUpdatedPositions == (0.toLong()) || (time-lastTimeMQTTUpdatedPositions)>500){
+ positionsToBeMatchedLiveData.postValue(mupds)
+ lastTimeMQTTUpdatedPositions = time
+ }
+ //we have received an update, so set the status to OK
+ serviceStatus.postValue(LivePositionsServiceStatus.OK)
}
- val time = System.currentTimeMillis()
- if(lastTimeReceived == (0.toLong()) || (time-lastTimeReceived)>500){
- positionsToBeMatchedLiveData.postValue(mupds)
- lastTimeReceived = time
+
+ override fun onStatusUpdate(status: LivePositionsServiceStatus) {
+ serviceStatus.postValue(status)
}
}
@@ -140,8 +196,11 @@
}
}
- // unify trips with updates
+ /**
+ * This livedata object contains the final updates with patterns present in the DB
+ */
val updatesWithTripAndPatterns = gtfsTripsPatternsInDB.map { tripPatterns->
+ //TODO: Change the mapping in the final updates, I don't know why the key is the tripID and not the vehicle ID
Log.i(DEBUG_TI, "Mapping trips and patterns")
val mdict = HashMap<String,FullPositionUpdate>()
//missing patterns
@@ -171,7 +230,7 @@
}
}
//have to request download of missing Patterns
- if (routesToDownload.size > 0){
+ if (routesToDownload.isNotEmpty()){
Log.d(DEBUG_TI, "Have ${routesToDownload.size} missing patterns from the DB: $routesToDownload")
//downloadMissingPatterns (ArrayList(routesToDownload))
MatoPatternsDownloadWorker.downloadPatternsForRoutes(routesToDownload.toList(), getApplication())
@@ -180,6 +239,28 @@
return@map mdict
}
+ fun clearOldPositionsUpdates(){
+ //RETURN if the map is null
+ val positionsOld = positionsToBeMatchedLiveData.value ?: return
+
+ val currentTimeSecs = (System.currentTimeMillis() / 1000 )
+ val updatedList = ArrayList<LivePositionUpdate>()
+ for (up in positionsOld){
+ //If the time has passed, remove it
+ if (currentTimeSecs - up.timestamp <= MAX_MINUTES_CLEAR_POSITIONS*60) //TODO decide time limit in minutes
+ updatedList.add(up)
+ }
+ val diff = positionsOld.size - updatedList.size
+ Log.d(DEBUG_TI, "Removed ${diff} positions marked as old")
+ // Re-trigger all the LiveData chain
+ positionsToBeMatchedLiveData.value = updatedList
+ }
+
+ fun clearAllPositions(){
+ positionsToBeMatchedLiveData.postValue(ArrayList())
+ Log.d(DEBUG_TI, "Cleared all positions in LivePositionsViewModel")
+ }
+
//OBSERVE THIS TO GET THE LOCATION UPDATES FILTERED
val filteredLocationUpdates = MediatorLiveData<Pair<FullPositionUpdatesMap, List<String>>>()
init {
@@ -242,6 +323,8 @@
lineListening = line
viewModelScope.launch {
mqttClient.startAndSubscribe(line,matoPositionListener, getApplication())
+ //clear old positions (useful when we are coming back to the map after some time)
+ mqttClient.clearOldPositions(MAX_MINUTES_CLEAR_POSITIONS)
}
@@ -267,25 +350,44 @@
private val gtfsPositionsReqListener = object: GtfsRtPositionsRequest.Companion.RequestListener{
override fun onResponse(response: ArrayList<LivePositionUpdate>?) {
Log.i(DEBUG_TI,"Got response from the GTFS RT server")
- response?.let {it:ArrayList<LivePositionUpdate> ->
+ if (response == null){
+ serviceStatus.postValue(LivePositionsServiceStatus.ERROR_CONNECTION)
+ }
+ else response.let { it:ArrayList<LivePositionUpdate> ->
+ val ss: LivePositionsServiceStatus
if (it.size == 0) {
Log.w(DEBUG_TI,"No position updates from the GTFS RT server")
- return
- }
- else {
+ ss = LivePositionsServiceStatus.NO_POSITIONS
+ } else {
//Log.i(DEBUG_TI, "Posting value to positionsLiveData")
viewModelScope.launch { positionsToBeMatchedLiveData.postValue(it) }
-
+ ss = LivePositionsServiceStatus.OK
}
+ serviceStatus.postValue(ss)
}
gtfsRtRequestRunning.postValue(false)
-
}
}
- private val positionRequestErrorListener = Response.ErrorListener {
+
+ /**
+ * Listener for the errors in downloading positions from GTFS RT
+ */
+ private val positionRequestErrorListener = GtfsRtPositionsRequest.Companion.ErrorListener {
Log.e(DEBUG_TI, "Could not download the update", it)
gtfsRtRequestRunning.postValue(false)
+ if(it is GtfsRtPositionsRequest.RequestError){
+ val status = when(it.result) {
+ Fetcher.Result.OK -> LivePositionsServiceStatus.OK
+ Fetcher.Result.PARSER_ERROR -> LivePositionsServiceStatus.ERROR_PARSING_RESPONSE
+ Fetcher.Result.SERVER_ERROR_404 -> LivePositionsServiceStatus.ERROR_NETWORK_RESPONSE
+ Fetcher.Result.SERVER_ERROR -> LivePositionsServiceStatus.ERROR_NETWORK_RESPONSE
+ Fetcher.Result.CONNECTION_ERROR -> LivePositionsServiceStatus.ERROR_CONNECTION
+ else -> LivePositionsServiceStatus.ERROR_CONNECTION
+ }
+ serviceStatus.postValue(status)
+ } else
+ serviceStatus.postValue(LivePositionsServiceStatus.ERROR_NETWORK_RESPONSE)
}
fun requestGTFSUpdates(){
@@ -351,5 +453,7 @@
private const val DEBUG_TI = "BusTO-LivePosViewModel"
private const val MAX_MINUTES_RETRY = 3
private const val MAX_TIME_RETRY = MAX_MINUTES_RETRY*60*1000 //3 minutes (in milliseconds)
+
+ public const val MAX_MINUTES_CLEAR_POSITIONS = 8
}
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bus_pos_circle_active.xml b/app/src/main/res/drawable/bus_pos_circle_active.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/bus_pos_circle_active.xml
@@ -0,0 +1,20 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/map_toolbar_icon_size"
+ android:height="@dimen/map_toolbar_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:pathData="M0.449,24.048a23.505,23.505 0,1 0,47.009 0a23.505,23.505 0,1 0,-47.009 0z"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1"
+ android:fillColor="@color/map_icon_background_active"
+ android:fillAlpha="0.8"
+ android:strokeColor="#222222"/>
+ <path
+ android:pathData="m32.3,7.3c4.418,0 8,3.582 8,8 0,4.418 -3.582,8 -8,8 -4.418,0 -8,-3.582 -8,-8 0,-4.418 3.582,-8 8,-8zM32.3,16.485c-0.873,0 -1.672,0.314 -2.291,0.836L29.786,17.527 32.3,20.041 34.812,17.525C34.169,16.882 33.281,16.485 32.3,16.485ZM32.3,11.744c-2.139,0 -4.089,0.81 -5.56,2.139l-0.308,0.293 1.678,1.674c1.072,-1.072 2.554,-1.736 4.19,-1.736 1.5,0 2.87,0.557 3.914,1.476l0.277,0.259 1.676,-1.676C36.665,12.673 34.591,11.744 32.3,11.744Z"
+ android:strokeWidth="1.778"
+ android:fillColor="#222222"/>
+ <path
+ android:pathData="M23.263,11.464L23.263,14.414L12.939,14.414l0,13.273l20.647,0l0,-2.95l2.95,0l0,11.798l-1.475,0l0,1.475c0,0.815 -0.66,1.475 -1.475,1.475l-1.475,0c-0.815,0 -1.475,-0.66 -1.475,-1.475L30.637,36.536L15.889,36.536l0,1.475c0,0.815 -0.66,1.475 -1.475,1.475L12.939,39.485C12.125,39.485 11.464,38.825 11.464,38.01L11.464,36.536L9.99,36.536L9.99,24.737L8.515,24.737L8.515,18.838L9.99,18.838L9.99,14.414C9.99,12.785 11.31,11.464 12.939,11.464ZM18.838,30.637L12.939,30.637l0,2.95l5.899,0zM33.586,30.637l-5.899,0l0,2.95l5.899,0z"
+ android:fillColor="#222222"/>
+</vector>
diff --git a/app/src/main/res/drawable/bus_pos_circle_inactive.xml b/app/src/main/res/drawable/bus_pos_circle_inactive.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/bus_pos_circle_inactive.xml
@@ -0,0 +1,20 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/map_toolbar_icon_size"
+ android:height="@dimen/map_toolbar_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:pathData="M0.449,24.048a23.505,23.505 0,1 0,47.009 0a23.505,23.505 0,1 0,-47.009 0z"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1"
+ android:fillColor="@color/map_icon_background_inactive"
+ android:fillAlpha="0.75"
+ android:strokeColor="#222222"/>
+ <path
+ android:pathData="m32.3,7.3c4.418,0 8,3.582 8,8 0,4.418 -3.582,8 -8,8 -4.418,0 -8,-3.582 -8,-8 0,-4.418 3.582,-8 8,-8zM32.3,16.485c-0.873,0 -1.672,0.314 -2.291,0.836L29.786,17.527 32.3,20.041 34.812,17.525C34.169,16.882 33.281,16.485 32.3,16.485ZM32.3,11.744c-2.139,0 -4.089,0.81 -5.56,2.139l-0.308,0.293 1.678,1.674c1.072,-1.072 2.554,-1.736 4.19,-1.736 1.5,0 2.87,0.557 3.914,1.476l0.277,0.259 1.676,-1.676C36.665,12.673 34.591,11.744 32.3,11.744Z"
+ android:strokeWidth="1.778"
+ android:fillColor="#222222"/>
+ <path
+ android:pathData="M23.263,11.464L23.263,14.414L12.939,14.414l0,13.273l20.647,0l0,-2.95l2.95,0l0,11.798l-1.475,0l0,1.475c0,0.815 -0.66,1.475 -1.475,1.475l-1.475,0c-0.815,0 -1.475,-0.66 -1.475,-1.475L30.637,36.536L15.889,36.536l0,1.475c0,0.815 -0.66,1.475 -1.475,1.475L12.939,39.485C12.125,39.485 11.464,38.825 11.464,38.01L11.464,36.536L9.99,36.536L9.99,24.737L8.515,24.737L8.515,18.838L9.99,18.838L9.99,14.414C9.99,12.785 11.31,11.464 12.939,11.464ZM18.838,30.637L12.939,30.637l0,2.95l5.899,0zM33.586,30.637l-5.899,0l0,2.95l5.899,0z"
+ android:fillColor="#222222"/>
+</vector>
diff --git a/app/src/main/res/drawable/bus_pos_circle_notworking.xml b/app/src/main/res/drawable/bus_pos_circle_notworking.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/bus_pos_circle_notworking.xml
@@ -0,0 +1,19 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="@dimen/map_toolbar_icon_size"
+ android:height="@dimen/map_toolbar_icon_size"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:pathData="M0.495,24a23.505,23.505 0,1 0,47.009 0a23.505,23.505 0,1 0,-47.009 0z"
+ android:strokeLineJoin="round"
+ android:strokeWidth="1"
+ android:fillColor="@color/map_icon_background_active"
+ android:strokeColor="#222"
+ android:fillAlpha="0.715968"/>
+ <path
+ android:pathData="M23.309,11.417L23.309,14.366L12.985,14.366l0,13.273l20.647,0l0,-2.95l2.95,0l0,11.798l-1.475,0l0,1.475c0,0.815 -0.66,1.475 -1.475,1.475l-1.475,0c-0.815,0 -1.475,-0.66 -1.475,-1.475L30.683,36.488L15.935,36.488l0,1.475c0,0.815 -0.66,1.475 -1.475,1.475L12.985,39.437C12.171,39.437 11.511,38.777 11.511,37.963L11.511,36.488L10.036,36.488L10.036,24.69L8.561,24.69L8.561,18.79L10.036,18.79L10.036,14.366C10.036,12.737 11.356,11.417 12.985,11.417ZM18.884,30.589L12.985,30.589l0,2.95l5.899,0zM33.632,30.589l-5.899,0l0,2.95l5.899,0z"
+ android:fillColor="#222"/>
+ <path
+ android:pathData="M33.296,23.052C28.74,23.052 25.046,19.359 25.046,14.802 25.046,10.246 28.74,6.552 33.296,6.552c4.556,0 8.25,3.694 8.25,8.25 0,4.556 -3.694,8.25 -8.25,8.25zM32.171,17.277l0,1.65l2.25,0l0,-1.65zM32.171,10.377l0,5.15l2.25,0L34.421,10.377Z"
+ android:fillColor="#222"/>
+</vector>
diff --git a/app/src/main/res/drawable/crosshair_circle_grey.xml b/app/src/main/res/drawable/crosshair_circle_grey.xml
--- a/app/src/main/res/drawable/crosshair_circle_grey.xml
+++ b/app/src/main/res/drawable/crosshair_circle_grey.xml
@@ -2,7 +2,7 @@
android:height="@dimen/map_toolbar_icon_size" android:viewportHeight="48"
android:viewportWidth="48" android:width="@dimen/map_toolbar_icon_size">
- <path android:fillAlpha="0.65" android:fillColor="#c6c6c6" android:pathData="M0.449,24.048a23.505,23.505 0,1 0,47.009 0a23.505,23.505 0,1 0,-47.009 0z" android:strokeColor="#333333" android:strokeLineJoin="round" android:strokeWidth="1.4"/>
+ <path android:fillAlpha="0.75" android:fillColor="#c8c8c8" android:pathData="M0.449,24.048a23.505,23.505 0,1 0,47.009 0a23.505,23.505 0,1 0,-47.009 0z" android:strokeColor="#333333" android:strokeLineJoin="round" android:strokeWidth="1.4"/>
<path android:fillColor="#282828" android:pathData="M22.364,12.661C17.347,13.379 13.379,17.347 12.661,22.364L15.818,22.364l0,3.273L12.661,25.636C13.379,30.653 17.347,34.621 22.364,35.339L22.364,32.182l3.273,0l0,3.157C30.653,34.621 34.621,30.653 35.339,25.636L32.182,25.636l0,-3.273l3.157,0C34.621,17.347 30.653,13.379 25.636,12.661L25.636,15.818L22.364,15.818ZM9.363,22.364C10.117,15.537 15.537,10.117 22.364,9.363L22.364,6l3.273,0L25.636,9.363C32.463,10.117 37.883,15.537 38.637,22.364L42,22.364l0,3.273L38.637,25.636C37.883,32.463 32.463,37.883 25.636,38.637L25.636,42L22.364,42L22.364,38.637C15.537,37.883 10.117,32.463 9.363,25.636L6,25.636L6,22.364ZM27.273,24c0,1.808 -1.465,3.273 -3.273,3.273 -1.808,0 -3.273,-1.465 -3.273,-3.273 0,-1.808 1.465,-3.273 3.273,-3.273 1.808,0 3.273,1.465 3.273,3.273z"/>
diff --git a/app/src/main/res/drawable/location_circlew_grey.xml b/app/src/main/res/drawable/location_circlew_grey.xml
--- a/app/src/main/res/drawable/location_circlew_grey.xml
+++ b/app/src/main/res/drawable/location_circlew_grey.xml
@@ -4,7 +4,7 @@
android:viewportWidth="48"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="0.6"
- android:fillColor="#c6c6c6"
+ android:fillColor="#c8c8c8"
android:pathData="M24.064,23.98m-23.21,0a23.21,23.21 0,1 1,46.42 0a23.21,23.21 0,1 1,-46.42 0"
android:strokeColor="#333333" android:strokeLineJoin="round"
android:strokeWidth="1.4"/>
diff --git a/app/src/main/res/drawable/location_circlew_red.xml b/app/src/main/res/drawable/location_circlew_red.xml
--- a/app/src/main/res/drawable/location_circlew_red.xml
+++ b/app/src/main/res/drawable/location_circlew_red.xml
@@ -4,9 +4,9 @@
android:viewportWidth="48"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="0.75"
- android:fillColor="#f15000"
+ android:fillColor="@color/map_icon_background_active"
android:pathData="M24.064,23.98m-23.21,0a23.21,23.21 0,1 1,46.42 0a23.21,23.21 0,1 1,-46.42 0"
- android:strokeColor="#333333" android:strokeLineJoin="round"
+ android:strokeColor="#222222" android:strokeLineJoin="round"
android:strokeWidth="1.4"/>
<path android:fillColor="#333333"
android:fillType="evenOdd" android:pathData="m23.424,43.817c0,0 0.001,0 0.576,-0.817zM24.576,43.817 L24.58,43.815 24.589,43.808 24.621,43.785c0.027,-0.02 0.067,-0.048 0.117,-0.085 0.101,-0.074 0.246,-0.183 0.43,-0.324 0.368,-0.283 0.889,-0.697 1.513,-1.231 1.247,-1.066 2.91,-2.613 4.575,-4.54C34.564,33.78 38,28.319 38,22.077 38,18.345 36.526,14.766 33.902,12.125 31.277,9.485 27.715,8 24,8 20.285,8 16.723,9.485 14.098,12.125 11.474,14.766 10,18.345 10,22.077c0,6.243 3.436,11.703 6.744,15.529 1.666,1.927 3.329,3.474 4.575,4.54 0.624,0.534 1.145,0.948 1.513,1.231 0.184,0.141 0.329,0.25 0.43,0.324 0.05,0.037 0.09,0.065 0.117,0.085l0.032,0.023 0.009,0.006 0.004,0.003c0.345,0.243 0.808,0.243 1.153,0zM24,43 L24.576,43.817C24.576,43.817 24.576,43.818 24,43ZM29,22c0,2.761 -2.239,5 -5,5 -2.761,0 -5,-2.239 -5,-5 0,-2.761 2.239,-5 5,-5 2.761,0 5,2.239 5,5z"/>
diff --git a/app/src/main/res/drawable/walk_circle_active.xml b/app/src/main/res/drawable/walk_circle_active.xml
--- a/app/src/main/res/drawable/walk_circle_active.xml
+++ b/app/src/main/res/drawable/walk_circle_active.xml
@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true"
android:height="@dimen/map_toolbar_icon_size" android:width="@dimen/map_toolbar_icon_size" android:viewportHeight="48" android:viewportWidth="48" >
- <path android:fillAlpha="0.75" android:fillColor="#f15000" android:pathData="M24.064,23.98m-23.21,0a23.21,23.21 0,1 1,46.42 0a23.21,23.21 0,1 1,-46.42 0"
+ <path android:fillAlpha="0.75" android:fillColor="@color/map_icon_background_active" android:pathData="M24.064,23.98m-23.21,0a23.21,23.21 0,1 1,46.42 0a23.21,23.21 0,1 1,-46.42 0"
android:strokeColor="#333333" android:strokeLineJoin="round" android:strokeWidth="1.4"/>
<path android:fillColor="#333" android:pathData="m16.428,18.116 l5.384,-3.912c0.591,-0.431 1.321,-0.669 2.088,-0.643 1.867,0.046 3.507,1.27 4.077,3.057 0.313,0.981 0.598,1.643 0.856,1.986C30.366,20.641 32.804,21.959 35.55,21.959l0,3.36c-3.653,0 -6.917,-1.666 -9.074,-4.279l-1.172,6.645 3.462,2.905 3.735,10.261L29.344,42 25.917,32.585 20.222,27.806C19.302,27.063 18.804,25.855 19.024,24.608L19.878,19.762 18.741,20.588 15.168,25.506 12.45,23.531 16.399,18.095ZM26.311,12.72c-1.856,0 -3.36,-1.504 -3.36,-3.36 0,-1.856 1.504,-3.36 3.36,-3.36 1.855,0 3.36,1.504 3.36,3.36 0,1.856 -1.504,3.36 -3.36,3.36zM21.319,34.863 L15.92,41.297 13.346,39.137 18.344,33.181 19.598,29.518 22.607,32.038Z"/>
diff --git a/app/src/main/res/drawable/walk_circle_inactive.xml b/app/src/main/res/drawable/walk_circle_inactive.xml
--- a/app/src/main/res/drawable/walk_circle_inactive.xml
+++ b/app/src/main/res/drawable/walk_circle_inactive.xml
@@ -3,8 +3,8 @@
android:height="@dimen/map_toolbar_icon_size" android:width="@dimen/map_toolbar_icon_size"
android:viewportHeight="48" android:viewportWidth="48" >
- <path android:fillAlpha="0.65"
- android:fillColor="#c6c6c6"
+ <path android:fillAlpha="0.75"
+ android:fillColor="@color/map_icon_background_inactive"
android:pathData="M0.449,24.048a23.505,23.505 0,1 0,47.009 0a23.505,23.505 0,1 0,-47.009 0z" android:strokeColor="#333333" android:strokeLineJoin="round"
android:strokeWidth="1.4"/>
diff --git a/app/src/main/res/layout/fragment_dialog_buspositions.xml b/app/src/main/res/layout/fragment_dialog_buspositions.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/layout/fragment_dialog_buspositions.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="15dp"
+ android:orientation="vertical">
+
+
+ <!-- Label fissa (SX) -->
+ <TextView
+ android:id="@+id/tvTitleFixed"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/live_positions_dialog_provider_text"
+ android:textStyle="bold"
+ android:textSize="18sp"
+ android:fontFamily="@font/lato_bold"
+ android:textColor="@color/black_900"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <!-- Label variabile (DX) -->
+ <TextView
+ android:id="@+id/providerNameTextView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Valore"
+ android:textSize="18sp"
+ app:layout_constraintTop_toTopOf="@+id/tvTitleFixed"
+ app:layout_constraintStart_toEndOf="@id/tvTitleFixed"
+ app:layout_constraintBottom_toTopOf="@id/statusCard"
+ android:layout_marginBottom="8dp"
+ android:layout_marginStart="6dp"
+ android:textColor="@color/black"
+ android:fontFamily="@font/lato_regular"
+ android:textAppearance="@style/TextAppearance.AppCompat.Display3"/>
+
+
+ <!-- BOX del messaggio di stato -->
+ <androidx.cardview.widget.CardView
+ android:id="@+id/statusCard"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ app:layout_constraintTop_toBottomOf="@+id/providerNameTextView"
+ app:layout_constraintBottom_toTopOf="@id/btnSwitch"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+
+ android:padding="12dp"
+ app:cardCornerRadius="12dp"
+ app:cardElevation="1dp"
+ app:strokeWidth="1dp"
+ android:layout_marginBottom="16dp"
+ android:backgroundTint="@color/grey_200"
+ app:strokeColor="@color/grey_050">
+
+ <TextView
+ android:id="@+id/statusMessageTextView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Messaggio di stato..."
+ android:fontFamily="@font/lato_regular"
+ android:textSize="16sp"
+ android:textColor="@color/black"
+ android:padding="8dp"
+ android:textAppearance="@style/TextAppearance.AppCompat.Medium"/>
+
+ </androidx.cardview.widget.CardView>
+
+ <!-- Pulsanti -->
+
+
+ <Button
+ android:id="@+id/btnSwitch"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ app:layout_constraintTop_toBottomOf="@+id/statusCard"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ android:backgroundTint="?colorPrimaryDark"
+ android:textColor="@color/white"
+ android:text="@string/live_positions_switch_provider" />
+
+ <ImageButton
+ android:id="@+id/btnClose"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginStart="12dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:src="@drawable/close_white_large"
+ android:backgroundTint="?colorPrimaryDark"
+ android:contentDescription="close"
+
+ />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml
deleted file mode 100644
--- a/app/src/main/res/layout/fragment_map.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <androidx.coordinatorlayout.widget.CoordinatorLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:id="@+id/coord_layout"
- android:layout_alignParentBottom="true"
- android:layout_alignParentEnd="true"
- android:layout_alignParentRight="true"
- android:layout_gravity="bottom|end"
-
- >
- <org.osmdroid.views.MapView android:id="@+id/map"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- </androidx.coordinatorlayout.widget.CoordinatorLayout>
- <ImageButton
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/centerMapImageButton"
- android:src="@drawable/ic_center_map"
- android:layout_alignParentTop="true"
- android:layout_alignParentRight="true"
- android:layout_alignParentEnd="true"
- android:layout_marginTop="10dp"
- android:layout_marginRight="10dp"
- android:background="#00ffffff"
- android:contentDescription="@string/bt_center_map_description"
- android:cropToPadding="true" />
-
- <ImageButton
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:id="@+id/followUserImageButton"
- android:src="@drawable/ic_follow_me"
- android:background="#00ffffff"
- android:contentDescription="@string/bt_follow_me_description"
- android:cropToPadding="true"
- android:layout_below="@+id/centerMapImageButton"
- android:layout_alignLeft="@+id/centerMapImageButton"
- android:layout_alignStart="@+id/centerMapImageButton"
- android:layout_marginTop="10dp" />
-</RelativeLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_map_libre.xml b/app/src/main/res/layout/fragment_map_libre.xml
--- a/app/src/main/res/layout/fragment_map_libre.xml
+++ b/app/src/main/res/layout/fragment_map_libre.xml
@@ -185,16 +185,27 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/followUserImageButton"
- android:src="@drawable/ic_follow_me"
+ android:src="@drawable/walk_circle_inactive"
android:background="#00ffffff"
android:contentDescription="@string/bt_follow_me_description"
- android:cropToPadding="true"
android:layout_marginEnd="1dp"
android:layout_below="@+id/centerMapImageButton"
android:layout_alignStart="@+id/centerMapImageButton"
android:layout_marginTop="7dp" />
+ <ImageButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/busPositionsImageButton"
+ android:src="@drawable/bus_pos_circle_inactive"
+ android:background="#00ffffff"
+ android:contentDescription="@string/bt_follow_me_description"
+ android:layout_marginEnd="1dp"
+
+ android:layout_below="@+id/followUserImageButton"
+ android:layout_alignStart="@+id/followUserImageButton"
+ android:layout_marginTop="7dp" />
</RelativeLayout>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -10,6 +10,7 @@
<color name="blue_620">#2a65e8</color>
<color name="blue_700">#2060dd</color>
<color name="brown_vd">#8A4247</color><!-- #1976D2 -->
+ <color name="dialog_background">#FFF3E0</color>
<color name="blue_mid_2">#2378e8</color>
<color name="blue_c_or_700">#0079f5</color>
@@ -29,6 +30,8 @@
<color name="grey_100">#F5F5F5</color>
<color name="grey_200">#dddddd</color>
<color name="grey_050">#f8f8f8</color>
+ <color name="grey_400">#bababa</color>
+ <color name="grey_500">#acacac</color>
<color name="grey_600">#757575</color>
<color name="grey_700">#444</color>
<color name="grey_800">#353535</color>
@@ -40,6 +43,9 @@
<color name="red_darker">#b30000</color>
<color name="red_orange">#dd441f</color>
<color name="red_dark">#b30d0d</color>
+ <color name="orange_icons">#f15000</color>
+ <color name="orange_icons_10light">#f2621a</color>
+ <color name="orange_icons_20light">#f47333</color>
<color name="blue_extraurbano">#2060DD</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
@@ -63,4 +69,6 @@
<color name="urban_bus_bg">@color/orange_500</color>
<color name="extraurban_bus_bg">@color/blue_extraurbano</color>
<color name="metro_bg">@color/metro_red</color>
+ <color name="map_icon_background_active">@color/orange_icons_10light</color>
+ <color name="map_icon_background_inactive">@color/grey_400</color>
</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
--- a/app/src/main/res/values/keys.xml
+++ b/app/src/main/res/values/keys.xml
@@ -3,6 +3,9 @@
tools:ignore="MissingTranslation">
<string name="pref_layout" translatable="false">layout_pref</string>
<string name="pref_update_db_now" translatable="false">pref_update_db_now</string>
+ <string name="positions_source_mqtt" translatable="false">mqtt</string>
+ <string name="positions_source_gtfsrt" translatable="false">gtfsrt</string>
+
<array name="first_screen_values">
<item>arrivals</item>
@@ -26,8 +29,8 @@
<string name="pref_positions_source">pref_positions_source</string>
<array name="positions_source_values">
- <item>mqtt</item>
- <item>gtfsrt</item>
+ <item>@string/positions_source_mqtt</item>
+ <item>@string/positions_source_gtfsrt</item>
</array>
<!-- MapLibre Preferences -->
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -360,6 +360,17 @@
<string name="showing_same_direction">Direction is already shown</string>
<string name="destination_loading">Loading destination…</string>
<string name="destination_unknown">Destination unknown</string>
-
+ <!-- LIVE POSITIONS DIALOG -->
+ <string name="live_positions_msg_ok">Service for positions working normally</string>
+ <string name="live_positions_msg_no_positions">No positions received</string>
+ <string name="live_positions_msg_connection_error">Error connecting to the server</string>
+ <string name="live_positions_msg_parsing_error">Error parsing the server response</string>
+ <string name="live_positions_msg_network_error">Error: network response is not as expected</string>
+ <string name="live_positions_msg_connecting">Connecting...</string>
+ <string name="positions_source_mato_short">MaTO</string>
+ <string name="positions_source_gtfsrt_short">GTFS RT</string>
+ <string name="live_positions_dialog_provider_text">Live positions source:</string>
+ <string name="live_positions_switch_provider">Switch source</string>
+ <string name="live_positions_preference_switch_clear_title">Clear positions when switching live positions source</string>
</resources>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -31,6 +31,13 @@
android:summary="%s"
/>
+ <!-- NEVER EVER CHANGE THE KEY PROPERTIES -->
+ <androidx.preference.CheckBoxPreference
+ android:defaultValue="true"
+ android:key="positions_clear_on_switch_pref"
+ android:title="@string/live_positions_preference_switch_clear_title"
+ app:singleLineTitle="false"
+ />
</androidx.preference.PreferenceCategory>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Dec 6, 21:44 (15 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1425479
Default Alt Text
D205.1765053881.diff (64 KB)
Attached To
Mode
D205: Add dialog to change the positions source in-map
Attached
Detach File
Event Timeline
Log In to Comment