Page MenuHomeGitPull.it

D126.1773471420.diff
No OneTemporary

Size
170 KB
Referenced Files
None
Subscribers
None

D126.1773471420.diff

diff --git a/app/build.gradle b/app/build.gradle
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@
defaultConfig {
applicationId "it.reyboz.bustorino"
- minSdkVersion 16
+ minSdkVersion 21
targetSdkVersion 33
versionCode 48
versionName "1.19.1"
@@ -68,15 +68,18 @@
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation "androidx.activity:activity:$activity_version"
- implementation "androidx.annotation:annotation:1.3.0"
+ implementation "androidx.annotation:annotation:1.6.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.preference:preference:$preference_version"
+
implementation "androidx.work:work-runtime:$work_version"
+ implementation "androidx.work:work-runtime-ktx:$work_version"
+
- implementation "com.google.android.material:material:1.5.0"
- implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+ implementation "com.google.android.material:material:1.9.0"
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
@@ -91,7 +94,11 @@
implementation "ch.acra:acra-mail:$acra_version"
implementation "ch.acra:acra-dialog:$acra_version"
// google transit realtime
- implementation 'com.google.protobuf:protobuf-java:3.14.0'
+ implementation 'com.google.protobuf:protobuf-java:3.17.2'
+ // mqtt library
+ implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
+ implementation 'com.github.hannesa2:paho.mqtt.android:3.5.3'
+
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
@@ -103,8 +110,8 @@
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
// Room components
+ implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
- implementation "androidx.work:work-runtime-ktx:$work_version"
kapt "androidx.room:room-compiler:$room_version"
//multidex - we need this to build the app
implementation "androidx.multidex:multidex:$multidex_version"
@@ -114,12 +121,12 @@
testImplementation 'junit:junit:4.12'
implementation 'junit:junit:4.12'
- implementation "androidx.test.ext:junit:1.1.3"
+ implementation "androidx.test.ext:junit:1.1.5"
implementation "androidx.test:core:$androidXTestVersion"
implementation "androidx.test:runner:$androidXTestVersion"
implementation "androidx.room:room-testing:$room_version"
- androidTestImplementation "androidx.test.ext:junit:1.1.3"
+ androidTestImplementation "androidx.test.ext:junit:1.1.5"
androidTestImplementation "androidx.test:core:$androidXTestVersion"
androidTestImplementation "androidx.test:runner:$androidXTestVersion"
androidTestImplementation "androidx.test:rules:$androidXTestVersion"
diff --git a/app/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java b/app/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
--- a/app/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
+++ b/app/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
@@ -25,8 +25,7 @@
public GtfsDBMigrationsTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
- GtfsDatabase.class.getCanonicalName(),
- new FrameworkSQLiteOpenHelperFactory());
+ GtfsDatabase.class.getCanonicalName());
}
@Test
diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java b/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
--- a/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
+++ b/app/src/main/java/it/reyboz/bustorino/ActivityExperiments.java
@@ -18,12 +18,14 @@
package it.reyboz.bustorino;
import android.os.Bundle;
+import android.util.Log;
import androidx.appcompat.app.ActionBar;
-import it.reyboz.bustorino.fragments.LinesDetailFragment;
-import it.reyboz.bustorino.fragments.TestRealtimeGtfsFragment;
+import androidx.fragment.app.FragmentTransaction;
+import it.reyboz.bustorino.backend.Stop;
+import it.reyboz.bustorino.fragments.*;
import it.reyboz.bustorino.middleware.GeneralActivity;
-public class ActivityExperiments extends GeneralActivity {
+public class ActivityExperiments extends GeneralActivity implements CommonFragmentListener {
final static String DEBUG_TAG = "ExperimentsGTFS";
@@ -40,15 +42,51 @@
if (savedInstanceState==null) {
getSupportFragmentManager().beginTransaction()
.setReorderingAllowed(true)
- /*
- .add(R.id.fragment_container_view, LinesDetailFragment.class,
- LinesDetailFragment.Companion.makeArgs("gtt:56U"))
- .commit();
- */
- .add(R.id.fragment_container_view, LinesDetailFragment.class,
- LinesDetailFragment.Companion.makeArgs("gtt:10U"))
+ /* .add(R.id.fragment_container_view, LinesDetailFragment.class,
+
+ LinesDetailFragment.Companion.makeArgs("gtt:4U"))
+
+ */
+ .add(R.id.fragment_container_view, LinesGridShowingFragment.class, null)
.commit();
+
+ //.add(R.id.fragment_container_view, LinesDetailFragment.class,
+ // LinesDetailFragment.Companion.makeArgs("gtt:4U"))
+ //.add(R.id.fragment_container_view, TestRealtimeGtfsFragment.class, null)
+ //.commit();
}
}
+
+ @Override
+ public void showFloatingActionButton(boolean yes) {
+ Log.d(DEBUG_TAG, "Asked to show the action button");
+ }
+
+ @Override
+ public void readyGUIfor(FragmentKind fragmentType) {
+ Log.d(DEBUG_TAG, "Asked to prepare the GUI for fragmentType "+fragmentType);
+ }
+
+ @Override
+ public void requestArrivalsForStopID(String ID) {
+
+ }
+
+ @Override
+ public void showMapCenteredOnStop(Stop stop) {
+
+ }
+ @Override
+ public void showLineOnMap(String routeGtfsId){
+
+ readyGUIfor(FragmentKind.LINES);
+ FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
+ tr.replace(R.id.fragment_container_view, LinesDetailFragment.class,
+ LinesDetailFragment.Companion.makeArgs(routeGtfsId));
+ tr.addToBackStack("LineonMap-"+routeGtfsId);
+ tr.commit();
+
+
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java
--- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java
@@ -521,15 +521,16 @@
private static void showLinesFragment(@NonNull FragmentManager fragmentManager, boolean addToBackStack, @Nullable Bundle fragArgs){
FragmentTransaction ft = fragmentManager.beginTransaction();
- Fragment f = fragmentManager.findFragmentByTag(LinesFragment.FRAGMENT_TAG);
+ Fragment f = fragmentManager.findFragmentByTag(LinesGridShowingFragment.FRAGMENT_TAG);
if(f!=null){
- ft.replace(R.id.mainActContentFrame, f, LinesFragment.FRAGMENT_TAG);
+ ft.replace(R.id.mainActContentFrame, f, LinesGridShowingFragment.FRAGMENT_TAG);
}else{
//use new method
- ft.replace(R.id.mainActContentFrame,LinesFragment.class,fragArgs,LinesFragment.FRAGMENT_TAG);
+ ft.replace(R.id.mainActContentFrame,LinesGridShowingFragment.class,fragArgs,
+ LinesGridShowingFragment.FRAGMENT_TAG);
}
if (addToBackStack)
- ft.addToBackStack("lines");
+ ft.addToBackStack("linesGrid");
ft.setReorderingAllowed(true)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
@@ -655,6 +656,19 @@
mNavView.setCheckedItem(R.id.nav_arrivals);
}
+ @Override
+ public void showLineOnMap(String routeGtfsId){
+
+ readyGUIfor(FragmentKind.LINES);
+
+ FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
+ tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class,
+ LinesDetailFragment.Companion.makeArgs(routeGtfsId));
+ tr.addToBackStack("LineonMap-"+routeGtfsId);
+ tr.commit();
+
+
+ }
@Override
public void toggleSpinner(boolean state) {
diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/RouteAdapter.kt b/app/src/main/java/it/reyboz/bustorino/adapters/RouteAdapter.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/adapters/RouteAdapter.kt
@@ -0,0 +1,59 @@
+package it.reyboz.bustorino.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.cardview.widget.CardView
+import androidx.recyclerview.widget.RecyclerView
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import java.lang.ref.WeakReference
+
+class RouteAdapter(val routes: List<GtfsRoute>,
+ click: onItemClick,
+ private val layoutId: Int = R.layout.line_title_header) :
+ RecyclerView.Adapter<RouteAdapter.ViewHolder>()
+{
+ val clickreference: WeakReference<onItemClick>
+ init {
+ clickreference = WeakReference(click)
+ }
+
+ class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val descrptionTextView: TextView
+ val nameTextView : TextView
+ val innerCardView : CardView?
+ init {
+ // Define click listener for the ViewHolder's View
+ nameTextView = view.findViewById(R.id.lineShortNameTextView)
+ descrptionTextView = view.findViewById(R.id.lineDirectionTextView)
+ innerCardView = view.findViewById(R.id.innerCardView)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(parent.context)
+ .inflate(layoutId, parent, false)
+
+ return ViewHolder(view)
+ }
+
+ override fun getItemCount() = routes.size
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ // Get element from your dataset at this position and replace the
+ // contents of the view with that element
+ val route = routes[position]
+ holder.nameTextView.text = route.shortName
+ holder.descrptionTextView.text = route.longName
+
+ holder.itemView.setOnClickListener{
+ clickreference.get()?.onRouteItemClicked(route)
+ }
+ }
+
+ fun interface onItemClick{
+ fun onRouteItemClicked(gtfsRoute: GtfsRoute)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java
--- a/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java
+++ b/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java
@@ -53,7 +53,7 @@
final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
//sort the stops by distance
if(stops != null && stops.size() > 0)
- Collections.sort(stops,new StopSorterByDistance(userPosition));
+ Collections.sort(stops,new StopSorterByDistance(userPosition));
return new SquareViewHolder(view);
}
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
@@ -23,13 +23,12 @@
import com.android.volley.VolleyError
import com.android.volley.toolbox.HttpHeaderParser
import com.google.transit.realtime.GtfsRealtime
-import com.google.transit.realtime.GtfsRealtime.VehiclePosition
class GtfsRtPositionsRequest(
errorListener: Response.ErrorListener?,
val listener: RequestListener) :
- Request<ArrayList<GtfsPositionUpdate>>(Method.GET, URL_POSITION, errorListener) {
- override fun parseNetworkResponse(response: NetworkResponse?): Response<ArrayList<GtfsPositionUpdate>> {
+ 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"))
}
@@ -39,13 +38,13 @@
val gtfsreq = GtfsRealtime.FeedMessage.parseFrom(response.data)
- val positionList = ArrayList<GtfsPositionUpdate>()
+ val positionList = ArrayList<LivePositionUpdate>()
if (gtfsreq.hasHeader() && gtfsreq.entityCount>0){
for (i in 0 until gtfsreq.entityCount){
val entity = gtfsreq.getEntity(i)
if (entity.hasVehicle()){
- positionList.add(GtfsPositionUpdate(entity.vehicle))
+ positionList.add(LivePositionUpdate(entity.vehicle))
}
}
}
@@ -53,7 +52,7 @@
return Response.success(positionList, HttpHeaderParser.parseCacheHeaders(response))
}
- override fun deliverResponse(response: ArrayList<GtfsPositionUpdate>?) {
+ override fun deliverResponse(response: ArrayList<LivePositionUpdate>?) {
listener.onResponse(response)
}
@@ -64,9 +63,9 @@
const val URL_ALERTS = "http://percorsieorari.gtt.to.it/das_gtfsrt/alerts.aspx"
public interface RequestListener{
- fun onResponse(response: ArrayList<GtfsPositionUpdate>?)
+ fun onResponse(response: ArrayList<LivePositionUpdate>?)
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsPositionUpdate.kt b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
rename from app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsPositionUpdate.kt
rename to app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/gtfs/GtfsPositionUpdate.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/gtfs/LivePositionUpdate.kt
@@ -21,39 +21,45 @@
import com.google.transit.realtime.GtfsRealtime.VehiclePosition
import com.google.transit.realtime.GtfsRealtime.VehiclePosition.OccupancyStatus
-data class GtfsPositionUpdate(
- val tripID: String,
- val startTime: String,
- val startDate: String,
+data class LivePositionUpdate(
+ val tripID: String, //tripID WITHOUT THE "gtt:" prefix
+ val startTime: String?,
+ val startDate: String?,
val routeID: String,
+ val vehicle: String,
- val latitude: Float,
- val longitude: Float,
- val bearing: Float,
+ val latitude: Double,
+ val longitude: Double,
+ val bearing: Float?,
val timestamp: Long,
- val vehicleInfo: VehicleInfo,
+ val nextStop: String?,
+
+ /*val vehicleInfo: VehicleInfo,
val occupancyStatus: OccupancyStatus?,
val scheduleRelationship: ScheduleRelationship?
+ */
){
constructor(position: VehiclePosition) : this(
position.trip.tripId,
position.trip.startTime,
position.trip.startDate,
position.trip.routeId,
- position.position.latitude,
- position.position.longitude,
+ position.vehicle.label,
+
+ position.position.latitude.toDouble(),
+ position.position.longitude.toDouble(),
position.position.bearing,
position.timestamp,
- VehicleInfo(position.vehicle.id, position.vehicle.label),
- position.occupancyStatus,
null
)
- data class VehicleInfo(
+ /*data class VehicleInfo(
val id: String,
val label:String
)
+
+ */
}
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
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
@@ -0,0 +1,323 @@
+package it.reyboz.bustorino.backend.mato
+
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.LifecycleOwner
+import info.mqtt.android.service.Ack
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import org.eclipse.paho.client.mqttv3.*
+import info.mqtt.android.service.MqttAndroidClient
+import info.mqtt.android.service.QoS
+
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence
+import org.json.JSONArray
+import org.json.JSONException
+import java.lang.ref.WeakReference
+import java.util.ArrayList
+import java.util.Properties
+
+typealias PositionsMap = HashMap<String, HashMap<String, LivePositionUpdate> >
+
+class MQTTMatoClient private constructor(): MqttCallbackExtended{
+
+ private var isStarted = false
+ private var subscribedToAll = false
+
+ private lateinit var client: MqttAndroidClient
+ //private var clientID = ""
+
+ private val respondersMap = HashMap<String, ArrayList<WeakReference<MQTTMatoListener>>>()
+
+ private val currentPositions = PositionsMap()
+
+ private lateinit var lifecycle: LifecycleOwner
+ private var context: Context?= null
+
+ private fun connect(context: Context, iMqttActionListener: IMqttActionListener?){
+
+ val clientID = "mqttjs_${getRandomString(8)}"
+
+ client = MqttAndroidClient(context,SERVER_ADDR,clientID,Ack.AUTO_ACK)
+
+ val options = MqttConnectOptions()
+ //options.sslProperties =
+ options.isCleanSession = true
+ val headersPars = Properties()
+ headersPars.setProperty("Origin","https://mato.muoversiatorino.it")
+ headersPars.setProperty("Host","mapi.5t.torino.it")
+ options.customWebSocketHeaders = headersPars
+
+ //actually connect
+ client.connect(options,null, iMqttActionListener)
+ isStarted = true
+ client.setCallback(this)
+
+ if (this.context ==null)
+ this.context = context.applicationContext
+ }
+
+
+ override fun connectComplete(reconnect: Boolean, serverURI: String?) {
+ Log.d(DEBUG_TAG, "Connected to server, reconnect: $reconnect")
+ Log.d(DEBUG_TAG, "Have listeners: $respondersMap")
+ }
+
+ fun startAndSubscribe(lineId: String, responder: MQTTMatoListener, context: Context): Boolean{
+ //start the client, and then subscribe to the topic
+ val topic = mapTopic(lineId)
+ synchronized(this) {
+ if(!isStarted){
+ connect(context, object : IMqttActionListener{
+ override fun onSuccess(asyncActionToken: IMqttToken?) {
+ val disconnectedBufferOptions = DisconnectedBufferOptions()
+ disconnectedBufferOptions.isBufferEnabled = true
+ disconnectedBufferOptions.bufferSize = 100
+ disconnectedBufferOptions.isPersistBuffer = false
+ disconnectedBufferOptions.isDeleteOldestMessages = false
+ client.setBufferOpts(disconnectedBufferOptions)
+ client.subscribe(topic, QoS.AtMostOnce.value)
+ }
+
+ override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
+ Log.e(DEBUG_TAG, "FAILED To connect to the server")
+ }
+
+ })
+ //wait for connection
+ } else {
+ client.subscribe(topic, QoS.AtMostOnce.value)
+ }
+ }
+
+
+
+ synchronized(this){
+ if (!respondersMap.contains(lineId))
+ respondersMap[lineId] = ArrayList()
+ respondersMap[lineId]!!.add(WeakReference(responder))
+ Log.d(DEBUG_TAG, "Add MQTT Listener for line $lineId, topic $topic")
+ }
+
+ return true
+ }
+
+ fun desubscribe(responder: MQTTMatoListener){
+ var removed = false
+ for ((line,v)in respondersMap.entries){
+ var done = false
+ for (el in v){
+ if (el.get()==null){
+ v.remove(el)
+ } else if(el.get() == responder){
+ v.remove(el)
+ done = true
+ }
+ if (done)
+ break
+ }
+ if(done) Log.d(DEBUG_TAG, "Removed one listener for line $line, listeners: $v")
+ //if (done) break
+ if (v.isEmpty()){
+ //actually unsubscribe
+ client.unsubscribe( mapTopic(line))
+ }
+ removed = done || removed
+ }
+ Log.d(DEBUG_TAG, "Removed: $removed, respondersMap: $respondersMap")
+ }
+ fun getPositions(): PositionsMap{
+ return currentPositions
+ }
+
+ fun sendUpdateToResponders(responders: ArrayList<WeakReference<MQTTMatoListener>>): Boolean{
+ var sent = false
+ for (wrD in responders)
+ if (wrD.get() == null)
+ responders.remove(wrD)
+ else {
+ wrD.get()!!.onUpdateReceived(currentPositions)
+ sent = true
+ }
+ return sent
+ }
+
+ override fun connectionLost(cause: Throwable?) {
+ Log.w(DEBUG_TAG, "Lost connection in MQTT Mato Client")
+
+
+ synchronized(this){
+ // isStarted = false
+ //var i = 0
+ // while(i < 20 && !isStarted) {
+ connect(context!!, object: IMqttActionListener{
+ override fun onSuccess(asyncActionToken: IMqttToken?) {
+ //relisten to messages
+ for ((line,elms) in respondersMap.entries){
+ val topic = mapTopic(line)
+ if(elms.isEmpty())
+ respondersMap.remove(line)
+ else
+ client.subscribe(topic, QoS.AtMostOnce.value, null, null)
+ }
+ Log.d(DEBUG_TAG, "Reconnected to MQTT Mato Client")
+
+ }
+
+ override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
+ Log.w(DEBUG_TAG, "Failed to reconnect to MQTT server")
+ }
+ })
+
+ }
+
+
+ }
+
+ override fun messageArrived(topic: String?, message: MqttMessage?) {
+ if (topic==null || message==null) return
+
+ parseMessageAndAddToList(topic, message)
+ //GlobalScope.launch { }
+
+ }
+
+ private fun parseMessageAndAddToList(topic: String, message: MqttMessage){
+
+ val vals = topic.split("/")
+ val lineId = vals[1]
+ val vehicleId = vals[2]
+ val timestamp = (System.currentTimeMillis() / 1000 ) as Long
+
+ val messString = String(message.payload)
+
+
+ try {
+ val jsonList = JSONArray(messString)
+ //val full = if(jsonList.length()>7) {
+ // if (jsonList.get(7).equals(null)) null else jsonList.getInt(7)
+ //}else null
+ /*val posUpdate = MQTTPositionUpdate(lineId+"U", vehicleId,
+ jsonList.getDouble(0),
+ jsonList.getDouble(1),
+ if(jsonList.get(2).equals(null)) null else jsonList.getInt(2),
+ if(jsonList.get(3).equals(null)) null else jsonList.getInt(3),
+ if(jsonList.get(4).equals(null)) null else jsonList.getString(4)+"U",
+ if(jsonList.get(5).equals(null)) null else jsonList.getInt(5),
+ if(jsonList.get(6).equals(null)) null else jsonList.getInt(6),
+ //full
+ )
+
+ */
+ if(jsonList.get(4)==null){
+ Log.d(DEBUG_TAG, "We have null tripId: line $lineId veh $vehicleId: $jsonList")
+ return
+ }
+ val posUpdate = LivePositionUpdate(
+ jsonList.getString(4)+"U",
+ null,
+ null,
+ lineId+"U",
+ vehicleId,
+ jsonList.getDouble(0), //latitude
+ jsonList.getDouble(1), //longitude
+ if(jsonList.get(2).equals(null)) null else jsonList.getInt(2).toFloat(), //"heading" (same as bearing?)
+ timestamp,
+ if(jsonList.get(6).equals(null)) null else jsonList.getInt(6).toString() //nextStop
+ )
+
+ //add update
+ var valid = false
+ if(!currentPositions.contains(lineId))
+ currentPositions[lineId] = HashMap()
+ currentPositions[lineId]?.let{
+ it[vehicleId] = posUpdate
+ valid = true
+ }
+ var sent = false
+ if (LINES_ALL in respondersMap.keys) {
+ sent = sendUpdateToResponders(respondersMap[LINES_ALL]!!)
+
+
+ }
+ if(lineId in respondersMap.keys){
+ sent = sendUpdateToResponders(respondersMap[lineId]!!) or sent
+
+ }
+ if(!sent){
+ Log.w(DEBUG_TAG, "We have received an update but apparently there is no one to send it")
+ var emptyResp = true
+ for(en in respondersMap.values){
+ if(!en.isEmpty()){
+ emptyResp=false
+ break
+ }
+ }
+ //try unsubscribing to all
+ if(emptyResp) {
+ Log.d(DEBUG_TAG, "Unsubscribe all")
+ client.unsubscribe(LINES_ALL)
+ }
+ }
+ //Log.d(DEBUG_TAG, "We have update on line $lineId, vehicle $vehicleId")
+ } catch (e: JSONException){
+ Log.e(DEBUG_TAG,"Cannot decipher message on topic $topic, line $lineId, veh $vehicleId")
+ e.printStackTrace()
+ }
+ }
+
+
+ override fun deliveryComplete(token: IMqttDeliveryToken?) {
+ //NOT USED (we're not sending any messages)
+ }
+
+
+ companion object{
+
+ const val SERVER_ADDR="wss://mapi.5t.torino.it:443/scre"
+ const val LINES_ALL="ALL"
+ private const val DEBUG_TAG="BusTO-MatoMQTT"
+ @Volatile
+ private var instance: MQTTMatoClient? = null
+
+ fun getInstance() = instance?: synchronized(this){
+ instance?: MQTTMatoClient().also { instance= it }
+ }
+
+ @JvmStatic
+ fun mapTopic(lineId: String): String{
+ return if(lineId== LINES_ALL || lineId == "#")
+ "#"
+ else{
+ "/${lineId}/#"
+ }
+ }
+
+ fun getRandomString(length: Int) : String {
+ val allowedChars = ('a'..'f') + ('0'..'9')
+ return (1..length)
+ .map { allowedChars.random() }
+ .joinToString("")
+ }
+
+
+ fun interface MQTTMatoListener{
+ //positionsMap is a dict with line -> vehicle -> Update
+ fun onUpdateReceived(posUpdates: PositionsMap)
+ }
+ }
+}
+
+data class MQTTPositionUpdate(
+ val lineId: String,
+ val vehicleId: String,
+ val latitude: Double,
+ val longitude: Double,
+ val heading: Int?,
+ val speed: Int?,
+ val tripId: String?,
+ val direct: Int?,
+ val nextStop: Int?,
+ //val full: Int?
+)
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt b/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
@@ -383,9 +383,9 @@
requestQueue.add(request)
val patterns = ArrayList<MatoPattern>()
- //var outObj = ""
+ var resObj = JSONObject()
try {
- val resObj = future.get(60,TimeUnit.SECONDS)
+ resObj = future.get(60,TimeUnit.SECONDS)
//outObj = resObj.toString(1)
val routesJSON = resObj.getJSONArray("routes")
for (i in 0 until routesJSON.length()){
@@ -406,7 +406,7 @@
} catch (e: JSONException){
e.printStackTrace()
res?.set(Fetcher.Result.PARSER_ERROR)
- //Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+ Log.e(DEBUG_TAG, "Got result: $resObj")
}
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/mato/ResponseParsing.kt b/app/src/main/java/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
--- a/app/src/main/java/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
@@ -117,7 +117,9 @@
MatoPattern(
mPatternJSON.getString("name"), mPatternJSON.getString("code"),
mPatternJSON.getString("semanticHash"), mPatternJSON.getInt("directionId"),
- routeGtfsId, mPatternJSON.getString("headsign"), polyline, numGeo, stopsCodes
+ routeGtfsId,
+ sanitize( mPatternJSON.getString("headsign")),
+ polyline, numGeo, stopsCodes
)
)
}
@@ -135,12 +137,26 @@
// still have "activeDates" which are the days in which the pattern is active
//Log.d("BusTO:RequestParsing", "Making GTFS trip for: $jsonData")
val trip = GtfsTrip(
- routeId, jsonTrip.getString("serviceId"), jsonTrip.getString("gtfsId"),
- jsonTrip.getString("tripHeadsign"), -1, "", "",
+ routeId, jsonTrip.getString("serviceId"),
+ jsonTrip.getString("gtfsId"),
+ sanitize(jsonTrip.getString("tripHeadsign")),
+ -1, "", "",
Converters.wheelchairFromString(jsonTrip.getString("wheelchairAccessible")),
false, patternId, jsonTrip.getString("semanticHash")
)
return trip
}
+
+ @JvmStatic
+ fun sanitize(dir: String): String{
+ var str = dir.trim()
+ val lastChar = str[str.length-1]
+ if(lastChar==','|| lastChar==';') {
+ Log.d(DEBUG_TAG, "Sanitization: removing last char from $str")
+ str = str.dropLast(1)
+ }
+
+ return str
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/utils.java b/app/src/main/java/it/reyboz/bustorino/backend/utils.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/utils.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/utils.java
@@ -64,16 +64,17 @@
public static Double angleRawDifferenceFromMeters(double distanceInMeters){
return Math.toDegrees(distanceInMeters/EarthRadius);
}
- /*
- public static int convertDipToPixels(Context con,float dips)
+
+ public static int convertDipToPixelsInt(Context con,double dips)
{
return (int) (dips * con.getResources().getDisplayMetrics().density + 0.5f);
}
- */
+
public static float convertDipToPixels(Context con, float dp){
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,con.getResources().getDisplayMetrics());
}
+
/*
public static int calculateNumColumnsFromSize(View containerView, int pixelsize){
int width = containerView.getWidth();
diff --git a/app/src/main/java/it/reyboz/bustorino/data/GtfsRepository.kt b/app/src/main/java/it/reyboz/bustorino/data/GtfsRepository.kt
--- a/app/src/main/java/it/reyboz/bustorino/data/GtfsRepository.kt
+++ b/app/src/main/java/it/reyboz/bustorino/data/GtfsRepository.kt
@@ -31,4 +31,8 @@
else
MutableLiveData(listOf())
}
+
+ fun getAllRoutes(): LiveData<List<GtfsRoute>>{
+ return gtfsDao.getAllRoutes()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/CommonFragmentListener.java
@@ -37,4 +37,9 @@
*/
void showMapCenteredOnStop(Stop stop);
+ /**
+ * We want to show the line in detail for route
+ * @param routeGtfsId the route gtfsID (eg, "gtt:10U")
+ */
+ void showLineOnMap(String routeGtfsId);
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/fragments/GTFSPositionsViewModel.kt
rename from app/src/main/java/it/reyboz/bustorino/fragments/MapViewModel.kt
rename to app/src/main/java/it/reyboz/bustorino/fragments/GTFSPositionsViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/GTFSPositionsViewModel.kt
@@ -22,31 +22,30 @@
import androidx.lifecycle.*
import com.android.volley.Response
import it.reyboz.bustorino.backend.NetworkVolleyManager
-import it.reyboz.bustorino.backend.gtfs.GtfsPositionUpdate
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.backend.gtfs.GtfsRtPositionsRequest
import it.reyboz.bustorino.data.*
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import java.util.concurrent.Executors
/**
* View Model for the map. For containing the stops, the trips and whatever
*/
-class MapViewModel(application: Application): AndroidViewModel(application) {
+class GTFSPositionsViewModel(application: Application): AndroidViewModel(application) {
private val gtfsRepo = GtfsRepository(application)
private val netVolleyManager = NetworkVolleyManager.getInstance(application)
- val positionsLiveData = MutableLiveData<ArrayList<GtfsPositionUpdate>>()
+ val positionsLiveData = MutableLiveData<ArrayList<LivePositionUpdate>>()
private val positionsRequestRunning = MutableLiveData<Boolean>()
private val positionRequestListener = object: GtfsRtPositionsRequest.Companion.RequestListener{
- override fun onResponse(response: ArrayList<GtfsPositionUpdate>?) {
+ override fun onResponse(response: ArrayList<LivePositionUpdate>?) {
Log.i(DEBUG_TI,"Got response from the GTFS RT server")
- response?.let {it:ArrayList<GtfsPositionUpdate> ->
+ response?.let {it:ArrayList<LivePositionUpdate> ->
if (it.size == 0) {
Log.w(DEBUG_TI,"No position updates from the server")
return
@@ -120,7 +119,7 @@
val tripNames=tripswithPatterns.map { twp-> twp.trip.tripID }
Log.i(DEBUG_TI, "Have ${tripswithPatterns.size} trips in the DB")
if (tripsIDsInUpdates.value!=null)
- return@map tripsIDsInUpdates.value!!.filter { !tripNames.contains(it) }
+ return@map tripsIDsInUpdates.value!!.filter { !tripNames.contains(it) }
else {
Log.e(DEBUG_TI,"Got results for gtfsTripsInDB but not tripsIDsInUpdates??")
return@map ArrayList<String>()
@@ -129,7 +128,7 @@
val updatesWithTripAndPatterns = gtfsTripsPatternsInDB.map { tripPatterns->
Log.i(DEBUG_TI, "Mapping trips and patterns")
- val mdict = HashMap<String,Pair<GtfsPositionUpdate, TripAndPatternWithStops?>>()
+ val mdict = HashMap<String,Pair<LivePositionUpdate, TripAndPatternWithStops?>>()
//missing patterns
val routesToDownload = HashSet<String>()
if(positionsLiveData.value!=null)
@@ -174,7 +173,7 @@
fun downloadTripsFromMato(trips: List<String>): Boolean{
return MatoTripsDownloadWorker.downloadTripsFromMato(trips,getApplication(), DEBUG_TI)
}
- fun downloadMissingPatterns(routeIds: List<String>): Boolean{
+ private fun downloadMissingPatterns(routeIds: List<String>): Boolean{
return MatoPatternsDownloadWorker.downloadPatternsForRoutes(routeIds, getApplication())
}
@@ -186,11 +185,9 @@
positionsRequestRunning.value = false;
}
fun testCascade(){
- val n = ArrayList<GtfsPositionUpdate>()
- n.add(GtfsPositionUpdate("22920721U","lala","lalal","lol",1000.0f,1000.0f, 9000.0f,
- 378192810192, GtfsPositionUpdate.VehicleInfo("aj","a"),
- null, null
-
+ val n = ArrayList<LivePositionUpdate>()
+ n.add(LivePositionUpdate("22920721U","lala","lalal","lol","ASD",
+ 1000.0,1000.0, 9000.0f, 21838191, null
))
positionsLiveData.value = n
}
@@ -202,8 +199,8 @@
companion object{
- const val DEBUG_TI="BusTO-MapViewModel"
+ private const val DEBUG_TI="BusTO-GTFSRTViewModel"
const val DEFAULT_DELAY_REQUESTS: Long=4000
}
}
\ No newline at end of file
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
@@ -1,145 +1,198 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2023 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
package it.reyboz.bustorino.fragments
+import android.animation.ObjectAnimator
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Paint
import android.os.Bundle
-import android.os.Parcelable
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.AdapterView
-import android.widget.ArrayAdapter
-import android.widget.Spinner
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
+import android.widget.*
+import androidx.appcompat.content.res.AppCompatResources
+import androidx.core.content.ContextCompat
+import androidx.core.content.res.ResourcesCompat
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.PreferenceManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import it.reyboz.bustorino.R
+import it.reyboz.bustorino.adapters.NameCapitalize
+import it.reyboz.bustorino.adapters.StopAdapterListener
+import it.reyboz.bustorino.adapters.StopRecyclerAdapter
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.backend.gtfs.GtfsUtils
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.backend.gtfs.PolylineParser
+import it.reyboz.bustorino.backend.utils
+import it.reyboz.bustorino.data.MatoTripsDownloadWorker
+import it.reyboz.bustorino.data.gtfs.MatoPattern
import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
-import it.reyboz.bustorino.data.gtfs.PatternStop
-import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase
+import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
+import it.reyboz.bustorino.map.BusInfoWindow
+import it.reyboz.bustorino.map.BusPositionUtils
+import it.reyboz.bustorino.map.CustomInfoWindow.TouchResponder
+import it.reyboz.bustorino.map.MapViewModel
+import it.reyboz.bustorino.map.MarkerUtils
+import it.reyboz.bustorino.viewmodels.MQTTPositionsViewModel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
+import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
-import org.osmdroid.util.MapTileIndex
import org.osmdroid.views.MapView
+import org.osmdroid.views.overlay.FolderOverlay
+import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.Polyline
+import org.osmdroid.views.overlay.advancedpolyline.MonochromaticPaintList
-class LinesDetailFragment() : Fragment() {
-
- private lateinit var lineID: String
+class LinesDetailFragment() : ScreenBaseFragment() {
+ private lateinit var lineID: String
private lateinit var patternsSpinner: Spinner
private var patternsAdapter: ArrayAdapter<String>? = null
- private var patternsSpinnerState: Parcelable? = null
+ //private var patternsSpinnerState: Parcelable? = null
private lateinit var currentPatterns: List<MatoPatternWithStops>
- private lateinit var gtfsStopsForCurrentPattern: List<PatternStop>
private lateinit var map: MapView
- private lateinit var viewingPattern: MatoPatternWithStops
+ private var viewingPattern: MatoPatternWithStops? = null
+
+ private val viewModel: LinesViewModel by viewModels()
+ private val mapViewModel: MapViewModel by viewModels()
+ private var firstInit = true
+ private var pausedFragment = false
+ private lateinit var switchButton: ImageButton
+ private lateinit var stopsRecyclerView: RecyclerView
+ //adapter for recyclerView
+ private val stopAdapterListener= object : StopAdapterListener {
+ override fun onTappedStop(stop: Stop?) {
+
+ if(viewModel.shouldShowMessage) {
+ Toast.makeText(context, R.string.long_press_stop_4_options, Toast.LENGTH_SHORT).show()
+ viewModel.shouldShowMessage=false
+ }
+ stop?.let {
+ fragmentListener?.requestArrivalsForStopID(it.ID)
+ }
+ if(stop == null){
+ Log.e(DEBUG_TAG,"Passed wrong stop")
+ }
+ if(fragmentListener == null){
+ Log.e(DEBUG_TAG, "Fragment listener is null")
+ }
+ }
- private lateinit var viewModel: LinesViewModel
+ override fun onLongPressOnStop(stop: Stop?): Boolean {
+ TODO("Not yet implemented")
+ }
- private var polyline = Polyline();
- private var stopPosList = ArrayList<GeoPoint>()
+ }
- companion object {
- private const val LINEID_KEY="lineID"
- fun newInstance() = LinesDetailFragment()
- const val DEBUG_TAG="LinesDetailFragment"
- fun makeArgs(lineID: String): Bundle{
- val b = Bundle()
- b.putString(LINEID_KEY, lineID)
- return b
- }
- private const val DEFAULT_CENTER_LAT = 45.0708
- private const val DEFAULT_CENTER_LON = 7.6858
+ private var polyline: Polyline? = null
+ //private var stopPosList = ArrayList<GeoPoint>()
+
+ private lateinit var stopsOverlay: FolderOverlay
+ //fragment actions
+ private lateinit var fragmentListener: CommonFragmentListener
+
+ private val stopTouchResponder = TouchResponder { stopID, stopName ->
+ Log.d(DEBUG_TAG, "Asked to show arrivals for stop ID: $stopID")
+ fragmentListener.requestArrivalsForStopID(stopID)
}
+ private var showOnTopOfLine = true
+ private var recyclerInitDone = false
+
+ //position of live markers
+ private val busPositionMarkersByTrip = HashMap<String,Marker>()
+ private var busPositionsOverlay = FolderOverlay()
+ private val tripMarkersAnimators = HashMap<String, ObjectAnimator>()
+ private val liveBusViewModel: MQTTPositionsViewModel by viewModels()
+ @SuppressLint("SetTextI18n")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_lines_detail, container, false)
lineID = requireArguments().getString(LINEID_KEY, "")
+ switchButton = rootView.findViewById(R.id.switchImageButton)
+ stopsRecyclerView = rootView.findViewById(R.id.patternStopsRecyclerView)
+
+ val titleTextView = rootView.findViewById<TextView>(R.id.titleTextView)
+
+ titleTextView.text = getString(R.string.line)+" "+GtfsUtils.getLineNameFromGtfsID(lineID)
patternsSpinner = rootView.findViewById(R.id.patternsSpinner)
patternsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, ArrayList<String>())
patternsSpinner.adapter = patternsAdapter
- map = rootView.findViewById(R.id.lineMap)
- val USGS_SAT: OnlineTileSourceBase = object : OnlineTileSourceBase(
- "USGS National Map Sat",
- 0,
- 15,
- 256,
- "",
- arrayOf("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/"),
- "USGS"
- ) {
- override fun getTileURLString(pMapTileIndex: Long): String {
- return baseUrl + MapTileIndex.getZoom(pMapTileIndex) + "/" + MapTileIndex.getY(pMapTileIndex) + "/" + MapTileIndex.getX(
- pMapTileIndex
- )
- }
- }
- map.setTileSource(TileSourceFactory.MAPNIK)
- /*
- object : OnlineTileSourceBase("USGS Topo", 0, 18, 256, "",
- arrayOf("https://basemap.nationalmap.gov/ArcGIS/rest/services/USGSTopo/MapServer/tile/" )) {
- override fun getTileURLString(pMapTileIndex: Long) : String{
- return baseUrl +
- MapTileIndex.getZoom(pMapTileIndex)+"/" + MapTileIndex.getY(pMapTileIndex) +
- "/" + MapTileIndex.getX(pMapTileIndex)+ mImageFilenameEnding;
- }
- }
- */
- //map.setTilesScaledToDpi(true);
- //map.setTilesScaledToDpi(true);
- map.setFlingEnabled(true)
- map.setUseDataConnection(true)
+ initializeMap(rootView)
+
+ initializeRecyclerView()
- // add ability to zoom with 2 fingers
- map.setMultiTouchControls(true)
- map.minZoomLevel = 10.0
+ switchButton.setOnClickListener{
+ if(map.visibility == View.VISIBLE){
+ map.visibility = View.GONE
+ stopsRecyclerView.visibility = View.VISIBLE
- //map controller setup
- val mapController = map.controller
- mapController.setZoom(12.0)
- mapController.setCenter(GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON))
- map.invalidate()
+ viewModel.setMapShowing(false)
+ liveBusViewModel.stopPositionsListening()
+ switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_map_white_30))
+ } else{
+ stopsRecyclerView.visibility = View.GONE
+ map.visibility = View.VISIBLE
+ viewModel.setMapShowing(true)
+ liveBusViewModel.requestPosUpdates(lineID)
+ switchButton.setImageDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_list_30))
+ }
+ }
+ viewModel.setRouteIDQuery(lineID)
viewModel.patternsWithStopsByRouteLiveData.observe(viewLifecycleOwner){
patterns -> savePatternsToShow(patterns)
}
-
-
/*
We have the pattern and the stops here, time to display them
*/
viewModel.stopsForPatternLiveData.observe(viewLifecycleOwner) { stops ->
- Log.d(DEBUG_TAG, "Got the stops: ${stops.map { s->s.gtfsID }}}")
-
- val pattern = viewingPattern.pattern
-
- val pointsList = PolylineParser.decodePolyline(pattern.patternGeometryPoly, pattern.patternGeometryLength)
- //val polyLine=Polyline(map)
- //polyLine.setPoints(pointsList)
- //save points
- if(map.overlayManager.contains(polyline)){
- map.overlayManager.remove(polyline)
+ if(map.visibility ==View.VISIBLE)
+ showPatternWithStopsOnMap(stops)
+ else{
+ if(stopsRecyclerView.visibility==View.VISIBLE)
+ showStopsAsList(stops)
}
- polyline = Polyline(map)
- polyline.setPoints(pointsList)
-
- map.overlayManager.add(polyline)
- map.controller.animateTo(pointsList[0])
- map.invalidate()
}
-
- viewModel.setRouteIDQuery(lineID)
+ if(pausedFragment && viewModel.selectedPatternLiveData.value!=null){
+ val patt = viewModel.selectedPatternLiveData.value!!
+ Log.d(DEBUG_TAG, "Recreating views on resume, setting pattern: ${patt.pattern.code}")
+ showPattern(patt)
+ pausedFragment = false
+ }
Log.d(DEBUG_TAG,"Data ${viewModel.stopsForPatternLiveData.value}")
@@ -147,7 +200,19 @@
patternsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
val patternWithStops = currentPatterns.get(position)
+ //viewModel.setPatternToDisplay(patternWithStops)
setPatternAndReqStops(patternWithStops)
+
+ Log.d(DEBUG_TAG, "item Selected, cleaning bus markers")
+ if(map?.visibility == View.VISIBLE) {
+ busPositionsOverlay.closeAllInfoWindows()
+ busPositionsOverlay.items.clear()
+ busPositionMarkersByTrip.clear()
+
+ stopAnimations()
+ tripMarkersAnimators.clear()
+ liveBusViewModel.retriggerPositionUpdate()
+ }
}
override fun onNothingSelected(p0: AdapterView<*>?) {
@@ -155,12 +220,127 @@
}
+ //live bus positions
+ liveBusViewModel.updatesWithTripAndPatterns.observe(viewLifecycleOwner){
+ if(map.visibility == View.GONE || viewingPattern ==null){
+ //DO NOTHING
+ return@observe
+ }
+ //filter buses with direction, show those only with the same direction
+ val outmap = HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>()
+ val currentPattern = viewingPattern!!.pattern
+ val numUpds = it.entries.size
+ Log.d(DEBUG_TAG, "Got $numUpds updates, current pattern is: ${currentPattern.name}, directionID: ${currentPattern.directionId}")
+ val patternsDirections = HashMap<String,Int>()
+ for((tripId, pair) in it.entries){
+
+ if(pair.second!=null && pair.second?.pattern !=null){
+ val dir = pair.second?.pattern?.directionId
+ if(dir !=null && dir == currentPattern.directionId){
+ outmap.set(tripId, pair)
+ }
+ patternsDirections.set(tripId,if (dir!=null) dir else -10)
+ } else{
+ outmap[tripId] = pair
+ //Log.d(DEBUG_TAG, "No pattern for tripID: $tripId")
+ patternsDirections.set(tripId, -10)
+ }
+ }
+ Log.d(DEBUG_TAG, " Filtered updates are ${outmap.keys.size}") // Original updates directs: $patternsDirections\n
+ updateBusPositionsInMap(outmap)
+ }
+
+ //download missing tripIDs
+ liveBusViewModel.tripsGtfsIDsToQuery.observe(viewLifecycleOwner){
+ //gtfsPosViewModel.downloadTripsFromMato(dat);
+ MatoTripsDownloadWorker.downloadTripsFromMato(
+ it, requireContext().applicationContext,
+ "BusTO-MatoTripDownload"
+ )
+ }
+
+
return rootView
}
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- viewModel = ViewModelProvider(this).get(LinesViewModel::class.java)
+ private fun initializeMap(rootView : View){
+ val ctx = requireContext().applicationContext
+ Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(ctx))
+
+ map = rootView.findViewById(R.id.lineMap)
+ map.let {
+ it.setTileSource(TileSourceFactory.MAPNIK)
+ /*
+ object : OnlineTileSourceBase("USGS Topo", 0, 18, 256, "",
+ arrayOf("https://basemap.nationalmap.gov/ArcG IS/rest/services/USGSTopo/MapServer/tile/" )) {
+ override fun getTileURLString(pMapTileIndex: Long) : String{
+ return baseUrl +
+ MapTileIndex.getZoom(pMapTileIndex)+"/" + MapTileIndex.getY(pMapTileIndex) +
+ "/" + MapTileIndex.getX(pMapTileIndex)+ mImageFilenameEnding;
+ }
+ }
+ */
+ stopsOverlay = FolderOverlay()
+ busPositionsOverlay = FolderOverlay()
+ //map.setTilesScaledToDpi(true);
+ //map.setTilesScaledToDpi(true);
+ it.setFlingEnabled(true)
+ it.setUseDataConnection(true)
+
+ // add ability to zoom with 2 fingers
+ it.setMultiTouchControls(true)
+ it.minZoomLevel = 11.0
+
+ //map controller setup
+ val mapController = it.controller
+ var zoom = 12.0
+ var centerMap = GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON)
+ if(mapViewModel.currentLat.value!=MapViewModel.INVALID) {
+ Log.d(DEBUG_TAG, "mapViewModel posi: ${mapViewModel.currentLat.value}, ${mapViewModel.currentLong.value}"+
+ " zoom ${mapViewModel.currentZoom.value}")
+ zoom = mapViewModel.currentZoom.value!!
+ centerMap = GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!)
+ /*viewLifecycleOwner.lifecycleScope.launch {
+ delay(100)
+ Log.d(DEBUG_TAG, "zooming back to point")
+ controller.animateTo(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!),
+ mapViewModel.currentZoom.value!!,null,null)
+ //controller.setCenter(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!))
+ //controller.setZoom(mapViewModel.currentZoom.value!!)
+
+ */
+
+ }
+ mapController.setZoom(zoom)
+ mapController.setCenter(centerMap)
+ Log.d(DEBUG_TAG, "Initializing map, first init $firstInit")
+ //map.invalidate()
+
+ it.overlayManager.add(stopsOverlay)
+ it.overlayManager.add(busPositionsOverlay)
+
+ zoomToCurrentPattern()
+ firstInit = false
+
+ }
+
+
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if(context is CommonFragmentListener){
+ fragmentListener = context
+ } else throw RuntimeException("$context must implement CommonFragmentListener")
+
+ fragmentListener.readyGUIfor(FragmentKind.LINES)
+ }
+
+
+ private fun stopAnimations(){
+ for(anim in tripMarkersAnimators.values){
+ anim.cancel()
+ }
}
private fun savePatternsToShow(patterns: List<MatoPatternWithStops>){
@@ -171,25 +351,375 @@
it.addAll(currentPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
it.notifyDataSetChanged()
}
-
- val pos = patternsSpinner.selectedItemPosition
- //might be possible that the selectedItem is different (larger than list size)
- if(pos!= AdapterView.INVALID_POSITION && pos >= 0 && (pos < currentPatterns.size)){
- val p = currentPatterns[pos]
- Log.d(LinesFragment.DEBUG_TAG, "Setting patterns with pos $pos and p gtfsID ${p.pattern.code}")
- setPatternAndReqStops(currentPatterns[pos])
+ viewingPattern?.let {
+ showPattern(it)
}
- Log.d(DEBUG_TAG, "Patterns changed")
}
+ /**
+ * Called when the position of the spinner is updated
+ */
private fun setPatternAndReqStops(patternWithStops: MatoPatternWithStops){
Log.d(DEBUG_TAG, "Requesting stops for pattern ${patternWithStops.pattern.code}")
- gtfsStopsForCurrentPattern = patternWithStops.stopsIndices.sortedBy { i-> i.order }
+ viewModel.selectedPatternLiveData.value = patternWithStops
+ viewModel.currentPatternStops.value = patternWithStops.stopsIndices.sortedBy { i-> i.order }
viewingPattern = patternWithStops
viewModel.requestStopsForPatternWithStops(patternWithStops)
}
+ private fun showPattern(patternWs: MatoPatternWithStops){
+ Log.d(DEBUG_TAG, "Finding pattern to show: ${patternWs.pattern.code}")
+ var pos = -2
+ val code = patternWs.pattern.code.trim()
+ for(k in currentPatterns.indices){
+ if(currentPatterns[k].pattern.code.trim() == code){
+ pos = k
+ break
+ }
+ }
+ Log.d(DEBUG_TAG, "Found pattern $code in position: $pos")
+ if(pos>=0)
+ patternsSpinner.setSelection(pos)
+ //set pattern
+ setPatternAndReqStops(patternWs)
+ }
+
+ private fun zoomToCurrentPattern(){
+ var pointsList: List<GeoPoint>
+ if(viewingPattern==null) {
+ Log.e(DEBUG_TAG, "asked to zoom to pattern but current viewing pattern is null")
+ if(polyline!=null)
+ pointsList = polyline!!.actualPoints
+ else {
+ Log.d(DEBUG_TAG, "The polyline is null")
+ return
+ }
+ }else{
+ val pattern = viewingPattern!!.pattern
+
+ pointsList = PolylineParser.decodePolyline(pattern.patternGeometryPoly, pattern.patternGeometryLength)
+ }
+
+ var maxLat = -4000.0
+ var minLat = -4000.0
+ var minLong = -4000.0
+ var maxLong = -4000.0
+ for (p in pointsList){
+ // get max latitude
+ if(maxLat == -4000.0)
+ maxLat = p.latitude
+ else if (maxLat < p.latitude) maxLat = p.latitude
+ // find min latitude
+ if (minLat == -4000.0)
+ minLat = p.latitude
+ else if (minLat > p.latitude) minLat = p.latitude
+ if(maxLong == -4000.0 || maxLong < p.longitude )
+ maxLong = p.longitude
+ if (minLong == -4000.0 || minLong > p.longitude)
+ minLong = p.longitude
+ }
+
+ val del = 0.008
+ //map.controller.c
+ Log.d(DEBUG_TAG, "Setting limits of bounding box of line: $minLat -> $maxLat, $minLong -> $maxLong")
+ map.zoomToBoundingBox(BoundingBox(maxLat+del, maxLong+del, minLat-del, minLong-del), false)
+ }
+
+ private fun showPatternWithStopsOnMap(stops: List<Stop>){
+ Log.d(DEBUG_TAG, "Got the stops: ${stops.map { s->s.gtfsID }}}")
+ if(viewingPattern==null || map == null) return
+
+ val pattern = viewingPattern!!.pattern
+
+ val pointsList = PolylineParser.decodePolyline(pattern.patternGeometryPoly, pattern.patternGeometryLength)
+
+ var maxLat = -4000.0
+ var minLat = -4000.0
+ var minLong = -4000.0
+ var maxLong = -4000.0
+ for (p in pointsList){
+ // get max latitude
+ if(maxLat == -4000.0)
+ maxLat = p.latitude
+ else if (maxLat < p.latitude) maxLat = p.latitude
+ // find min latitude
+ if (minLat == -4000.0)
+ minLat = p.latitude
+ else if (minLat > p.latitude) minLat = p.latitude
+ if(maxLong == -4000.0 || maxLong < p.longitude )
+ maxLong = p.longitude
+ if (minLong == -4000.0 || minLong > p.longitude)
+ minLong = p.longitude
+ }
+ //val polyLine=Polyline(map)
+ //polyLine.setPoints(pointsList)
+ //save points
+ if(map.overlayManager.contains(polyline)){
+ map.overlayManager.remove(polyline)
+ }
+ polyline = Polyline(map, false)
+ polyline!!.setPoints(pointsList)
+ //polyline.color = ContextCompat.getColor(context!!,R.color.brown_vd)
+ polyline!!.infoWindow = null
+ val paint = Paint()
+ paint.color = ContextCompat.getColor(requireContext(),R.color.line_drawn_poly)
+ paint.isAntiAlias = true
+ paint.strokeWidth = 16f
+ paint.style = Paint.Style.FILL_AND_STROKE
+ paint.strokeJoin = Paint.Join.ROUND
+ paint.strokeCap = Paint.Cap.ROUND
+ polyline!!.outlinePaintLists.add(MonochromaticPaintList(paint))
+
+ map.overlayManager.add(0,polyline!!)
+
+ stopsOverlay.closeAllInfoWindows()
+ stopsOverlay.items.clear()
+ val stopIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ball)
+
+ for(s in stops){
+ val gp = if (showOnTopOfLine)
+ findOptimalPosition(s,pointsList)
+ else GeoPoint(s.latitude!!,s.longitude!!)
+
+ val marker = MarkerUtils.makeMarker(
+ gp, s.ID, s.stopDefaultName,
+ s.routesThatStopHereToString(),
+ map,stopTouchResponder, stopIcon,
+ R.layout.linedetail_stop_infowindow,
+ R.color.line_drawn_poly
+ )
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
+ stopsOverlay.add(marker)
+ }
+ //POINTS LIST IS NOT IN ORDER ANY MORE
+ //if(!map.overlayManager.contains(stopsOverlay)){
+ // map.overlayManager.add(stopsOverlay)
+ //}
+ polyline!!.setOnClickListener(Polyline.OnClickListener { polyline, mapView, eventPos ->
+ Log.d(DEBUG_TAG, "clicked")
+ true
+ })
+
+ //map.controller.zoomToB//#animateTo(pointsList[0])
+ val del = 0.008
+ map.zoomToBoundingBox(BoundingBox(maxLat+del, maxLong+del, minLat-del, minLong-del), true)
+ //map.invalidate()
+ }
+
+ private fun initializeRecyclerView(){
+ val llManager = LinearLayoutManager(context)
+ llManager.orientation = LinearLayoutManager.VERTICAL
+
+ stopsRecyclerView.layoutManager = llManager
+ }
+ private fun showStopsAsList(stops: List<Stop>){
+
+ Log.d(DEBUG_TAG, "Setting stops from: "+viewModel.currentPatternStops.value)
+ val orderBy = viewModel.currentPatternStops.value!!.withIndex().associate{it.value.stopGtfsId to it.index}
+ val stopsSorted = stops.sortedBy { s -> orderBy[s.gtfsID] }
+ val numStops = stopsSorted.size
+ Log.d(DEBUG_TAG, "RecyclerView adapter is: ${stopsRecyclerView.adapter}")
+
+ val setNewAdapter = true
+ if(setNewAdapter){
+ stopsRecyclerView.adapter = StopRecyclerAdapter(
+ stopsSorted, stopAdapterListener, StopRecyclerAdapter.Use.LINES,
+ NameCapitalize.FIRST
+ )
+
+ }
+
+
+
+ }
+
+
+ /**
+ * Remove bus marker from overlay associated with tripID
+ */
+ private fun removeBusMarker(tripID: String){
+ if(!busPositionMarkersByTrip.containsKey(tripID)){
+ Log.e(DEBUG_TAG, "Asked to remove veh with tripID $tripID but it's supposedly not shown")
+ return
+ }
+ val marker = busPositionMarkersByTrip[tripID]
+ busPositionsOverlay.remove(marker)
+ busPositionMarkersByTrip.remove(tripID)
+
+ val animator = tripMarkersAnimators[tripID]
+ animator?.let{
+ it.cancel()
+ tripMarkersAnimators.remove(tripID)
+ }
+
+ }
+
+ private fun showPatternWithStop(patternId: String){
+ //var index = 0
+ Log.d(DEBUG_TAG, "Showing pattern with code $patternId ")
+ for (i in currentPatterns.indices){
+ val pattStop = currentPatterns[i]
+ if(pattStop.pattern.code == patternId){
+ Log.d(DEBUG_TAG, "Pattern found in position $i")
+ //setPatternAndReqStops(pattStop)
+ patternsSpinner.setSelection(i)
+ break
+ }
+ }
+ }
+ /**
+ * draw the position of the buses in the map. Copied from MapFragment
+ */
+ private fun updateBusPositionsInMap(tripsPatterns: java.util.HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops?>>
+ ) {
+ //Log.d(MapFragment.DEBUG_TAG, "Updating positions of the buses")
+ //if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay();
+ val noPatternsTrips = ArrayList<String>()
+ for (tripID in tripsPatterns.keys) {
+ val (update, tripWithPatternStops) = tripsPatterns[tripID] ?: continue
+
+ var marker: Marker? = null
+ //check if Marker is already created
+ if (busPositionMarkersByTrip.containsKey(tripID)) {
+
+ //check if the trip direction ID is the same, if not remove
+ if(tripWithPatternStops?.pattern != null &&
+ tripWithPatternStops.pattern.directionId != viewingPattern?.pattern?.directionId){
+ removeBusMarker(tripID)
+
+ } else {
+ //need to change the position of the marker
+ marker = busPositionMarkersByTrip.get(tripID)!!
+ BusPositionUtils.updateBusPositionMarker(map, marker, update, tripMarkersAnimators, false)
+ // Set the pattern to add the info
+ if (marker.infoWindow != null && marker.infoWindow is BusInfoWindow) {
+ val window = marker.infoWindow as BusInfoWindow
+ if (window.pattern == null && tripWithPatternStops != null) {
+ //Log.d(DEBUG_TAG, "Update pattern for trip: "+tripID);
+ window.setPatternAndDraw(tripWithPatternStops.pattern)
+ }
+ }
+ }
+ } else {
+ //marker is not there, need to make it
+ //if (mapView == null) Log.e(MapFragment.DEBUG_TAG, "Creating marker with null map, things will explode")
+ marker = Marker(map)
+
+ //String route = GtfsUtils.getLineNameFromGtfsID(update.getRouteID());
+ val mdraw = ResourcesCompat.getDrawable(getResources(), R.drawable.map_bus_position_icon, null)!!
+ //mdraw.setBounds(0,0,28,28);
+
+ marker.icon = mdraw
+ var markerPattern: MatoPattern? = null
+ if (tripWithPatternStops != null) {
+ if (tripWithPatternStops.pattern != null)
+ markerPattern = tripWithPatternStops.pattern
+ }
+ marker.infoWindow = BusInfoWindow(map, update, markerPattern, true) {
+ // set pattern to show
+ if(it!=null)
+ showPatternWithStop(it.code)
+ }
+ //marker.infoWindow as BusInfoWindow
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
+ BusPositionUtils.updateBusPositionMarker(map,marker, update, tripMarkersAnimators,true)
+ // the overlay is null when it's not attached yet?
+ // cannot recreate it because it becomes null very soon
+ // if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay();
+ //save the marker
+ if (busPositionsOverlay != null) {
+ busPositionsOverlay.add(marker)
+ busPositionMarkersByTrip.put(tripID, marker)
+ }
+ }
+ }
+ if (noPatternsTrips.size > 0) {
+ Log.i(DEBUG_TAG, "These trips have no matching pattern: $noPatternsTrips")
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ Log.d(DEBUG_TAG, "Resetting paused from onResume")
+ pausedFragment = false
+
+ liveBusViewModel.requestPosUpdates(GtfsUtils.getLineNameFromGtfsID(lineID))
+
+ if(mapViewModel.currentLat.value!=MapViewModel.INVALID) {
+ Log.d(DEBUG_TAG, "mapViewModel posi: ${mapViewModel.currentLat.value}, ${mapViewModel.currentLong.value}"+
+ " zoom ${mapViewModel.currentZoom.value}")
+ val controller = map.controller
+ viewLifecycleOwner.lifecycleScope.launch {
+ delay(100)
+ Log.d(DEBUG_TAG, "zooming back to point")
+ controller.animateTo(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!),
+ mapViewModel.currentZoom.value!!,null,null)
+ //controller.setCenter(GeoPoint(mapViewModel.currentLat.value!!, mapViewModel.currentLong.value!!))
+ //controller.setZoom(mapViewModel.currentZoom.value!!)
+ }
+ //controller.setZoom()
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ liveBusViewModel.stopPositionsListening()
+ pausedFragment = true
+ //save map
+ val center = map.mapCenter
+ mapViewModel.currentLat.value = center.latitude
+ mapViewModel.currentLong.value = center.longitude
+ mapViewModel.currentZoom.value = map.zoomLevel.toDouble()
+ }
+
+ override fun getBaseViewForSnackBar(): View? {
+ return null
+ }
+
+ companion object {
+ private const val LINEID_KEY="lineID"
+ fun newInstance() = LinesDetailFragment()
+ const val DEBUG_TAG="LinesDetailFragment"
+
+ fun makeArgs(lineID: String): Bundle{
+ val b = Bundle()
+ b.putString(LINEID_KEY, lineID)
+ return b
+ }
+ @JvmStatic
+ private fun findOptimalPosition(stop: Stop, pointsList: MutableList<GeoPoint>): GeoPoint{
+ if(stop.latitude==null || stop.longitude ==null|| pointsList.isEmpty())
+ throw IllegalArgumentException()
+ val sLat = stop.latitude!!
+ val sLong = stop.longitude!!
+ if(pointsList.size < 2)
+ return pointsList[0]
+ pointsList.sortBy { utils.measuredistanceBetween(sLat, sLong, it.latitude, it.longitude) }
+
+ val p1 = pointsList[0]
+ val p2 = pointsList[1]
+ if (p1.longitude == p2.longitude){
+ //Log.e(DEBUG_TAG, "Same longitude")
+ return GeoPoint(sLat, p1.longitude)
+ } else if (p1.latitude == p2.latitude){
+ //Log.d(DEBUG_TAG, "Same latitude")
+ return GeoPoint(p2.latitude,sLong)
+ }
+
+ val m = (p1.latitude - p2.latitude) / (p1.longitude - p2.longitude)
+ val minv = (p1.longitude-p2.longitude)/(p1.latitude - p2.latitude)
+ val cR = p1.latitude - p1.longitude * m
+
+ val longNew = (minv * sLong + sLat -cR ) / (m+minv)
+ val latNew = (m*longNew + cR)
+ //Log.d(DEBUG_TAG,"Stop ${stop.ID} old pos: ($sLat, $sLong), new pos ($latNew,$longNew)")
+ return GeoPoint(latNew,longNew)
+ }
+
+ private const val DEFAULT_CENTER_LAT = 45.12
+ private const val DEFAULT_CENTER_LON = 7.6858
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesFragment.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesFragment.kt
@@ -45,7 +45,7 @@
fun newInstance(){
LinesFragment()
}
- const val DEBUG_TAG="BusTO-LinesFragment"
+ private const val DEBUG_TAG="BusTO-LinesFragment"
const val FRAGMENT_TAG="LinesFragment"
val patternStopsComparator = PatternWithStopsSorter()
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
@@ -0,0 +1,277 @@
+package it.reyboz.bustorino.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Animation
+import android.view.animation.LinearInterpolator
+import android.view.animation.RotateAnimation
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.fragment.app.viewModels
+import androidx.recyclerview.widget.RecyclerView
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.adapters.RouteAdapter
+import it.reyboz.bustorino.backend.utils
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.middleware.AutoFitGridLayoutManager
+import it.reyboz.bustorino.util.LinesNameSorter
+import it.reyboz.bustorino.util.ViewUtils
+import it.reyboz.bustorino.viewmodels.LinesGridShowingViewModel
+
+
+class LinesGridShowingFragment : ScreenBaseFragment() {
+
+
+
+ private val viewModel: LinesGridShowingViewModel by viewModels()
+ //private lateinit var gridLayoutManager: AutoFitGridLayoutManager
+
+ private lateinit var urbanRecyclerView: RecyclerView
+ private lateinit var extraurbanRecyclerView: RecyclerView
+ private lateinit var touristRecyclerView: RecyclerView
+
+ private lateinit var urbanLinesTitle: TextView
+ private lateinit var extrurbanLinesTitle: TextView
+ private lateinit var touristLinesTitle: TextView
+
+
+ private var routesByAgency = HashMap<String, ArrayList<GtfsRoute>>()
+ /*hashMapOf(
+ AG_URBAN to ArrayList<GtfsRoute>(),
+ AG_EXTRAURB to ArrayList(),
+ AG_TOUR to ArrayList()
+ )*/
+
+ private lateinit var fragmentListener: CommonFragmentListener
+
+ private val linesNameSorter = LinesNameSorter()
+ private val linesComparator = Comparator<GtfsRoute> { a,b ->
+ return@Comparator linesNameSorter.compare(a.shortName, b.shortName)
+ }
+
+ private val routeClickListener = RouteAdapter.onItemClick {
+ fragmentListener.showLineOnMap(it.gtfsId)
+ }
+ private val arrows = HashMap<String, ImageView>()
+ private val durations = HashMap<String, Long>()
+ private var openRecyclerView = "AG_URBAN"
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val rootView = inflater.inflate(R.layout.fragment_lines_grid, container, false)
+
+ urbanRecyclerView = rootView.findViewById(R.id.urbanLinesRecyclerView)
+ extraurbanRecyclerView = rootView.findViewById(R.id.extraurbanLinesRecyclerView)
+ touristRecyclerView = rootView.findViewById(R.id.touristLinesRecyclerView)
+
+ urbanLinesTitle = rootView.findViewById(R.id.urbanLinesTitleView)
+ extrurbanLinesTitle = rootView.findViewById(R.id.extraurbanLinesTitleView)
+ touristLinesTitle = rootView.findViewById(R.id.touristLinesTitleView)
+
+ arrows[AG_URBAN] = rootView.findViewById(R.id.arrowUrb)
+ arrows[AG_TOUR] = rootView.findViewById(R.id.arrowTourist)
+ arrows[AG_EXTRAURB] = rootView.findViewById(R.id.arrowExtraurban)
+ //show urban expanded by default
+
+ val recViews = listOf(urbanRecyclerView, extraurbanRecyclerView, touristRecyclerView)
+ for (recyView in recViews) {
+ val gridLayoutManager = AutoFitGridLayoutManager(
+ requireContext().applicationContext,
+ (utils.convertDipToPixels(context, COLUMN_WIDTH_DP.toFloat())).toInt()
+ )
+ recyView.layoutManager = gridLayoutManager
+ }
+
+ viewModel.routesLiveData.observe(viewLifecycleOwner){
+ //routesList = ArrayList(it)
+ //routesList.sortWith(linesComparator)
+ routesByAgency.clear()
+
+ for(route in it){
+ val agency = route.agencyID
+ if(!routesByAgency.containsKey(agency)){
+ routesByAgency[agency] = ArrayList()
+ }
+ routesByAgency[agency]?.add(route)
+
+ }
+
+
+ //val adapter = RouteOnlyLineAdapter(routesByAgency.map { route-> route.shortName })
+ //zip agencies and recyclerviews
+ Companion.AGENCIES.zip(recViews) { ag, recView ->
+ routesByAgency[ag]?.let { routeList ->
+ routeList.sortWith(linesComparator)
+ //val adapter = RouteOnlyLineAdapter(it.map { rt -> rt.shortName })
+ val adapter = RouteAdapter(routeList,routeClickListener)
+ recView.adapter = adapter
+ durations[ag] = if(routeList.size < 20) ViewUtils.DEF_DURATION else 1000
+ }
+ }
+
+ }
+
+ //onClicks
+ urbanLinesTitle.setOnClickListener {
+ if(openRecyclerView!=""&& openRecyclerView!= AG_URBAN){
+ openCloseRecyclerView(openRecyclerView)
+ openCloseRecyclerView(AG_URBAN)
+ }
+ }
+ extrurbanLinesTitle.setOnClickListener {
+ if(openRecyclerView!=""&& openRecyclerView!= AG_EXTRAURB){
+ openCloseRecyclerView(openRecyclerView)
+ openCloseRecyclerView(AG_EXTRAURB)
+
+ }
+ }
+ touristLinesTitle.setOnClickListener {
+ if(openRecyclerView!="" && openRecyclerView!= AG_TOUR) {
+ openCloseRecyclerView(openRecyclerView)
+ openCloseRecyclerView(AG_TOUR)
+ }
+ }
+
+ return rootView
+ }
+
+ private fun openCloseRecyclerView(agency: String){
+ val recyclerView = when(agency){
+ AG_TOUR -> touristRecyclerView
+ AG_EXTRAURB -> extraurbanRecyclerView
+ AG_URBAN -> urbanRecyclerView
+ else -> throw IllegalArgumentException("$DEBUG_TAG: Agency Invalid")
+ }
+ val expandedLiveData = when(agency){
+ AG_TOUR -> viewModel.isTouristExpanded
+ AG_URBAN -> viewModel.isUrbanExpanded
+ AG_EXTRAURB -> viewModel.isExtraUrbanExpanded
+ else -> throw IllegalArgumentException("$DEBUG_TAG: Agency Invalid")
+ }
+ val duration = durations[agency]
+ val arrow = arrows[agency]
+ val durArrow = if(duration == null || duration==ViewUtils.DEF_DURATION) 500 else duration
+ if(duration!=null&&arrow!=null)
+ when (recyclerView.visibility){
+ View.GONE -> {
+ Log.d(DEBUG_TAG, "Open recyclerview $agency")
+ //val a =ViewUtils.expand(recyclerView, duration, 0)
+ recyclerView.visibility = View.VISIBLE
+ expandedLiveData.value = true
+ Log.d(DEBUG_TAG, "Arrow for $agency has rotation: ${arrow.rotation}")
+
+ setOpen(arrow, true)
+ //arrow.startAnimation(rotateArrow(true,durArrow))
+ openRecyclerView = agency
+
+ }
+ View.VISIBLE -> {
+ Log.d(DEBUG_TAG, "Close recyclerview $agency")
+ //ViewUtils.collapse(recyclerView, duration)
+ recyclerView.visibility = View.GONE
+ expandedLiveData.value = false
+ //arrow.rotation = 90f
+ Log.d(DEBUG_TAG, "Arrow for $agency has rotation ${arrow.rotation} pre-rotate")
+ setOpen(arrow, false)
+ //arrow.startAnimation(rotateArrow(false,durArrow))
+ openRecyclerView = ""
+ }
+ View.INVISIBLE -> {
+ TODO()
+ }
+ }
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if(context is CommonFragmentListener){
+ fragmentListener = context
+ } else throw RuntimeException("$context must implement CommonFragmentListener")
+
+ fragmentListener.readyGUIfor(FragmentKind.LINES)
+ }
+
+ override fun getBaseViewForSnackBar(): View? {
+ return null
+ }
+
+ override fun onResume() {
+ super.onResume()
+ viewModel.isUrbanExpanded.value?.let {
+ if(it) {
+ urbanRecyclerView.visibility = View.VISIBLE
+ arrows[AG_URBAN]?.rotation= 90f
+ openRecyclerView = AG_URBAN
+ Log.d(DEBUG_TAG, "RecyclerView gtt:U is expanded")
+ }
+ else {
+ urbanRecyclerView.visibility = View.GONE
+ arrows[AG_URBAN]?.rotation= 0f
+ }
+ }
+ viewModel.isTouristExpanded.value?.let {
+ val recview = touristRecyclerView
+ if(it) {
+ recview.visibility = View.VISIBLE
+ arrows[AG_TOUR]?.rotation=90f
+ openRecyclerView = AG_TOUR
+ } else {
+ recview.visibility = View.GONE
+ arrows[AG_TOUR]?.rotation= 0f
+ }
+ }
+ viewModel.isExtraUrbanExpanded.value?.let {
+ val recview = extraurbanRecyclerView
+ if(it) {
+ openRecyclerView = AG_EXTRAURB
+ recview.visibility = View.VISIBLE
+ arrows[AG_EXTRAURB]?.rotation=90f
+ } else {
+ recview.visibility = View.GONE
+ arrows[AG_EXTRAURB]?.rotation=0f
+ }
+ }
+ }
+
+
+ companion object {
+ private const val COLUMN_WIDTH_DP=200
+ private const val AG_URBAN = "gtt:U"
+ private const val AG_EXTRAURB ="gtt:E"
+ private const val AG_TOUR ="gtt:T"
+ private const val DEBUG_TAG ="BusTO-LinesGridFragment"
+
+ const val FRAGMENT_TAG = "LinesGridShowingFragment"
+
+ private val AGENCIES = listOf(AG_URBAN, AG_EXTRAURB, AG_TOUR)
+ fun newInstance() = LinesGridShowingFragment()
+
+ @JvmStatic
+ fun setOpen(imageView: ImageView, value: Boolean){
+ if(value)
+ imageView.rotation = 90f
+ else
+ imageView.rotation = 0f
+ }
+ @JvmStatic
+ fun rotateArrow(toOpen: Boolean, duration: Long): RotateAnimation{
+ val start = if (toOpen) 0f else 90f
+ val stop = if(toOpen) 90f else 0f
+ Log.d(DEBUG_TAG, "Rotate arrow from $start to $stop")
+ val rotate = RotateAnimation(start, stop, Animation.RELATIVE_TO_SELF,
+ 0.5f, Animation.RELATIVE_TO_SELF, 0.5f)
+ rotate.duration = duration
+ rotate.interpolator = LinearInterpolator()
+ //rotate.fillAfter = true
+ rotate.fillBefore = false
+ return rotate
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesViewModel.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesViewModel.kt
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesViewModel.kt
@@ -29,6 +29,12 @@
val stopsForPatternLiveData = MutableLiveData<List<Stop>>()
private val executor = Executors.newFixedThreadPool(2)
+ val mapShowing = MutableLiveData(true)
+ fun setMapShowing(yes: Boolean){
+ mapShowing.value = yes
+ //retrigger redraw
+ stopsForPatternLiveData.postValue(stopsForPatternLiveData.value)
+ }
init {
val gtfsDao = GtfsDatabase.getGtfsDatabase(application).gtfsDao()
gtfsRepo = GtfsRepository(gtfsDao)
@@ -37,6 +43,7 @@
}
+
val routesGTTLiveData: LiveData<List<GtfsRoute>> by lazy{
gtfsRepo.getLinesLiveDataForFeed("gtt")
}
@@ -45,6 +52,7 @@
}
+
fun setRouteIDQuery(routeID: String){
routeIDToSearch.value = routeID
}
@@ -54,6 +62,10 @@
}
var shouldShowMessage = true
+ fun setPatternToDisplay(patternStops: MatoPatternWithStops){
+
+ selectedPatternLiveData.value = patternStops
+ }
/**
* Find the
*/
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -148,7 +148,7 @@
} else {
//Toast.makeText(MyActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
if (getContext()!=null)
- Toast.makeText(getContext().getApplicationContext(),
+ Toast.makeText(getContext().getApplicationContext(),
R.string.no_qrcode, Toast.LENGTH_SHORT).show();
@@ -729,6 +729,12 @@
}
+ @Override
+ public void showLineOnMap(String routeGtfsId) {
+ //pass to activity
+ mListener.showLineOnMap(routeGtfsId);
+ }
+
@Override
public void showMapCenteredOnStop(Stop stop) {
if(mListener!=null) mListener.showMapCenteredOnStop(stop);
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
@@ -27,7 +27,6 @@
import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;
-import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@@ -44,12 +43,14 @@
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
-import it.reyboz.bustorino.backend.gtfs.GtfsPositionUpdate;
-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.utils;
+import it.reyboz.bustorino.data.MatoTripsDownloadWorker;
import it.reyboz.bustorino.data.gtfs.MatoPattern;
import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops;
import it.reyboz.bustorino.map.*;
+import it.reyboz.bustorino.viewmodels.MQTTPositionsViewModel;
import org.osmdroid.api.IGeoPoint;
import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
@@ -78,7 +79,7 @@
public class MapFragment extends ScreenBaseFragment {
- private static final String TAG = "Busto-MapActivity";
+ //private static final String TAG = "Busto-MapActivity";
private static final String MAP_CURRENT_ZOOM_KEY = "map-current-zoom";
private static final String MAP_CENTER_LAT_KEY = "map-center-lat";
private static final String MAP_CENTER_LON_KEY = "map-center-lon";
@@ -117,7 +118,8 @@
private boolean hasMapStartFinished = false;
private boolean followingLocation = false;
- private MapViewModel mapViewModel ; //= new ViewModelProvider(this).get(MapViewModel.class);
+ //private GTFSPositionsViewModel gtfsPosViewModel; //= new ViewModelProvider(this).get(MapViewModel.class);
+ private MQTTPositionsViewModel positionsViewModel;
private final HashMap<String,Marker> busPositionMarkersByTrip = new HashMap<>();
private FolderOverlay busPositionsOverlay = null;
@@ -270,6 +272,7 @@
.show();
});
+
return root;
}
@@ -277,7 +280,9 @@
public void onAttach(@NonNull Context context) {
super.onAttach(context);
- mapViewModel = new ViewModelProvider(this).get(MapViewModel.class);
+ //gtfsPosViewModel = new ViewModelProvider(this).get(GTFSPositionsViewModel.class);
+ //viewModel
+ positionsViewModel = new ViewModelProvider(this).get(MQTTPositionsViewModel.class);
if (context instanceof FragmentListenerMain) {
listenerMain = (FragmentListenerMain) context;
} else {
@@ -306,6 +311,7 @@
}
}
tripMarkersAnimators.clear();
+ positionsViewModel.stopPositionsListening();
if (stopFetcher!= null)
stopFetcher.cancel(true);
@@ -342,12 +348,15 @@
public void onResume() {
super.onResume();
if(listenerMain!=null) listenerMain.readyGUIfor(FragmentKind.MAP);
- if(mapViewModel!=null) {
- mapViewModel.requestUpdates();
+ if(positionsViewModel !=null) {
+ //gtfsPosViewModel.requestUpdates();
+ positionsViewModel.requestPosUpdates(MQTTMatoClient.LINES_ALL);
//mapViewModel.testCascade();
- mapViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> {
+ positionsViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> {
Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: "+dat);
- mapViewModel.downloadTripsFromMato(dat);
+ //gtfsPosViewModel.downloadTripsFromMato(dat);
+ MatoTripsDownloadWorker.Companion.downloadTripsFromMato(dat,getContext().getApplicationContext(),
+ "BusTO-MatoTripDownload");
});
}
}
@@ -484,11 +493,16 @@
@SuppressLint("MissingPermission")
Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
+
if (userLocation != null) {
- mapController.setZoom(POSITION_FOUND_ZOOM);
- startPoint = new GeoPoint(userLocation);
- found = true;
- setLocationFollowing(true);
+ double distan = utils.measuredistanceBetween(userLocation.getLatitude(), userLocation.getLongitude(),
+ DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON);
+ if (distan < 100_000.0) {
+ mapController.setZoom(POSITION_FOUND_ZOOM);
+ startPoint = new GeoPoint(userLocation);
+ found = true;
+ setLocationFollowing(true);
+ }
}
}
if(!found){
@@ -528,14 +542,16 @@
}
- if(mapViewModel!=null){
+ if(positionsViewModel !=null){
//should always be the case
- mapViewModel.getUpdatesWithTripAndPatterns().observe(this, data->{
+ positionsViewModel.getUpdatesWithTripAndPatterns().observe(getViewLifecycleOwner(), data->{
Log.d(DEBUG_TAG, "Have "+data.size()+" trip updates, has Map start finished: "+hasMapStartFinished);
if (hasMapStartFinished) updateBusPositionsInMap(data);
- if(!isDetached())
- mapViewModel.requestDelayedUpdates(4000);
+ //if(!isDetached())
+ // gtfsPosViewModel.requestDelayedUpdates(4000);
});
+ } else {
+ Log.e(DEBUG_TAG, "PositionsViewModel is null");
}
map.getOverlays().add(this.busPositionsOverlay);
//set map as started
@@ -560,21 +576,16 @@
new AsyncStopFetcher.BoundingBoxLimit(lngFrom,lngTo,latFrom, latTo));
}
- private void updateBusMarker(final Marker marker,final GtfsPositionUpdate posUpdate,@Nullable boolean justCreated){
+ private void updateBusMarker(final Marker marker, final LivePositionUpdate posUpdate, @Nullable boolean justCreated){
GeoPoint position;
final String updateID = posUpdate.getTripID();
if(!justCreated){
position = marker.getPosition();
if(posUpdate.getLatitude()!=position.getLatitude() || posUpdate.getLongitude()!=position.getLongitude()){
GeoPoint newpos = new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude());
- ObjectAnimator valueAnimator = MarkerAnimation.makeMarkerAnimator(map, marker, newpos, new GeoPointInterpolator.LinearFixed(), 2500);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- valueAnimator.setAutoCancel(true);
- } else if(tripMarkersAnimators.containsKey(updateID)) {
- ObjectAnimator otherAnim = tripMarkersAnimators.get(updateID);
- assert otherAnim != null;
- otherAnim.cancel();
- }
+ ObjectAnimator valueAnimator = MarkerUtils.makeMarkerAnimator(
+ map, marker, newpos, MarkerUtils.LINEAR_ANIMATION, 1200);
+ valueAnimator.setAutoCancel(true);
tripMarkersAnimators.put(updateID,valueAnimator);
valueAnimator.start();
}
@@ -585,17 +596,18 @@
marker.setPosition(position);
}
- marker.setRotation(posUpdate.getBearing()*(-1.f));
+ if(posUpdate.getBearing()!=null)
+ marker.setRotation(posUpdate.getBearing()*(-1.f));
}
- private void updateBusPositionsInMap(HashMap<String, Pair<GtfsPositionUpdate, TripAndPatternWithStops>> tripsPatterns){
+ private void updateBusPositionsInMap(HashMap<String, Pair<LivePositionUpdate, TripAndPatternWithStops>> tripsPatterns){
Log.d(DEBUG_TAG, "Updating positions of the buses");
//if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay();
final ArrayList<String> noPatternsTrips = new ArrayList<>();
for(String tripID: tripsPatterns.keySet()) {
- final Pair<GtfsPositionUpdate, TripAndPatternWithStops> pair = tripsPatterns.get(tripID);
+ final Pair<LivePositionUpdate, TripAndPatternWithStops> pair = tripsPatterns.get(tripID);
if (pair == null) continue;
- final GtfsPositionUpdate update = pair.getFirst();
+ final LivePositionUpdate update = pair.getFirst();
final TripAndPatternWithStops tripWithPatternStops = pair.getSecond();
@@ -624,8 +636,8 @@
R.dimen.map_icons_size, R.dimen.map_icons_size);
*/
- String route = GtfsUtils.getLineNameFromGtfsID(update.getRouteID());
- final Drawable mdraw = ResourcesCompat.getDrawable(getResources(),R.drawable.point_heading_icon, null);
+ //String route = GtfsUtils.getLineNameFromGtfsID(update.getRouteID());
+ final Drawable mdraw = ResourcesCompat.getDrawable(getResources(),R.drawable.map_bus_position_icon, null);
/*final Drawable mdraw = DrawableUtils.Companion.writeOnDrawable(getResources(),
R.drawable.point_heading_icon,
R.color.white,
@@ -641,12 +653,11 @@
MatoPattern markerPattern = null;
if(tripWithPatternStops != null && tripWithPatternStops.getPattern()!=null)
markerPattern = tripWithPatternStops.getPattern();
- marker.setInfoWindow(new BusInfoWindow(map, update, markerPattern , () -> {
-
- }));
+ marker.setInfoWindow(new BusInfoWindow(map, update, markerPattern , false, (pattern) -> { }));
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);
updateBusMarker(marker, update, true);
- // the overlay is null when it's not attached yet?
+ // the overlay is null when it's not attached yet?5
// cannot recreate it because it becomes null very soon
// if(busPositionsOverlay == null) busPositionsOverlay = new FolderOverlay();
//save the marker
@@ -717,7 +728,7 @@
// set custom info window as info window
CustomInfoWindow popup = new CustomInfoWindow(map, stopID, stopName, routesStopping,
- responder);
+ responder, R.layout.linedetail_stop_infowindow, R.color.red_darker);
marker.setInfoWindow(popup);
// make the marker clickable
@@ -741,7 +752,7 @@
// set its position
marker.setPosition(geoPoint);
- marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER);
// add to it an icon
//marker.setIcon(getResources().getDrawable(R.drawable.bus_marker));
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -59,6 +59,7 @@
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB.Contract.*;
import it.reyboz.bustorino.adapters.SquareStopAdapter;
+import it.reyboz.bustorino.middleware.AutoFitGridLayoutManager;
import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.StopSorterByDistance;
@@ -632,43 +633,4 @@
}
}
-
- /**
- * Simple trick to get an automatic number of columns (from https://www.journaldev.com/13792/android-gridlayoutmanager-example)
- *
- */
- class AutoFitGridLayoutManager extends GridLayoutManager {
-
- private int columnWidth;
- private boolean columnWidthChanged = true;
-
- public AutoFitGridLayoutManager(Context context, int columnWidth) {
- super(context, 1);
-
- setColumnWidth(columnWidth);
- }
-
- public void setColumnWidth(int newColumnWidth) {
- if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
- columnWidth = newColumnWidth;
- columnWidthChanged = true;
- }
- }
-
- @Override
- public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
- if (columnWidthChanged && columnWidth > 0) {
- int totalSpace;
- if (getOrientation() == VERTICAL) {
- totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
- } else {
- totalSpace = getHeight() - getPaddingTop() - getPaddingBottom();
- }
- int spanCount = Math.max(1, totalSpace / columnWidth);
- setSpanCount(spanCount);
- columnWidthChanged = false;
- }
- super.onLayoutChildren(recycler, state);
- }
- }
}
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
@@ -1,24 +1,19 @@
package it.reyboz.bustorino.fragments
import android.os.Bundle
+import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
+import android.widget.EditText
import android.widget.TextView
-import android.widget.Toast
-import com.android.volley.Response
-import com.google.transit.realtime.GtfsRealtime
+import androidx.fragment.app.viewModels
import it.reyboz.bustorino.R
-import it.reyboz.bustorino.backend.NetworkVolleyManager
-import it.reyboz.bustorino.backend.gtfs.GtfsPositionUpdate
-import it.reyboz.bustorino.backend.gtfs.GtfsRtPositionsRequest
+import it.reyboz.bustorino.backend.mato.MQTTMatoClient
+import it.reyboz.bustorino.viewmodels.MQTTPositionsViewModel
-// TODO: Rename parameter arguments, choose names that match
-// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
-private const val ARG_PARAM1 = "param1"
-private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
@@ -30,7 +25,14 @@
private lateinit var buttonLaunch: Button
private lateinit var messageTextView: TextView
- private val requestListener = object: GtfsRtPositionsRequest.Companion.RequestListener{
+ private var subscribed = false
+ private lateinit var mqttMatoClient: MQTTMatoClient
+
+ private lateinit var lineEditText: EditText
+
+ private val mqttViewModel: MQTTPositionsViewModel by viewModels()
+
+ /*private val requestListener = object: GtfsRtPositionsRequest.Companion.RequestListener{
override fun onResponse(response: ArrayList<GtfsPositionUpdate>?) {
if (response == null) return
@@ -43,6 +45,14 @@
messageTextView.text = "Entity message 0: ${position}"
}
+
+ }
+ */
+
+ private val listener = MQTTMatoClient.Companion.MQTTMatoListener{
+
+ messageTextView.text = "Update: ${it}"
+ Log.d("BUSTO-TestMQTT", "Received update $it")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -60,16 +70,37 @@
val rootView= inflater.inflate(R.layout.fragment_test_realtime_gtfs, container, false)
buttonLaunch = rootView.findViewById(R.id.btn_download_data)
+ buttonLaunch.text="Start"
messageTextView = rootView.findViewById(R.id.gtfsMessageTextView)
+ lineEditText = rootView.findViewById(R.id.lineEditText)
+
+ mqttViewModel.updatesWithTripAndPatterns.observe(viewLifecycleOwner){
+ val upds = it.entries.map { it.value.first }
+ messageTextView.text = "$upds"
+ }
buttonLaunch.setOnClickListener {
context?.let {cont->
- val req = GtfsRtPositionsRequest(
+ /*val req = GtfsRtPositionsRequest(
Response.ErrorListener { Toast.makeText(cont, "Error: ${it.message}",Toast.LENGTH_SHORT) },
requestListener
)
NetworkVolleyManager.getInstance(cont).addToRequestQueue(req)
+
+ */
+ subscribed = if(subscribed){
+ //mqttMatoClient.desubscribe(listener)
+ mqttViewModel.stopPositionsListening()
+ buttonLaunch.text="Start"
+ false
+ } else{
+ //mqttMatoClient.startAndSubscribe(lineEditText.text.trim().toString(), listener)
+ mqttViewModel.requestPosUpdates(lineEditText.text.trim().toString())
+ buttonLaunch.text="Stop"
+ true
+ }
+
}
diff --git a/app/src/main/java/it/reyboz/bustorino/map/BusInfoWindow.kt b/app/src/main/java/it/reyboz/bustorino/map/BusInfoWindow.kt
--- a/app/src/main/java/it/reyboz/bustorino/map/BusInfoWindow.kt
+++ b/app/src/main/java/it/reyboz/bustorino/map/BusInfoWindow.kt
@@ -18,34 +18,47 @@
package it.reyboz.bustorino.map
import android.annotation.SuppressLint
-import android.view.MotionEvent
-import android.view.View
import android.view.View.*
+import android.widget.ImageView
import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.marginEnd
import it.reyboz.bustorino.R
-import it.reyboz.bustorino.backend.gtfs.GtfsPositionUpdate
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
import it.reyboz.bustorino.backend.gtfs.GtfsUtils
-import it.reyboz.bustorino.data.gtfs.GtfsTrip
+import it.reyboz.bustorino.backend.utils
import it.reyboz.bustorino.data.gtfs.MatoPattern
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.infowindow.BasicInfoWindow
@SuppressLint("ClickableViewAccessibility")
class BusInfoWindow(map: MapView,
- val update: GtfsPositionUpdate,
+ private val routeName: String,
+ private val vehicleLabel: String,
var pattern: MatoPattern?,
- private val touchUp: onTouchUp):
+ val showClose: Boolean,
+ private val touchUp: onTouchUp
+ ):
BasicInfoWindow(R.layout.bus_info_window,map) {
init {
mView.setOnTouchListener { view, motionEvent ->
- touchUp.onActionUp()
+ touchUp.onActionUp(pattern)
close()
//mView.performClick()
true
}
}
+ constructor(map: MapView, update: LivePositionUpdate, pattern: MatoPattern?, showClose: Boolean, touchUp: onTouchUp, ):
+ this(map,
+ GtfsUtils.getLineNameFromGtfsID(update.routeID),
+ update.vehicle,
+ pattern,
+ showClose,
+ touchUp
+ )
+
override fun onOpen(item: Any?) {
// super.onOpen(item)
@@ -53,10 +66,13 @@
val descrView = mView.findViewById<TextView>(R.id.businfo_description)
val subdescrView = mView.findViewById<TextView>(R.id.businfo_subdescription)
- val nameRoute = GtfsUtils.getLineNameFromGtfsID(update.routeID)
- titleView.text = (mView.resources.getString(R.string.line_fill, nameRoute)
+ val iconClose = mView.findViewById<ImageView>(R.id.closeIcon)
+
+ //val nameRoute = GtfsUtils.getLineNameFromGtfsID(update.lineGtfsId)
+
+ titleView.text = (mView.resources.getString(R.string.line_fill, routeName)
)
- subdescrView.text = update.vehicleInfo.label
+ subdescrView.text = vehicleLabel
if(pattern!=null){
@@ -65,7 +81,19 @@
} else{
descrView.visibility = GONE
}
+ if(!showClose){
+ iconClose.visibility = GONE
+ val ctx = titleView.context
+ val layPars = (titleView.layoutParams as ConstraintLayout.LayoutParams).apply {
+ marginStart= 0 //utils.convertDipToPixelsInt(ctx, 8.0)//8.dpToPixels()
+ topMargin=utils.convertDipToPixelsInt(ctx, 4.0)
+ marginEnd=0
+ bottomMargin=0
+ }
+ //titleView.layoutParams = layPars
+ }
}
+
fun setPatternAndDraw(pattern: MatoPattern?){
if(pattern==null){
return
@@ -77,6 +105,6 @@
}
fun interface onTouchUp{
- fun onActionUp()
+ fun onActionUp(pattern: MatoPattern?)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt b/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/map/BusPositionUtils.kt
@@ -0,0 +1,41 @@
+package it.reyboz.bustorino.map
+
+import android.animation.ObjectAnimator
+import android.util.Log
+import androidx.core.content.res.ResourcesCompat
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
+import it.reyboz.bustorino.data.gtfs.MatoPattern
+import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
+import it.reyboz.bustorino.fragments.MapFragment
+import org.osmdroid.util.GeoPoint
+import org.osmdroid.views.MapView
+import org.osmdroid.views.overlay.Marker
+
+class BusPositionUtils {
+ companion object{
+ @JvmStatic
+ public fun updateBusPositionMarker(map: MapView, marker: Marker?, posUpdate: LivePositionUpdate,
+ tripMarkersAnimators: HashMap<String, ObjectAnimator>,
+ justCreated: Boolean) {
+ val position: GeoPoint
+ val updateID = posUpdate.tripID
+ if (!justCreated) {
+ position = marker!!.position
+ if (posUpdate.latitude != position.latitude || posUpdate.longitude != position.longitude) {
+ val newpos = GeoPoint(posUpdate.latitude, posUpdate.longitude)
+ val valueAnimator = MarkerUtils.makeMarkerAnimator(
+ map, marker, newpos, MarkerUtils.LINEAR_ANIMATION, 1200
+ )
+ valueAnimator.setAutoCancel(true)
+ tripMarkersAnimators.put(updateID, valueAnimator)
+ valueAnimator.start()
+ }
+ //marker.setPosition(new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude()));
+ } else {
+ position = GeoPoint(posUpdate.latitude, posUpdate.longitude)
+ marker!!.position = position
+ }
+ if (posUpdate.bearing != null) marker.rotation = posUpdate.bearing * -1f
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/map/CustomInfoWindow.java b/app/src/main/java/it/reyboz/bustorino/map/CustomInfoWindow.java
--- a/app/src/main/java/it/reyboz/bustorino/map/CustomInfoWindow.java
+++ b/app/src/main/java/it/reyboz/bustorino/map/CustomInfoWindow.java
@@ -23,11 +23,13 @@
import android.view.MotionEvent;
import android.view.View;
+import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.infowindow.BasicInfoWindow;
@@ -37,6 +39,8 @@
//TODO: Make the action on the Click customizable
private final TouchResponder touchResponder;
private final String stopID, name, routesStopping;
+
+ private final int colorResID;
//final DisplayMetrics metrics;
@Override
@@ -45,6 +49,7 @@
TextView descr_textView = mView.findViewById(R.id.bubble_description);
CharSequence text = descr_textView.getText();
TextView titleTV = mView.findViewById(R.id.bubble_title);
+ titleTV.setTextColor(ContextCompat.getColor(mView.getContext(),colorResID));
//Log.d("BusTO-MapInfoWindow", "Descrip: "+text+", title "+(titleTV==null? "null": titleTV.getText()));
if (text==null || !text.toString().isEmpty()){
@@ -61,16 +66,34 @@
subDescriptTextView.setVisibility(View.VISIBLE);
}
+ //check if there is a close image
+ ImageView image = mView.findViewById(R.id.closeIcon);
+ if (image != null) {
+ image.setOnClickListener( view -> close());
+ }
+
+ }
+ public CustomInfoWindow(MapView mapView, String stopID, String name, String routesStopping,
+ TouchResponder responder){
+
+ this(mapView, stopID, name, routesStopping, responder,R.layout.map_popup, R.color.red_darker);
}
@SuppressLint("ClickableViewAccessibility")
- public CustomInfoWindow(MapView mapView, String stopID, String name, String routesStopping, TouchResponder responder) {
+ public CustomInfoWindow(MapView mapView,
+ String stopID,
+ String name,
+ String routesStopping,
+ TouchResponder responder,
+ int layoutId,
+ int colorResId) {
// get the personalized layout
- super(R.layout.map_popup, mapView);
+ super(layoutId, mapView);
touchResponder =responder;
this.stopID = stopID;
this.name = name;
this.routesStopping = routesStopping;
+ colorResID = colorResId;
//metrics = Resources.getSystem().getDisplayMetrics();
@@ -82,6 +105,8 @@
}
return true;
});
+
+
}
public interface TouchResponder{
diff --git a/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/map/MapViewModel.kt
@@ -0,0 +1,15 @@
+package it.reyboz.bustorino.map
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class MapViewModel : ViewModel() {
+
+ val currentLat = MutableLiveData(INVALID)
+ val currentLong = MutableLiveData(INVALID)
+ val currentZoom = MutableLiveData(-10.0)
+
+ companion object{
+ const val INVALID = -1000.0
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/map/MarkerAnimation.java b/app/src/main/java/it/reyboz/bustorino/map/MarkerAnimation.java
deleted file mode 100644
--- a/app/src/main/java/it/reyboz/bustorino/map/MarkerAnimation.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package it.reyboz.bustorino.map;
-
-/* Copyright 2013 Google Inc.
- Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0.html */
-
-
- import android.animation.ObjectAnimator;
- import android.animation.TypeEvaluator;
- import android.util.Property;
-
- import org.osmdroid.util.GeoPoint;
- import org.osmdroid.views.MapView;
- import org.osmdroid.views.overlay.Marker;
-
-public class MarkerAnimation {
-
-
- public static ObjectAnimator makeMarkerAnimator(final MapView map, Marker marker, GeoPoint finalPosition, final GeoPointInterpolator GeoPointInterpolator, int durationMs) {
- TypeEvaluator<GeoPoint> typeEvaluator = new TypeEvaluator<GeoPoint>() {
- @Override
- public GeoPoint evaluate(float fraction, GeoPoint startValue, GeoPoint endValue) {
- return GeoPointInterpolator.interpolate(fraction, startValue, endValue);
- }
- };
- Property<Marker, GeoPoint> property = Property.of(Marker.class, GeoPoint.class, "position");
- ObjectAnimator animator = ObjectAnimator.ofObject(marker, property, typeEvaluator, finalPosition);
- animator.setDuration(durationMs);
- //animator.start();
- return animator;
- }
-}
diff --git a/app/src/main/java/it/reyboz/bustorino/map/MarkerUtils.java b/app/src/main/java/it/reyboz/bustorino/map/MarkerUtils.java
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/map/MarkerUtils.java
@@ -0,0 +1,102 @@
+package it.reyboz.bustorino.map;
+
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+import android.util.Property;
+
+
+import android.view.animation.LinearInterpolator;
+import it.reyboz.bustorino.R;
+import org.osmdroid.util.GeoPoint;
+import org.osmdroid.views.MapView;
+import org.osmdroid.views.overlay.Marker;
+import org.osmdroid.views.overlay.infowindow.InfoWindow;
+
+public class MarkerUtils {
+
+ public static final int LINEAR_ANIMATION = 1;
+
+ /* Copyright 2013 Google Inc.
+ Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0.html */
+ public static ObjectAnimator makeMarkerAnimator(final MapView map, Marker marker, GeoPoint finalPosition, int animationType, int durationMs) {
+
+ GeoPointInterpolator interpolator;
+ switch (animationType){
+ case LINEAR_ANIMATION:
+ interpolator = new GeoPointInterpolator.Linear();
+ break;
+ default:
+ throw new IllegalArgumentException("Value "+animationType+ " for animationType is invalid");
+ }
+ TypeEvaluator<GeoPoint> typeEvaluator = (fraction, startValue, endValue) ->
+ interpolator.interpolate(fraction, startValue, endValue);
+ Property<Marker, GeoPoint> property = Property.of(Marker.class, GeoPoint.class, "position");
+ ObjectAnimator animator = ObjectAnimator.ofObject(marker, property, typeEvaluator, finalPosition);
+ switch (animationType){
+ case LINEAR_ANIMATION:
+
+ animator.setInterpolator(new LinearInterpolator());
+ default:
+ }
+ animator.setDuration(durationMs);
+ //animator.start();
+ return animator;
+ }
+
+ public static Marker makeMarker(GeoPoint geoPoint, String stopID, String stopName,
+ String routesStopping,
+ MapView map,
+ CustomInfoWindow.TouchResponder responder,
+ Drawable icon,
+ int infoWindowLayout,
+ int titleColorId) {
+
+ // add a marker
+ final Marker marker = new Marker(map);
+
+ // set custom info window as info window
+ CustomInfoWindow popup = new CustomInfoWindow(map, stopID, stopName, routesStopping, responder, infoWindowLayout, titleColorId);
+ marker.setInfoWindow(popup);
+
+ // make the marker clickable
+ marker.setOnMarkerClickListener((thisMarker, mapView) -> {
+ if (thisMarker.isInfoWindowOpen()) {
+ // on second click
+ Log.w("BusTO-OsmMap", "Pressed on the click marker");
+ } else {
+ // on first click
+
+ // hide all opened info window
+ InfoWindow.closeAllInfoWindowsOn(map);
+ // show this particular info window
+ thisMarker.showInfoWindow();
+ // move the map to its position
+ map.getController().animateTo(thisMarker.getPosition());
+ }
+
+ return true;
+ });
+
+ // set its position
+ marker.setPosition(geoPoint);
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
+ // add to it an icon
+ //marker.setIcon(getResources().getDrawable(R.drawable.bus_marker));
+
+ marker.setIcon(icon);
+ // add to it a title
+ marker.setTitle(stopName);
+ // set the description as the ID
+ marker.setSnippet(stopID);
+
+ // show popup info window of the searched marker
+ /*if (isStartMarker) {
+ marker.showInfoWindow();
+ //map.getController().animateTo(marker.getPosition());
+ }*/
+
+ return marker;
+ }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/middleware/AutoFitGridLayoutManager.kt b/app/src/main/java/it/reyboz/bustorino/middleware/AutoFitGridLayoutManager.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/middleware/AutoFitGridLayoutManager.kt
@@ -0,0 +1,41 @@
+package it.reyboz.bustorino.middleware
+
+import android.content.Context
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.Recycler
+
+/**
+ * Simple trick to get an automatic number of columns (from https://www.journaldev.com/13792/android-gridlayoutmanager-example)
+ *
+ */
+class AutoFitGridLayoutManager(context: Context?, columnWidth: Int):
+ GridLayoutManager(context, 1) {
+ private var columnWidth = 0
+ private var columnWidthChanged = true
+
+ init {
+ setColumnWidth(columnWidth)
+ }
+
+ fun setColumnWidth(newColumnWidth: Int) {
+ if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
+ columnWidth = newColumnWidth
+ columnWidthChanged = true
+ }
+ }
+
+ override fun onLayoutChildren(recycler: Recycler, state: RecyclerView.State) {
+ if (columnWidthChanged && columnWidth > 0) {
+ val totalSpace: Int = if (orientation == VERTICAL) {
+ width - paddingRight - paddingLeft
+ } else {
+ height - paddingTop - paddingBottom
+ }
+ val spanCount = Math.max(1, totalSpace / columnWidth)
+ setSpanCount(spanCount)
+ columnWidthChanged = false
+ }
+ super.onLayoutChildren(recycler, state)
+ }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt b/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt
--- a/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt
+++ b/app/src/main/java/it/reyboz/bustorino/util/ViewUtils.kt
@@ -2,8 +2,10 @@
import android.graphics.Rect
import android.util.Log
-
import android.view.View
+import android.view.WindowManager
+import android.view.animation.Animation
+import android.view.animation.Transformation
import androidx.core.widget.NestedScrollView
@@ -29,5 +31,65 @@
return false
}
}
+
+ //from https://stackoverflow.com/questions/4946295/android-expand-collapse-animation
+ fun expand(v: View,duration: Long, layoutHeight: Int = WindowManager.LayoutParams.WRAP_CONTENT) {
+ val matchParentMeasureSpec =
+ View.MeasureSpec.makeMeasureSpec((v.parent as View).width, View.MeasureSpec.EXACTLY)
+ val wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ v.measure(matchParentMeasureSpec, wrapContentMeasureSpec)
+ val targetHeight = v.measuredHeight
+
+ // Older versions of android (pre API 21) cancel animations for views with a height of 0.
+ v.layoutParams.height = 1
+ v.visibility = View.VISIBLE
+ val a: Animation = object : Animation() {
+ override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
+ v.layoutParams.height =
+ if (interpolatedTime == 1f) layoutHeight
+ else (targetHeight * interpolatedTime).toInt()
+ v.requestLayout()
+ }
+
+ override fun willChangeBounds(): Boolean {
+ return true
+ }
+ }
+
+ // Expansion speed of 1dp/ms
+ if(duration == DEF_DURATION)
+ a.duration = (targetHeight / v.context.resources.displayMetrics.density).toInt().toLong()
+ else
+ a.duration = duration
+ v.startAnimation(a)
+ }
+
+ fun collapse(v: View, duration: Long): Animation {
+ val initialHeight = v.measuredHeight
+ val a: Animation = object : Animation() {
+ override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
+ if (interpolatedTime == 1f) {
+ v.visibility = View.GONE
+ } else {
+ v.layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
+ v.requestLayout()
+ }
+ }
+
+ override fun willChangeBounds(): Boolean {
+ return true
+ }
+ }
+
+ // Collapse speed of 1dp/ms
+ if (duration == DEF_DURATION)
+ a.duration = (initialHeight / v.context.resources.displayMetrics.density).toInt().toLong()
+ else
+ a.duration = duration
+ v.startAnimation(a)
+ return a
+ }
+
+ const val DEF_DURATION: Long = -2
}
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesGridShowingViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesGridShowingViewModel.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesGridShowingViewModel.kt
@@ -0,0 +1,27 @@
+package it.reyboz.bustorino.viewmodels
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import it.reyboz.bustorino.data.GtfsRepository
+import it.reyboz.bustorino.data.NextGenDB
+import it.reyboz.bustorino.data.OldDataRepository
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase
+
+class LinesGridShowingViewModel(application: Application) : AndroidViewModel(application) {
+
+ private val gtfsRepo: GtfsRepository
+
+ init {
+ val gtfsDao = GtfsDatabase.getGtfsDatabase(application).gtfsDao()
+ gtfsRepo = GtfsRepository(gtfsDao)
+
+ }
+
+ val routesLiveData = gtfsRepo.getAllRoutes()
+
+ val isUrbanExpanded = MutableLiveData(true)
+ val isExtraUrbanExpanded = MutableLiveData(false)
+ val isTouristExpanded = MutableLiveData(false)
+}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/MQTTPositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/MQTTPositionsViewModel.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/MQTTPositionsViewModel.kt
@@ -0,0 +1,165 @@
+/*
+ BusTO - ViewModel components
+ Copyright (C) 2023 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.viewmodels
+
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.*
+import it.reyboz.bustorino.backend.gtfs.LivePositionUpdate
+import it.reyboz.bustorino.backend.mato.MQTTMatoClient
+import it.reyboz.bustorino.data.GtfsRepository
+import it.reyboz.bustorino.data.MatoPatternsDownloadWorker
+import it.reyboz.bustorino.data.gtfs.TripAndPatternWithStops
+import it.reyboz.bustorino.fragments.GTFSPositionsViewModel
+import kotlinx.coroutines.launch
+
+
+typealias UpdatesMap = HashMap<String, LivePositionUpdate>
+
+class MQTTPositionsViewModel(application: Application): AndroidViewModel(application) {
+
+ private val gtfsRepo = GtfsRepository(application)
+
+ //private val updates = UpdatesMap()
+ private val updatesLiveData = MutableLiveData<ArrayList<LivePositionUpdate>>()
+
+ private var mqttClient = MQTTMatoClient.getInstance()
+
+ private var lineListening = ""
+ private var lastTimeReceived: Long = 0
+
+ private val positionListener = 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)
+ }
+ }
+ } else{
+ //we're listening to one
+ if (it.containsKey(lineListening.trim()) ){
+ for(up in it[lineListening]?.values!!){
+ mupds.add(up)
+ }
+ }
+ }
+ val time = System.currentTimeMillis()
+ if(lastTimeReceived == (0.toLong()) || (time-lastTimeReceived)>500){
+ updatesLiveData.value = (mupds)
+ lastTimeReceived = time
+ }
+
+ }
+
+ //find the trip IDs in the updates
+ private val tripsIDsInUpdates = updatesLiveData.map { it ->
+ //Log.d(DEBUG_TI, "Updates map has keys ${upMap.keys}")
+ it.map { pos -> "gtt:"+pos.tripID }
+
+ }
+ // get the trip IDs in the DB
+ private val gtfsTripsPatternsInDB = tripsIDsInUpdates.switchMap {
+ Log.i(DEBUG_TI, "tripsIds in updates changed: ${it.size}")
+ gtfsRepo.gtfsDao.getTripPatternStops(it)
+ }
+ //trip IDs to query, which are not present in the DB
+ //REMEMBER TO OBSERVE THIS IN THE MAP
+ val tripsGtfsIDsToQuery: LiveData<List<String>> = gtfsTripsPatternsInDB.map { tripswithPatterns ->
+ val tripNames=tripswithPatterns.map { twp-> twp.trip.tripID }
+ Log.i(DEBUG_TI, "Have ${tripswithPatterns.size} trips in the DB")
+ if (tripsIDsInUpdates.value!=null)
+ return@map tripsIDsInUpdates.value!!.filter { !(tripNames.contains(it) || it.contains("null"))}
+ else {
+ Log.e(DEBUG_TI,"Got results for gtfsTripsInDB but not tripsIDsInUpdates??")
+ return@map ArrayList<String>()
+ }
+ }
+
+ // unify trips with updates
+ val updatesWithTripAndPatterns = gtfsTripsPatternsInDB.map { tripPatterns->
+ Log.i(DEBUG_TI, "Mapping trips and patterns")
+ val mdict = HashMap<String,Pair<LivePositionUpdate, TripAndPatternWithStops?>>()
+ //missing patterns
+ val routesToDownload = HashSet<String>()
+ if(updatesLiveData.value!=null)
+ for(update in updatesLiveData.value!!){
+
+ val trID:String = update.tripID
+ var found = false
+ for(trip in tripPatterns){
+ if (trip.pattern == null){
+ //pattern is null, which means we have to download
+ // the pattern data from MaTO
+ routesToDownload.add(trip.trip.routeID)
+ }
+ if (trip.trip.tripID == "gtt:$trID"){
+ found = true
+ //insert directly
+ mdict[trID] = Pair(update,trip)
+ break
+ }
+ }
+ if (!found){
+ //Log.d(DEBUG_TI, "Cannot find pattern ${tr}")
+ //give the update anyway
+ mdict[trID] = Pair(update,null)
+ }
+ }
+ //have to request download of missing Patterns
+ if (routesToDownload.size > 0){
+ Log.d(DEBUG_TI, "Have ${routesToDownload.size} missing patterns from the DB: $routesToDownload")
+ //downloadMissingPatterns (ArrayList(routesToDownload))
+ MatoPatternsDownloadWorker.downloadPatternsForRoutes(routesToDownload.toList(), getApplication())
+ }
+
+ return@map mdict
+ }
+
+
+ fun requestPosUpdates(line: String){
+ lineListening = line
+ viewModelScope.launch {
+ mqttClient.startAndSubscribe(line,positionListener, getApplication())
+ }
+
+
+ //updatePositions(1000)
+ }
+
+ fun stopPositionsListening(){
+ viewModelScope.launch {
+ val tt = System.currentTimeMillis()
+ mqttClient.desubscribe(positionListener)
+ val time = System.currentTimeMillis() -tt
+ Log.d(DEBUG_TI, "Took $time ms to unsubscribe")
+ }
+
+ }
+
+ fun retriggerPositionUpdate(){
+ if(updatesLiveData.value!=null){
+ updatesLiveData.postValue(updatesLiveData.value)
+ }
+ }
+
+ companion object{
+ private const val DEBUG_TI = "BusTO-MQTTLiveData"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ball.xml b/app/src/main/res/drawable/ball.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/ball.xml
@@ -0,0 +1,6 @@
+<vector android:height="19dp" android:viewportHeight="200"
+ android:viewportWidth="200" android:width="19dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/line_drawn_poly" android:fillType="evenOdd"
+ android:pathData="M100,100m-100,0a100,100 0,1 1,200 0a100,100 0,1 1,-200 0"
+ android:strokeLineCap="round" android:strokeLineJoin="bevel" android:strokeWidth="0.264999"/>
+</vector>
diff --git a/app/src/main/res/drawable/baseline_chevron_right_24.xml b/app/src/main/res/drawable/baseline_chevron_right_24.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_chevron_right_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="30dp" android:tint="@color/black_900"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
+</vector>
diff --git a/app/src/main/res/drawable/baseline_close_16.xml b/app/src/main/res/drawable/baseline_close_16.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_close_16.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/red_darker" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_list_30.xml b/app/src/main/res/drawable/ic_list_30.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/ic_list_30.xml
@@ -0,0 +1,5 @@
+<vector android:autoMirrored="true" android:height="30dp"
+ android:tint="@color/white" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M3,13h2v-2L3,11v2zM3,17h2v-2L3,15v2zM3,9h2L5,7L3,7v2zM7,13h14v-2L7,11v2zM7,17h14v-2L7,15v2zM7,7v2h14L21,7L7,7z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_map_white_30.xml b/app/src/main/res/drawable/ic_map_white_30.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/ic_map_white_30.xml
@@ -0,0 +1,5 @@
+<vector android:height="30dp" android:tint="#FFFFFF"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="30dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"/>
+</vector>
diff --git a/app/src/main/res/drawable/map_bus_position_icon.xml b/app/src/main/res/drawable/map_bus_position_icon.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/drawable/map_bus_position_icon.xml
@@ -0,0 +1,7 @@
+<vector android:height="40dp" android:viewportHeight="35.719"
+ android:viewportWidth="35.719" android:width="40dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@color/bus_marker_color"
+ android:strokeColor="@color/black"
+ android:pathData="m17.859,0.505c-2.419,2.799 -6.614,7.924 -8.107,10.349 -1.294,2.102 -2.431,4.162 -2.431,6.824 0,5.83 4.714,10.559 10.538,10.583 5.824,-0.024 10.538,-4.753 10.538,-10.583 0,-2.662 -1.138,-4.722 -2.431,-6.824C24.474,8.429 20.279,3.304 17.859,0.505Z"
+ android:strokeLineJoin="round" android:strokeWidth="0.8"/>
+</vector>
diff --git a/app/src/main/res/drawable/point_heading_icon.xml b/app/src/main/res/drawable/point_heading_icon.xml
deleted file mode 100644
--- a/app/src/main/res/drawable/point_heading_icon.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="40dp"
- android:width="40dp"
- android:viewportHeight="35.719"
- android:viewportWidth="35.719"
- >
- <path
-
- android:pathData="M17.859,0.505C15.44,3.305 11.245,8.429 9.753,10.854 8.459,12.956 7.321,15.016 7.321,17.678c0,5.83 4.714,10.559 10.538,10.583zM17.859,0.505c2.419,2.799 6.614,7.924 8.107,10.349 1.294,2.102 2.432,4.162 2.432,6.824 0,5.83 -4.714,10.559 -10.538,10.583z"
- android:fillColor="@color/blue_700"
- android:strokeLineJoin="round"
- android:strokeWidth="0.147034"/>
- <!--android:fillColor="#0e00c1 -->
-</vector>
diff --git a/app/src/main/res/layout/bus_info_window.xml b/app/src/main/res/layout/bus_info_window.xml
--- a/app/src/main/res/layout/bus_info_window.xml
+++ b/app/src/main/res/layout/bus_info_window.xml
@@ -9,56 +9,78 @@
android:textAlignment="center"
android:padding="2dp"
android:gravity="center_horizontal">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_horizontal"
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent" android:layout_height="match_parent"
+
>
<TextView
android:id="@+id/businfo_title"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:textColor="@color/blue_700"
+ android:textColor="@color/bus_marker_color"
android:textSize="16sp"
- android:maxWidth="150sp"
- app:layout_constraintStart_toStartOf="parent"
+ android:maxWidth="130sp"
android:text="BALABALA"
android:textAlignment="center"
+ android:layout_marginTop="4dp"
+ android:layout_marginStart="6dp"
+ android:layout_marginEnd="6dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintBottom_toTopOf="@+id/businfo_description" android:layout_marginRight="5dp"/>
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" app:srcCompat="@drawable/baseline_close_16"
+ android:id="@+id/closeIcon"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginLeft="8dp"
- android:layout_marginStart="8dp"
- android:layout_marginRight="8dp" android:layout_marginEnd="8dp"
- app:layout_constraintTop_toTopOf="parent"/>
+ app:layout_constraintLeft_toRightOf="@id/businfo_title"
+
+ android:layout_alignParentTop="true"
+ app:layout_constraintHorizontal_bias="0.5"
+
+ app:layout_constraintStart_toEndOf="@+id/businfo_title"
+ android:layout_marginTop="4dp"
+ android:layout_marginEnd="2dp"
+ android:layout_marginStart="6dp"
+ />
+
+
<TextView
android:id="@+id/businfo_description"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center_horizontal"
- android:textAlignment="center"
android:textSize="15sp"
- android:maxWidth="100sp"
+ android:maxWidth="120sp"
+ android:textColor="@color/grey_600"
app:layout_constraintTop_toBottomOf="@id/businfo_title"
app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
-
- android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginEnd="8dp"/>
+ app:layout_constraintRight_toRightOf="parent"
+ android:layout_below="@id/businfo_title"
+ android:text="BUCAGLIONE GIANGI"
+ android:gravity="center"
+ android:textAlignment="center"
+ android:layout_marginTop="2dp"
+ app:layout_constraintBottom_toTopOf="@+id/businfo_subdescription"
+ android:layout_marginLeft="4dp" android:layout_marginRight="4dp"/>
<TextView
android:id="@+id/businfo_subdescription"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="5dp"
- android:paddingRight="5dp"
- android:gravity="center_horizontal"
- android:textSize="12sp"
- android:textAlignment="center"
- android:maxWidth="130sp"
+ android:textSize="13sp"
+ android:text="672881"
+ android:textColor="@color/grey_600"
app:layout_constraintTop_toBottomOf="@id/businfo_description"
app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginEnd="8dp"/>
-
- </LinearLayout>
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:layout_marginTop="2dp"
+ android:layout_marginBottom="3dp"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_lines_detail.xml b/app/src/main/res/layout/fragment_lines_detail.xml
--- a/app/src/main/res/layout/fragment_lines_detail.xml
+++ b/app/src/main/res/layout/fragment_lines_detail.xml
@@ -9,47 +9,48 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
- <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:text="Line 10"
android:id="@+id/titleTextView"
android:textAlignment="center"
+ android:textSize="28sp"
- app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"
- android:layout_marginTop="8dp" android:gravity="center_horizontal|center_vertical"/>
+ app:layout_constraintTop_toTopOf="parent"
+ android:layout_marginTop="8dp" android:gravity="center_horizontal|center_vertical"
+ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
+ android:layout_marginStart="8dp" android:layout_marginEnd="8dp"/>
<Spinner
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/patternsSpinner"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/titleTextView"
- android:layout_marginTop="16dp" />
+ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/routeDescrTextView"
+ android:layout_marginTop="8dp" app:layout_constraintTop_toBottomOf="@+id/titleTextView"
+ android:layout_marginStart="4dp"/>
<TextView
- android:text="Descr"
- android:layout_width="0dp"
+ android:text="@string/direction_duep"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/routeDescrTextView"
- app:layout_constraintStart_toEndOf="@id/patternsSpinner"
- app:layout_constraintBottom_toBottomOf="@id/patternsSpinner"
- app:layout_constraintEnd_toEndOf="parent"
-
- android:layout_marginTop="8dp"
- android:layout_marginLeft="16dp"
- android:layout_marginStart="16dp"
- android:textAppearance="@style/TextAppearance.AppCompat.Medium"
-
+ app:layout_constraintStart_toStartOf="parent"
+ android:textAppearance="@style/TextAppearance.AppCompat.Body1"
+ android:textColor="@color/grey_600"
android:gravity="center_vertical"
+ android:textSize="18sp"
+ android:layout_marginLeft="10dp"
- android:layout_marginRight="16dp" android:layout_marginEnd="16dp"/>
+ app:layout_constraintTop_toTopOf="@+id/patternsSpinner"
+ app:layout_constraintBottom_toBottomOf="@+id/patternsSpinner"
+ />
<org.osmdroid.views.MapView android:id="@+id/lineMap"
android:layout_width="fill_parent"
android:layout_height="0dp"
- android:layout_marginTop="20dp"
+ android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/patternsSpinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
- <ImageButton
+ <!--<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/icon_center_map"
@@ -77,7 +78,47 @@
android:layout_marginTop="10dp"
android:layout_marginRight="10dp"
android:layout_marginEnd="10dp"
- />
+ />-->
+ <ImageButton
+ android:src="@drawable/ic_list_30"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:id="@+id/switchImageButton"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/patternsSpinner"
+ android:layout_margin="6dp"
+ android:backgroundTint="@color/blue_500"
+ />
+
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/listDivider"
+ app:layout_constraintTop_toBottomOf="@id/patternsSpinner"
+
+ android:layout_marginTop="8dp"/>
+ <androidx.recyclerview.widget.RecyclerView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:id="@+id/patternStopsRecyclerView"
+ app:layout_constraintTop_toBottomOf="@+id/divider"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintVertical_bias="0.0"
+ android:layout_marginBottom="8dp"
+ android:layout_marginTop="0dp"
+ android:layout_margin="4dp"
+ app:layout_constraintHorizontal_bias="0.0"
+ app:fastScrollEnabled="true"
+ app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
+ app:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
+ app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
+ app:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
+ android:visibility="gone"
+ />
+
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_lines_grid.xml b/app/src/main/res/layout/fragment_lines_grid.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/layout/fragment_lines_grid.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".fragments.LinesGridShowingFragment">
+ <!--<androidx.core.widget.NestedScrollView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/linesScrollView"
+ android:layout_weight="12"
+ >-->
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent" android:layout_height="match_parent"
+ android:animateLayoutChanges="true"
+ >
+ <ImageView
+ android:src="@drawable/baseline_chevron_right_24"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:id="@+id/arrowUrb"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/urbanLinesTitleView"
+ android:layout_margin="4dp"
+ android:layout_marginStart="16dp"
+ android:rotation="0"
+ />
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/urban_lines"
+ android:textAppearance="@style/TextAppearance.AppCompat.Body2"
+ android:textSize="@dimen/subtitle_size"
+ android:layout_margin="4dp"
+ android:textColor="@color/black_900"
+ android:gravity="center"
+ android:id="@+id/urbanLinesTitleView"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintLeft_toRightOf="@id/arrowUrb"
+ app:layout_constraintBottom_toTopOf="@id/urbanLinesRecyclerView"
+ android:layout_marginLeft="6dp"
+ app:layout_constraintVertical_bias="0.0"
+ app:layout_constraintVertical_chainStyle="spread"/>
+ <androidx.recyclerview.widget.RecyclerView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:id="@+id/urbanLinesRecyclerView"
+ android:layout_marginLeft="10dp"
+ android:layout_marginRight="10dp"
+ android:visibility="visible"
+ android:layout_below="@id/urbanLinesTitleView"
+ app:layout_constraintTop_toBottomOf="@id/urbanLinesTitleView"
+ app:layout_constraintBottom_toTopOf="@id/touristLinesTitleView"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintVertical_bias="0.0"
+
+ />
+ <ImageView
+ android:src="@drawable/baseline_chevron_right_24"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:id="@+id/arrowTourist"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/touristLinesTitleView"
+ android:layout_margin="4dp"
+ android:layout_marginStart="16dp"
+ android:rotation="0"
+ />
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/turist_lines"
+ android:textAppearance="@style/TextAppearance.AppCompat.Body2"
+ android:textSize="@dimen/subtitle_size"
+ android:textColor="@color/black_900"
+ android:layout_margin="4dp"
+ android:layout_marginStart="6dp"
+ android:gravity="center"
+ android:id="@+id/touristLinesTitleView"
+ app:layout_constraintLeft_toRightOf="@id/arrowTourist"
+ app:layout_constraintTop_toBottomOf="@id/urbanLinesRecyclerView"
+ app:layout_constraintBottom_toTopOf="@id/touristLinesRecyclerView"
+ app:layout_constraintVertical_bias="0.0"
+ android:layout_marginLeft="6dp"/>
+ <androidx.recyclerview.widget.RecyclerView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:id="@+id/touristLinesRecyclerView"
+ android:layout_marginLeft="10dp"
+ android:layout_marginRight="10dp"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/touristLinesTitleView"
+ app:layout_constraintBottom_toTopOf="@id/extraurbanLinesTitleView"
+ app:layout_constraintVertical_bias="0.0"
+
+ />
+ <ImageView
+ android:src="@drawable/baseline_chevron_right_24"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:id="@+id/arrowExtraurban"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintBottom_toBottomOf="@id/extraurbanLinesTitleView"
+ android:layout_margin="4dp"
+ android:layout_marginStart="16dp"
+ android:rotation="0"
+ />
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/extraurban_lines"
+ android:textAppearance="@style/TextAppearance.AppCompat.Body2"
+ android:textSize="@dimen/subtitle_size"
+ android:layout_margin="4dp"
+ android:textColor="@color/black_900"
+ android:gravity="center"
+ android:layout_marginStart="6dp"
+ android:id="@+id/extraurbanLinesTitleView"
+ app:layout_constraintTop_toBottomOf="@id/touristLinesRecyclerView"
+ app:layout_constraintLeft_toRightOf="@id/arrowExtraurban"
+ app:layout_constraintBottom_toTopOf="@id/extraurbanLinesRecyclerView"
+ app:layout_constraintVertical_bias="0.0"
+
+ />
+ <androidx.recyclerview.widget.RecyclerView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:id="@+id/extraurbanLinesRecyclerView"
+ android:layout_marginLeft="10dp"
+ android:layout_marginRight="10dp"
+ android:visibility="gone"
+ android:layout_below="@id/extraurbanLinesTitleView"
+
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/extraurbanLinesTitleView"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintVertical_bias="0.0"
+
+ />
+ </androidx.constraintlayout.widget.ConstraintLayout>
+ <!--</androidx.core.widget.NestedScrollView>-->
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_test_realtime_gtfs.xml b/app/src/main/res/layout/fragment_test_realtime_gtfs.xml
--- a/app/src/main/res/layout/fragment_test_realtime_gtfs.xml
+++ b/app/src/main/res/layout/fragment_test_realtime_gtfs.xml
@@ -1,27 +1,43 @@
<?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" xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".fragments.TestRealtimeGtfsFragment">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".fragments.TestRealtimeGtfsFragment">
<!-- TODO: Update blank fragment layout -->
+ <EditText
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:inputType="text"
+ android:text="10"
+ android:ems="10"
+ android:id="@+id/lineEditText" app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@+id/btn_download_data" app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:layout_margin="20dp"
+ app:layout_constraintHorizontal_bias="0.5" android:minHeight="48dp"/>
<TextView
android:id="@+id/gtfsMessageTextView"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
android:text="BABLABLA"
- app:layout_constraintTop_toBottomOf="@id/btn_download_data"
+ app:layout_constraintTop_toBottomOf="@+id/btn_download_data"
app:layout_constraintEnd_toEndOf="parent"
- android:layout_margin="20dp" app:layout_constraintStart_toStartOf="parent"/>
+ android:layout_margin="20dp" app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintBottom_toBottomOf="parent"
+ />
<Button
- android:text="Download GTFS update"
+ android:text="Start/stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/btn_download_data"
android:layout_margin="10dp"
- app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="parent"/>
-
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/lineEditText" app:layout_constraintHorizontal_bias="0.5"
+ app:layout_constraintBottom_toTopOf="@+id/gtfsMessageTextView"/>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/line_title_header.xml b/app/src/main/res/layout/line_title_header.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/layout/line_title_header.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:cardCornerRadius="5dp"
+ android:layout_margin="4dp"
+ >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+ <androidx.cardview.widget.CardView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/innerCardView"
+ android:background="@color/orange_500"
+ app:cardCornerRadius="54sp"
+ app:cardElevation="0sp"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="5dp"
+ android:padding="3dp"
+ app:cardBackgroundColor="@color/orange_500"
+
+
+ >
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:minHeight="54sp"
+ android:minWidth="54sp"
+ >
+ <TextView
+ android:id="@+id/lineShortNameTextView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/grey_100"
+ android:textSize="21sp"
+ android:text="231"
+ android:paddingStart="4sp"
+ android:paddingLeft="4sp"
+ android:paddingRight="4sp"
+ android:paddingEnd="4sp"
+ >
+ </TextView>
+ </RelativeLayout>
+ </androidx.cardview.widget.CardView>
+ <TextView android:layout_width="0dp" android:layout_height="match_parent"
+ android:id="@+id/lineDirectionTextView"
+ android:layout_weight="8"
+ android:textSize="20sp"
+ android:text="@string/route_towards_destination"
+ android:layout_margin="5dp"
+ android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+ android:layout_marginEnd="10dp"
+ />
+ </LinearLayout>
+
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/linedetail_stop_infowindow.xml b/app/src/main/res/layout/linedetail_stop_infowindow.xml
new file mode 100644
--- /dev/null
+++ b/app/src/main/res/layout/linedetail_stop_infowindow.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@color/cardview_light_background"
+ app:cardCornerRadius="5dp"
+ app:cardElevation="0dp"
+ android:textAlignment="center"
+ android:padding="0dp"
+ android:gravity="center_horizontal"
+>
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <TextView
+ android:id="@+id/bubble_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textColor="@color/line_drawn_poly"
+ android:textSize="16sp"
+ android:maxWidth="130sp"
+ app:layout_constraintStart_toStartOf="parent"
+ android:text="BALABALA"
+ android:textAlignment="center"
+ android:layout_margin="10dp"
+ app:layout_constraintTop_toTopOf="parent"
+ />
+ <TextView
+ android:id="@+id/bubble_description"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textAlignment="center"
+ android:textSize="15sp"
+ android:maxWidth="100sp"
+ android:text="8"
+ app:layout_constraintTop_toBottomOf="@id/bubble_title"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:layout_marginLeft="8dp" android:layout_marginEnd="8dp"/>
+
+ <TextView
+ android:id="@+id/bubble_subdescription"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:paddingLeft="5dp"
+ android:paddingRight="5dp"
+ android:gravity="center_horizontal"
+ android:textSize="12sp"
+ android:textAlignment="center"
+ android:maxWidth="130sp"
+ android:text="21"
+ app:flow_horizontalBias="0.5"
+ app:layout_constraintTop_toBottomOf="@id/bubble_description"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:layout_marginLeft="8dp" android:layout_marginEnd="8dp"
+ android:layout_marginBottom="10dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ />
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" app:srcCompat="@drawable/baseline_close_16"
+ android:id="@+id/closeIcon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:layout_marginLeft="5dp"
+ android:layout_marginBottom="10dp"
+ android:layout_marginRight="1dp"
+ android:layout_marginTop="1dp"
+
+ app:layout_constraintStart_toEndOf="@+id/bubble_title"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.cardview.widget.CardView>
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -20,6 +20,11 @@
<string name="passages">Fermata: %1$s</string>
<string name="line">Linea</string>
<string name="lines">Linee</string>
+ <string name="urban_lines">Linee urbane</string>
+ <string name="extraurban_lines">Linee extraurbane</string>
+ <string name="turist_lines">Linee turistiche</string>
+ <string name="direction_duep">Direzione:</string>
+
<string name="line_fill">Linea: %1$s</string>
<string name="lines_fill">Linee: %1$s</string>
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
@@ -7,13 +7,18 @@
<color name="orange_700_30light">#994d00</color>
<color name="blue_500">#2196F3</color>
<color name="blue_620">#2a65e8</color>
- <color name="blue_700">#2060dd</color> <!-- #1976D2 -->
+ <color name="blue_700">#2060dd</color>
+ <color name="brown_vd">#8A4247</color><!-- #1976D2 -->
<color name="blue_mid_2">#2378e8</color>
<color name="blue_c_or_700">#0079f5</color>
<color name="teal_dark">#2a968b</color>
<color name="blue_comp_500">#0067ff</color>
+ <color name="blue_extra">#2F59CC</color>
+ <color name="brown_mattone">#CC5E43</color>
+ <color name="green_dark">#548017</color>
+
<color name="teal_500">#009688</color>
<color name="teal_300">#4DB6AC</color>
<color name="teal_200">#80cbc4</color>
@@ -21,14 +26,22 @@
<color name="grey_200">#dddddd</color>
<color name="grey_050">#f8f8f8</color>
<color name="grey_600">#757575</color>
+
<!--<color name="white">#FFFFFF</color>
<color name="accent">#009688</color>-->
<color name="metro_red">#DE0908</color>
<color name="red_darker">#b30000</color>
+ <color name="red_orange">#dd441f</color>
+ <color name="red_dark">#b30d0d</color>
<color name="blue_extraurbano">#2060DD</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
<color name="black_900">#1c1c1c</color>
+
<color name="line_pattern_color">@color/blue_mid_2</color><!-- 2e8df0-->
+ <color name="line_drawn_poly">@color/red_dark</color>
+
+ <color name="bus_marker_color">@color/blue_extra</color>
+
</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -10,5 +10,6 @@
<dimen name="default_textView_margin">6dp</dimen>
<dimen name="margin_arr">5dp</dimen>
+ <dimen name="subtitle_size">28sp</dimen>
</resources>
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
@@ -32,6 +32,11 @@
<string name="results">Choose the bus stop…</string>
<string name="line">Line</string>
<string name="lines">Lines</string>
+ <string name="urban_lines">Urban lines</string>
+ <string name="extraurban_lines">Extra urban lines</string>
+ <string name="turist_lines">Tourist lines</string>
+
+ <string name="direction_duep">Heading to:</string>
<string name="lines_fill">Lines: %1$s</string>
<string name="line_fill">Line: %1$s</string>
<string name="no_passages">No timetable found</string>
diff --git a/app/src/test/java/it/reyboz/bustorino/util/DistanceTest.java b/app/src/test/java/it/reyboz/bustorino/util/DistanceTest.java
new file mode 100644
--- /dev/null
+++ b/app/src/test/java/it/reyboz/bustorino/util/DistanceTest.java
@@ -0,0 +1,13 @@
+package it.reyboz.bustorino.util;
+
+import it.reyboz.bustorino.backend.utils;
+import org.junit.Test;
+import static org.junit.Assert.*;
+public class DistanceTest {
+
+ @Test
+ public void testDistance(){
+ double dist = utils.measuredistanceBetween(44.161957,8.302445, 44.645321, 7.656055);
+ assertEquals(dist,74333.9, 0.05);
+ }
+}
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -5,34 +5,36 @@
mavenCentral()
maven { url 'https://maven.google.com' }
google()
+ maven { url 'https://jitpack.io' }
+
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20"
}
}
ext {
- androidXTestVersion = "1.4.0"
+ androidXTestVersion = "1.5.0"
//multidex
multidex_version = "2.0.1"
//libraries versions
- fragment_version = "1.4.1"
- activity_version = "1.4.0"
- appcompat_version = "1.4.1"
- preference_version = "1.2.0"
- work_version = "2.7.1"
+ fragment_version = "1.6.1"
+ activity_version = "1.7.2"
+ appcompat_version = "1.6.1"
+ preference_version = "1.2.1"
+ work_version = "2.8.1"
acra_version = "5.7.0"
lifecycle_version = "2.4.1"
arch_version = "2.1.0"
- room_version = "2.4.1"
+ room_version = "2.5.2"
//kotlin
- kotlin_version = '1.6.0'
- coroutines_version = "1.5.0"
+ kotlin_version = '1.8.22'
+ coroutines_version = "1.7.0"
}
@@ -41,7 +43,7 @@
maven { url 'https://maven.google.com' }
google()
mavenCentral()
- //maven { url "https://jitpack.io" }
+ maven { url "https://jitpack.io" }
}
}

File Metadata

Mime Type
text/plain
Expires
Sat, Mar 14, 07:57 (4 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1744744
Default Alt Text
D126.1773471420.diff (170 KB)

Event Timeline