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
index 385de81..caf7657 100644
--- a/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
+++ b/app/src/main/java/it/reyboz/bustorino/backend/mato/MQTTMatoClient.kt
@@ -1,323 +1,329 @@
 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
         }
+        // remove lines that have no responders
+        for(line in respondersMap.keys){
+            if(respondersMap[line]?.isEmpty() == true){
+                respondersMap.remove(line)
+            }
+        }
         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/data/OldDataRepository.java b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.java
deleted file mode 100644
index c521381..0000000
--- a/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-	BusTO - Data components
-    Copyright (C) 2021 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.data;
-
-import android.database.sqlite.SQLiteDatabase;
-import it.reyboz.bustorino.backend.Result;
-import it.reyboz.bustorino.backend.Stop;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-public class OldDataRepository {
-
-    private final Executor executor;
-    private final NextGenDB nextGenDB;
-
-    public OldDataRepository(Executor executor, final NextGenDB nextGenDB) {
-        this.executor = executor;
-        this.nextGenDB = nextGenDB;
-    }
-
-    public void requestStopsWithGtfsIDs(final List<String> gtfsIDs,
-                                        final Callback<List<Stop>> callback){
-        executor.execute(() -> {
-
-            try {
-                //final NextGenDB dbHelper = new NextGenDB(context);
-                final SQLiteDatabase db = nextGenDB.getReadableDatabase();
-
-                final List<Stop> stops = NextGenDB.queryAllStopsWithGtfsIDs(db, gtfsIDs);
-                //Result<List<Stop>> result = Result.success;
-
-                callback.onComplete(Result.success(stops));
-            } catch (Exception e){
-                callback.onComplete(Result.failure(e));
-            }
-        });
-    }
-
-    public interface Callback<T>{
-        void onComplete(Result<T> result);
-    }
-}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt
new file mode 100644
index 0000000..380b67d
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt
@@ -0,0 +1,69 @@
+/*
+	BusTO - Data components
+    Copyright (C) 2021 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.data
+
+import android.content.Context
+import it.reyboz.bustorino.backend.Result
+import it.reyboz.bustorino.backend.Stop
+import java.util.concurrent.Executor
+
+class OldDataRepository(private val executor: Executor, private val nextGenDB: NextGenDB) {
+
+    constructor(executor: Executor, context: Context): this(executor, NextGenDB.getInstance(context))
+    fun requestStopsWithGtfsIDs(
+        gtfsIDs: List<String?>?,
+        callback: Callback<List<Stop>>
+    ) {
+        executor.execute {
+            try {
+                //final NextGenDB dbHelper = new NextGenDB(context);
+                val db = nextGenDB.readableDatabase
+                val stops: List<Stop> = NextGenDB.queryAllStopsWithGtfsIDs(db, gtfsIDs)
+                //Result<List<Stop>> result = Result.success;
+                callback.onComplete(Result.success(stops))
+            } catch (e: Exception) {
+                callback.onComplete(Result.failure(e))
+            }
+        }
+    }
+
+    fun requestStopsInArea(
+        latitFrom: Double,
+        latitTo: Double,
+        longitFrom: Double,
+        longitTo: Double,
+        callback: Callback<java.util.ArrayList<Stop>>
+    ){
+        //Log.d(DEBUG_TAG, "Async Stop Fetcher started working");
+        executor.execute {
+            val stops = nextGenDB.queryAllInsideMapView(
+                latitFrom, latitTo,
+                longitFrom, longitTo
+            )
+            if (stops!=null)
+                callback.onComplete(Result.success(stops))
+        }
+
+    }
+
+
+
+    fun interface Callback<T> {
+        fun onComplete(result: Result<T>)
+    }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java b/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java
index 444934b..3a0c77c 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java
@@ -1,285 +1,285 @@
 /*
 	BusTO (fragments)
     Copyright (C) 2018 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.content.Context;
 
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentTransaction;
 
 import android.os.AsyncTask;
 import android.util.Log;
 import android.widget.Toast;
 
 import it.reyboz.bustorino.R;
 import it.reyboz.bustorino.backend.Fetcher;
 import it.reyboz.bustorino.backend.Palina;
 import it.reyboz.bustorino.backend.Stop;
 import it.reyboz.bustorino.backend.utils;
 import it.reyboz.bustorino.middleware.*;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
 
 /**
  * Helper class to manage the fragments and their needs
  */
 public class FragmentHelper {
     //GeneralActivity act;
     private final FragmentListenerMain listenerMain;
     private final WeakReference<FragmentManager> managerWeakRef;
     private Stop lastSuccessfullySearchedBusStop;
     //support for multiple frames
     private final int secondaryFrameLayout;
     private final int primaryFrameLayout;
     private final Context context;
     public static final int NO_FRAME = -3;
     private static final String DEBUG_TAG = "BusTO FragmHelper";
     private WeakReference<AsyncTask> lastTaskRef;
     private boolean shouldHaltAllActivities=false;
 
 
     public FragmentHelper(FragmentListenerMain listener, FragmentManager framan, Context context, int mainFrame) {
         this(listener,framan, context,mainFrame,NO_FRAME);
     }
 
     public FragmentHelper(FragmentListenerMain listener, FragmentManager fraMan, Context context, int primaryFrameLayout, int secondaryFrameLayout) {
         this.listenerMain = listener;
         this.managerWeakRef = new WeakReference<>(fraMan);
         this.primaryFrameLayout = primaryFrameLayout;
         this.secondaryFrameLayout = secondaryFrameLayout;
         this.context = context.getApplicationContext();
     }
 
     /**
      * Get the last successfully searched bus stop or NULL
      *
      * @return the stop
      */
     public Stop getLastSuccessfullySearchedBusStop() {
         return lastSuccessfullySearchedBusStop;
     }
 
     public void setLastSuccessfullySearchedBusStop(Stop stop) {
         this.lastSuccessfullySearchedBusStop = stop;
     }
 
     public void setLastTaskRef(AsyncTask task) {
         this.lastTaskRef = new WeakReference<>(task);
     }
 
     /**
      * Called when you need to create a fragment for a specified Palina
      * @param p the Stop that needs to be displayed
      */
     public void createOrUpdateStopFragment(Palina p, boolean addToBackStack){
         boolean sameFragment;
         ArrivalsFragment arrivalsFragment = null;
 
         if(managerWeakRef.get()==null || shouldHaltAllActivities) {
             //SOMETHING WENT VERY WRONG
             Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
             return;
         }
 
         FragmentManager fm = managerWeakRef.get();
 
         if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment) {
             arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
             //Log.d(DEBUG_TAG, "Arrivals are for fragment with same stop?");
             if (arrivalsFragment == null) sameFragment = false;
             else sameFragment = arrivalsFragment.isFragmentForTheSameStop(p);
         } else {
             sameFragment = false;
             Log.d(DEBUG_TAG, "We aren't showing an ArrivalsFragment");
 
         }
         setLastSuccessfullySearchedBusStop(p);
         if (sameFragment){
             Log.d("BusTO", "Same bus stop, accessing existing fragment");
             arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
             if (arrivalsFragment == null) sameFragment = false;
         }
         if(!sameFragment) {
             //set the String to be displayed on the fragment
             String displayName = p.getStopDisplayName();
 
             if (displayName != null && displayName.length() > 0) {
                 arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName);
             } else {
                 arrivalsFragment = ArrivalsFragment.newInstance(p.ID);
             }
             String probableTag = ArrivalsFragment.getFragmentTag(p);
             attachFragmentToContainer(fm,arrivalsFragment,new AttachParameters(probableTag, true, addToBackStack));
         }
         // DO NOT CALL `setListAdapter` ever on arrivals fragment
         arrivalsFragment.updateFragmentData(p);
         // enable fragment auto refresh
         arrivalsFragment.setReloadOnResume(true);
 
         listenerMain.hideKeyboard();
         toggleSpinner(false);
     }
 
     /**
      * Called when you need to display the results of a search of stops
      * @param resultList the List of stops found
      * @param query String queried
      */
     public void createStopListFragment(List<Stop> resultList, String query, boolean addToBackStack){
         listenerMain.hideKeyboard();
         StopListFragment listfragment = StopListFragment.newInstance(query);
         if(managerWeakRef.get()==null || shouldHaltAllActivities) {
             //SOMETHING WENT VERY WRONG
             Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
             return;
         }
         attachFragmentToContainer(managerWeakRef.get(),listfragment,
                 new AttachParameters("search_"+query, false,addToBackStack));
         listfragment.setStopList(resultList);
-        listenerMain.readyGUIfor(FragmentKind.STOPS);
+        //listenerMain.readyGUIfor(FragmentKind.STOPS);
         toggleSpinner(false);
 
     }
 
     /**
      * Wrapper for toggleSpinner in Activity
      * @param on new status of spinner system
      */
     public void toggleSpinner(boolean on){
         listenerMain.toggleSpinner(on);
     }
 
     /**
      * Attach a new fragment to a cointainer
      * @param fm the FragmentManager
      * @param fragment the Fragment
      * @param parameters attach parameters
      */
     protected void attachFragmentToContainer(FragmentManager fm,Fragment fragment, AttachParameters parameters){
         if(shouldHaltAllActivities) //nothing to do
             return;
         FragmentTransaction ft = fm.beginTransaction();
         int frameID;
         if(parameters.attachToSecondaryFrame && secondaryFrameLayout!=NO_FRAME)
            // ft.replace(secondaryFrameLayout,fragment,tag);
             frameID = secondaryFrameLayout;
         else frameID = primaryFrameLayout;
         switch (parameters.transaction){
             case REPLACE:
                 ft.replace(frameID,fragment,parameters.tag);
 
         }
         if (parameters.addToBackStack)
             ft.addToBackStack("state_"+parameters.tag);
         ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
         if(!fm.isDestroyed() && !shouldHaltAllActivities)
             ft.commit();
         //fm.executePendingTransactions();
     }
 
     public synchronized void setBlockAllActivities(boolean shouldI) {
         this.shouldHaltAllActivities = shouldI;
     }
 
     public void stopLastRequestIfNeeded(boolean interruptIfRunning){
         if(lastTaskRef == null) return;
         AsyncTask task = lastTaskRef.get();
         if(task!=null){
             task.cancel(interruptIfRunning);
         }
     }
 
     /**
      * Wrapper to show the errors/status that happened
      * @param res result from Fetcher
      */
     public void showErrorMessage(Fetcher.Result res, SearchRequestType type){
         //TODO: implement a common set of errors for all fragments
         if (res==null){
             Log.e(DEBUG_TAG, "Asked to show result with null result");
             return;
         }
         Log.d(DEBUG_TAG, "Showing result for "+res);
         switch (res){
             case OK:
                 break;
             case CLIENT_OFFLINE:
                 showToastMessage(R.string.network_error, true);
                 break;
             case SERVER_ERROR:
                 if (utils.isConnected(context)) {
                     showToastMessage(R.string.parsing_error, true);
                 } else {
                     showToastMessage(R.string.network_error, true);
                 }
             case PARSER_ERROR:
             default:
                 showShortToast(R.string.internal_error);
                 break;
             case QUERY_TOO_SHORT:
                 showShortToast(R.string.query_too_short);
                 break;
             case EMPTY_RESULT_SET:
                 if (type == SearchRequestType.STOPS)
                     showShortToast(R.string.no_bus_stop_have_this_name);
                 else if(type == SearchRequestType.ARRIVALS){
                     showShortToast(R.string.no_arrivals_stop);
                 }
                 break;
             case NOT_FOUND:
                 showShortToast(R.string.no_bus_stop_have_this_name);
                 break;
         }
     }
 
     public void showToastMessage(int messageID, boolean short_lenght) {
         final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
         if (context != null)
         Toast.makeText(context, messageID, length).show();
     }
     private void showShortToast(int messageID){
         showToastMessage(messageID, true);
     }
 
     enum Transaction{
         REPLACE,
     }
     static final class AttachParameters {
         String tag;
         boolean attachToSecondaryFrame;
         Transaction transaction;
         boolean addToBackStack;
 
         public AttachParameters(String tag, boolean attachToSecondaryFrame, Transaction transaction, boolean addToBackStack) {
             this.tag = tag;
             this.attachToSecondaryFrame = attachToSecondaryFrame;
             this.transaction = transaction;
             this.addToBackStack = addToBackStack;
         }
 
         public AttachParameters(String tag, boolean attachToSecondaryFrame, boolean addToBackStack) {
             this.tag = tag;
             this.attachToSecondaryFrame = attachToSecondaryFrame;
             this.addToBackStack = addToBackStack;
             this.transaction = Transaction.REPLACE;
         }
     }
 }
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
index c9d6786..7673975 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesDetailFragment.kt
@@ -1,725 +1,728 @@
 /*
 	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.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 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.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.LinesViewModel
 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.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() : ScreenBaseFragment() {
 
     private lateinit var lineID: String
     private lateinit var patternsSpinner: Spinner
     private var patternsAdapter: ArrayAdapter<String>? = null
 
     //private var patternsSpinnerState: Parcelable? = null
 
     private lateinit var currentPatterns: List<MatoPatternWithStops>
 
     private lateinit var map: MapView
     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")
             }
         }
 
         override fun onLongPressOnStop(stop: Stop?): Boolean {
             TODO("Not yet implemented")
         }
 
     }
 
 
     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
 
         initializeMap(rootView)
 
         initializeRecyclerView()
 
         switchButton.setOnClickListener{
             if(map.visibility == View.VISIBLE){
                 map.visibility = View.GONE
                 stopsRecyclerView.visibility = View.VISIBLE
 
                 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 ->
             if(map.visibility ==View.VISIBLE)
                 showPatternWithStopsOnMap(stops)
             else{
                 if(stopsRecyclerView.visibility==View.VISIBLE)
                     showStopsAsList(stops)
             }
         }
         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}")
 
         //listeners
         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<*>?) {
             }
         }
 
 
         //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
     }
 
     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>){
         currentPatterns = patterns.sortedBy { p-> p.pattern.code }
 
         patternsAdapter?.let {
             it.clear()
             it.addAll(currentPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
             it.notifyDataSetChanged()
         }
         viewingPattern?.let {
             showPattern(it)
         }
 
     }
 
     /**
      * 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}")
         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()
         }
+        //initialize GUI here
+        fragmentListener.readyGUIfor(FragmentKind.LINES)
+
     }
 
     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
index e9dbabf..34a4fb4 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesFragment.kt
@@ -1,418 +1,418 @@
 /*
 	BusTO  - Fragments components
     Copyright (C) 2022 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.content.Context
 import android.os.Bundle
 import android.os.Parcelable
 import android.util.Log
 import android.view.*
 import android.widget.*
 import android.widget.AdapterView.INVALID_POSITION
 import android.widget.AdapterView.OnItemSelectedListener
 import androidx.lifecycle.ViewModelProvider
 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.data.gtfs.GtfsRoute
 import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
-import it.reyboz.bustorino.data.gtfs.PatternStop
 import it.reyboz.bustorino.util.LinesNameSorter
 import it.reyboz.bustorino.util.PatternWithStopsSorter
+import it.reyboz.bustorino.viewmodels.LinesViewModel
 
 class LinesFragment : ScreenBaseFragment() {
 
     companion object {
         fun newInstance(){
             LinesFragment()
         }
         private const val DEBUG_TAG="BusTO-LinesFragment"
         const val FRAGMENT_TAG="LinesFragment"
 
         val patternStopsComparator = PatternWithStopsSorter()
     }
 
 
     private lateinit var viewModel: LinesViewModel
 
     private lateinit var linesSpinner: Spinner
     private lateinit var patternsSpinner: Spinner
 
     private lateinit var currentRoutes: List<GtfsRoute>
     private lateinit var selectedPatterns: List<MatoPatternWithStops>
 
     private lateinit var routeDescriptionTextView: TextView
     private lateinit var stopsRecyclerView: RecyclerView
 
     private var linesAdapter: ArrayAdapter<String>? = null
     private var patternsAdapter: ArrayAdapter<String>? = null
     private var mListener: CommonFragmentListener? = null
 
     private val linesNameSorter = LinesNameSorter()
     private val linesComparator = Comparator<GtfsRoute> { a,b ->
         return@Comparator linesNameSorter.compare(a.shortName, b.shortName)
     }
     private var firstClick = true
     private var recyclerViewState:Parcelable? = null
     private var patternsSpinnerState:Parcelable? = null
 
     private val adapterListener = object : StopAdapterListener {
         override fun onTappedStop(stop: Stop?) {
             //var r = ""
             //stop?.let { r= it.stopDisplayName.toString() }
             if(viewModel.shouldShowMessage) {
                 Toast.makeText(context, R.string.long_press_stop_4_options, Toast.LENGTH_SHORT).show()
                 viewModel.shouldShowMessage=false
             }
             stop?.let {
                 mListener?.requestArrivalsForStopID(it.ID)
             }
             if(stop == null){
                 Log.e(DEBUG_TAG,"Passed wrong stop")
             }
             if(mListener == null){
                 Log.e(DEBUG_TAG, "Listener is null")
             }
         }
 
         override fun onLongPressOnStop(stop: Stop?): Boolean {
             Log.d(DEBUG_TAG, "LongPressOnStop")
             return true
         }
     }
 
     override fun onSaveInstanceState(outState: Bundle) {
         super.onSaveInstanceState(outState)
 
         Log.d(DEBUG_TAG, "saveInstanceState bundle: $outState")
     }
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                               savedInstanceState: Bundle?): View? {
         val rootView =  inflater.inflate(R.layout.fragment_lines, container, false)
 
         linesSpinner = rootView.findViewById(R.id.linesSpinner)
         patternsSpinner = rootView.findViewById(R.id.patternsSpinner)
 
 
         routeDescriptionTextView = rootView.findViewById(R.id.routeDescriptionTextView)
         stopsRecyclerView = rootView.findViewById(R.id.patternStopsRecyclerView)
 
         val llManager = LinearLayoutManager(context)
         llManager.orientation = LinearLayoutManager.VERTICAL
 
         stopsRecyclerView.layoutManager = llManager
         //allow the context menu to be opened
         registerForContextMenu(stopsRecyclerView)
         Log.d(DEBUG_TAG, "Called onCreateView for LinesFragment")
         Log.d(DEBUG_TAG, "OnCreateView, selected line spinner pos: ${linesSpinner.selectedItemPosition}")
         Log.d(DEBUG_TAG, "OnCreateView, selected patterns spinner pos: ${patternsSpinner.selectedItemPosition}")
 
         //set requests
         viewModel.routesGTTLiveData.observe(viewLifecycleOwner) {
             setRoutes(it)
         }
 
         viewModel.patternsWithStopsByRouteLiveData.observe(viewLifecycleOwner){
                 patterns ->
             run {
                 selectedPatterns = patterns.sortedBy { p-> p.pattern.code }
                 //patterns. //sortedBy {-1*it.stopsIndices.size}// "${p.pattern.directionId} - ${p.pattern.headsign}" }
                 patternsAdapter?.let {
                     it.clear()
                     it.addAll(selectedPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
                     it.notifyDataSetChanged()
                 }
                 viewModel.selectedPatternLiveData.value?.let {
                    setSelectedPattern(it)
                 }
 
                 val  pos = patternsSpinner.selectedItemPosition
                 //might be possible that the selectedItem is different (larger than list size)
                 if(pos!= INVALID_POSITION && pos >= 0 && (pos < selectedPatterns.size)){
                     val p = selectedPatterns[pos]
                     Log.d(DEBUG_TAG, "Setting patterns with pos $pos and p gtfsID ${p.pattern.code}")
                     setPatternAndReqStops(selectedPatterns[pos])
                 }
 
             }
         }
 
         viewModel.stopsForPatternLiveData.observe(viewLifecycleOwner){stops->
             Log.d("BusTO-LinesFragment", "Setting stops from DB")
             setCurrentStops(stops)
         }
 
         if(context!=null) {
             patternsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, ArrayList<String>())
             patternsSpinner.adapter = patternsAdapter
             linesAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, ArrayList<String>())
             linesSpinner.adapter = linesAdapter
 
             if (linesSpinner.onItemSelectedListener != null){
                 Log.d(DEBUG_TAG, "linesSpinner listener != null")
             }
             //listener
             linesSpinner.onItemSelectedListener = object: OnItemSelectedListener{
                 override fun onItemSelected(p0: AdapterView<*>?, p1: View?, pos: Int, p3: Long) {
                     val selRoute = currentRoutes.get(pos)
 
                     routeDescriptionTextView.text = selRoute.longName
                     val oldRoute = viewModel.getRouteIDQueried()
                     val resetSpinner = (oldRoute != null) && (oldRoute.trim() != selRoute.gtfsId.trim())
                     Log.d(DEBUG_TAG, "Selected route: ${selRoute.gtfsId}, reset spinner: $resetSpinner, oldRoute: $oldRoute")
                     //launch query for this gtfsID
                     viewModel.setRouteIDQuery(selRoute.gtfsId)
                     //reset spinner position
                     if(resetSpinner) patternsSpinner.setSelection(0)
 
                 }
 
                 override fun onNothingSelected(p0: AdapterView<*>?) {
 
                 }
             }
 
             patternsSpinner.onItemSelectedListener = object : OnItemSelectedListener{
                 override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
                     val patternWithStops = selectedPatterns.get(position)
                     //
                     setPatternAndReqStops(patternWithStops)
                     //viewModel.currentPositionInPatterns.value = position
 
                 }
 
                 override fun onNothingSelected(p0: AdapterView<*>?) {
                 }
             }
         }
 
         return rootView
     }
 
     override fun onAttach(context: Context) {
         super.onAttach(context)
         if(context is CommonFragmentListener)
             mListener = context
         else throw RuntimeException(context.toString()
                 + " must implement CommonFragmentListener")
     }
 
     override fun onResume() {
         super.onResume()
         mListener?.readyGUIfor(FragmentKind.LINES)
 
         Log.d(DEBUG_TAG, "Resuming lines fragment")
         //Log.d(DEBUG_TAG, "OnResume, selected line spinner pos: ${linesSpinner.selectedItemPosition}")
         //Log.d(DEBUG_TAG, "OnResume, selected patterns spinner pos: ${patternsSpinner.selectedItemPosition}")
     }
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         viewModel = ViewModelProvider(this).get(LinesViewModel::class.java)
         Log.d(DEBUG_TAG, "Fragment onCreate")
 
     }
 
     override fun getBaseViewForSnackBar(): View? {
         return null
     }
 
     private fun setSelectedPattern(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 selectedPatterns.indices){
             if(selectedPatterns[k].pattern.code.trim() == code){
                 pos = k
                 break
             }
         }
         Log.d(DEBUG_TAG, "Found pattern $code in position: $pos")
         if(pos>=0){
             patternsSpinner.setSelection(pos)
         }
     }
 
     private fun setRoutes(routes: List<GtfsRoute>){
         Log.d(DEBUG_TAG, "Resetting routes")
         currentRoutes = routes.sortedWith<GtfsRoute>(linesComparator)
 
         if (linesAdapter!=null){
 
             var selGtfsRoute = viewModel.getRouteIDQueried()
             var selRouteIdx = 0
             if(selGtfsRoute == null){
                 selGtfsRoute =""
             }
             Log.d(DEBUG_TAG, "Setting routes, selected route gtfsID: $selGtfsRoute")
             val adapter = linesAdapter!!
 
             if (adapter.isEmpty) {
                 Log.d(DEBUG_TAG, "Lines adapter is empty")
             }
             else{
                 adapter.clear()
 
             }
             adapter.addAll(currentRoutes.map { r -> r.shortName })
             adapter.notifyDataSetChanged()
             for(j in currentRoutes.indices){
                 val route = currentRoutes[j]
                 if (route.gtfsId == selGtfsRoute) {
                     selRouteIdx = j
                     Log.d(DEBUG_TAG, "Route $selGtfsRoute has index $j")
                 }
             }
             linesSpinner.setSelection(selRouteIdx)
             //
         }
         /*
         linesAdapter?.clear()
         linesAdapter?.addAll(currentRoutes.map { r -> r.shortName })
         linesAdapter?.notifyDataSetChanged()
          */
     }
 
     private fun setCurrentStops(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}")
 
         var setNewAdapter = true
         if(stopsRecyclerView.adapter is StopRecyclerAdapter){
             val adapter = stopsRecyclerView.adapter as StopRecyclerAdapter
             if(adapter.stops.size == stopsSorted.size && (adapter.stops.get(0).gtfsID == stopsSorted.get(0).gtfsID)
                 && (adapter.stops.get(numStops-1).gtfsID == stopsSorted.get(numStops-1).gtfsID)
             ){
                 Log.d(DEBUG_TAG, "Found same stops on recyclerview")
                 setNewAdapter = false
             }
             /*else {
                 Log.d(DEBUG_TAG, "Found adapter on recyclerview, but not the same stops")
                 adapter.stops = stopsSorted
                 adapter.notifyDataSetChanged()
             }*/
 
         }
         if(setNewAdapter){
             stopsRecyclerView.adapter = StopRecyclerAdapter(
                 stopsSorted, adapterListener, StopRecyclerAdapter.Use.LINES,
                 NameCapitalize.FIRST
             )
         }
 
 
 
     }
 
     private fun setPatternAndReqStops(patternWithStops: MatoPatternWithStops){
         Log.d(DEBUG_TAG, "Requesting stops for pattern ${patternWithStops.pattern.code}")
         //currentPatternStops = patternWithStops.stopsIndices.sortedBy { i-> i.order }
         viewModel.currentPatternStops.value =  patternWithStops.stopsIndices.sortedBy { i-> i.order }
         viewModel.selectedPatternLiveData.value = patternWithStops
         viewModel.requestStopsForPatternWithStops(patternWithStops)
     }
 
     override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
         super.onCreateContextMenu(menu, v, menuInfo)
         Log.d("BusTO-LinesFragment", "Creating context menu ")
 
 
         if (v.id == R.id.patternStopsRecyclerView) {
             // if we aren't attached to activity, return null
             if (activity == null) return
             val inflater = requireActivity().menuInflater
             inflater.inflate(R.menu.menu_line_item, menu)
 
         }
     }
 
 
     override fun onContextItemSelected(item: MenuItem): Boolean {
 
         if (stopsRecyclerView.getAdapter() !is StopRecyclerAdapter) return false
         val adapter =stopsRecyclerView.adapter as StopRecyclerAdapter
         val stop = adapter.stops.get(adapter.getPosition())
 
         val acId = item.itemId
         if(acId == R.id.action_view_on_map){
             // view on the map
             if ((stop.latitude == null) or (stop.longitude == null) or (mListener == null) ) {
                 Toast.makeText(context, R.string.cannot_show_on_map_no_position, Toast.LENGTH_SHORT).show()
                 return true
             }
             mListener!!.showMapCenteredOnStop(stop)
             return true
         } else if (acId == R.id.action_show_arrivals){
             mListener?.requestArrivalsForStopID(stop.ID)
             return true
         }
         return false
     }
 
     override fun onStop() {
         super.onStop()
         Log.d(DEBUG_TAG, "Fragment stopped")
 
         recyclerViewState = stopsRecyclerView.layoutManager?.onSaveInstanceState()
         patternsSpinnerState = patternsSpinner.onSaveInstanceState()
     }
 
 
     override fun onStart() {
         super.onStart()
 
         Log.d(DEBUG_TAG, "OnStart, selected line spinner pos: ${linesSpinner.selectedItemPosition}")
         Log.d(DEBUG_TAG, "OnStart, selected patterns spinner pos: ${patternsSpinner.selectedItemPosition}")
 
         if (recyclerViewState!=null){
             stopsRecyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
         }
         if(patternsSpinnerState!=null){
             patternsSpinner.onRestoreInstanceState(patternsSpinnerState)
         }
     }
     /*
     override fun onDestroyView() {
         super.onDestroyView()
         Log.d(DEBUG_TAG, "Fragment view destroyed")
 
     }
 
     override fun onDestroy() {
         super.onDestroy()
         Log.d(DEBUG_TAG, "Fragment destroyed")
     }
     */
 
     override fun onViewStateRestored(savedInstanceState: Bundle?) {
         super.onViewStateRestored(savedInstanceState)
         Log.d(DEBUG_TAG, "OnViewStateRes, bundled saveinstancestate: $savedInstanceState")
         Log.d(DEBUG_TAG, "OnViewStateRes, selected line spinner pos: ${linesSpinner.selectedItemPosition}")
         Log.d(DEBUG_TAG, "OnViewStateRes, selected patterns spinner pos: ${patternsSpinner.selectedItemPosition}")
     }
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
index 0c69a4d..f1cc0cc 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt
@@ -1,277 +1,278 @@
 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
             }
         }
+        fragmentListener.readyGUIfor(FragmentKind.LINES)
+
     }
 
 
     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/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
index fc1e0d1..78b56bf 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,891 +1,891 @@
 
 package it.reyboz.bustorino.fragments;
 
 import android.Manifest;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.location.Criteria;
 import android.location.Location;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 
 import androidx.activity.result.ActivityResultCallback;
 import androidx.activity.result.ActivityResultLauncher;
 import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.widget.AppCompatImageButton;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 import androidx.core.app.ActivityCompat;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentTransaction;
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 
 import android.os.Handler;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import com.google.android.material.floatingactionbutton.FloatingActionButton;
 
 import java.util.List;
 import java.util.Map;
 
 import it.reyboz.bustorino.R;
 import it.reyboz.bustorino.backend.*;
 import it.reyboz.bustorino.middleware.AppLocationManager;
 import it.reyboz.bustorino.middleware.AsyncArrivalsSearcher;
 import it.reyboz.bustorino.middleware.AsyncStopsSearcher;
 import it.reyboz.bustorino.middleware.BarcodeScanContract;
 import it.reyboz.bustorino.middleware.BarcodeScanOptions;
 import it.reyboz.bustorino.middleware.BarcodeScanUtils;
 import it.reyboz.bustorino.util.LocationCriteria;
 import it.reyboz.bustorino.util.Permissions;
 
 import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
 import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS;
 
 
 /**
  * A simple {@link Fragment} subclass.
  * Use the {@link MainScreenFragment#newInstance} factory method to
  * create an instance of this fragment.
  */
 public class MainScreenFragment extends ScreenBaseFragment implements  FragmentListenerMain{
 
 
     private static final String OPTION_SHOW_LEGEND = "show_legend";
     private static final String SAVED_FRAGMENT="saved_fragment";
 
     private static final String DEBUG_TAG = "BusTO - MainFragment";
 
     public static final String PENDING_STOP_SEARCH="PendingStopSearch";
 
     public final static String FRAGMENT_TAG = "MainScreenFragment";
 
     /// UI ELEMENTS //
     private ImageButton addToFavorites;
     private FragmentHelper fragmentHelper;
     private SwipeRefreshLayout swipeRefreshLayout;
     private EditText busStopSearchByIDEditText;
     private EditText busStopSearchByNameEditText;
     private ProgressBar progressBar;
     private TextView howDoesItWorkTextView;
     private Button hideHintButton;
     private MenuItem actionHelpMenuItem;
     private FloatingActionButton floatingActionButton;
     private FrameLayout resultFrameLayout;
 
     private boolean setupOnStart = true;
     private boolean suppressArrivalsReload = false;
     private boolean instanceStateSaved = false;
     //private Snackbar snackbar;
     /*
      * Search mode
      */
     private static final int SEARCH_BY_NAME = 0;
     private static final int SEARCH_BY_ID = 1;
     private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12
     private int searchMode;
     //private ImageButton addToFavorites;
     //// HIDDEN BUT IMPORTANT ELEMENTS ////
     FragmentManager fragMan;
     Handler mainHandler;
     private final Runnable refreshStop = new Runnable() {
         public void run() {
             if(getContext() == null) return;
             List<ArrivalsFetcher> fetcherList = utils.getDefaultArrivalsFetchers(getContext());
             ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[fetcherList.size()];
             arrivalsFetchers = fetcherList.toArray(arrivalsFetchers);
 
             if (fragMan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
                 ArrivalsFragment fragment = (ArrivalsFragment) fragMan.findFragmentById(R.id.resultFrame);
                 if (fragment == null){
                     //we create a new fragment, which is WRONG
                     Log.e("BusTO-RefreshStop", "Asking for refresh when there is no fragment");
                     // AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute();
                 } else{
                     String stopName = fragment.getStopID();
 
                     new AsyncArrivalsSearcher(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName);
                 }
             } else //we create a new fragment, which is WRONG
                 new AsyncArrivalsSearcher(fragmentHelper, arrivalsFetchers, getContext()).execute();
         }
     };
     //
     private final ActivityResultLauncher<BarcodeScanOptions> barcodeLauncher = registerForActivityResult(new BarcodeScanContract(),
             result -> {
                 if(result!=null && result.getContents()!=null) {
                     //Toast.makeText(MyActivity.this, "Cancelled", Toast.LENGTH_LONG).show();
                     Uri uri;
                     try {
                         uri = Uri.parse(result.getContents()); // this apparently prevents NullPointerException. Somehow.
                     } catch (NullPointerException e) {
                         if (getContext()!=null)
                             Toast.makeText(getContext().getApplicationContext(),
                                 R.string.no_qrcode, Toast.LENGTH_SHORT).show();
                         return;
                     }
                     String busStopID = getBusStopIDFromUri(uri);
                     busStopSearchByIDEditText.setText(busStopID);
                     requestArrivalsForStopID(busStopID);
 
                 } else {
                     //Toast.makeText(MyActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
                     if (getContext()!=null)
                         Toast.makeText(getContext().getApplicationContext(),
                             R.string.no_qrcode, Toast.LENGTH_SHORT).show();
 
 
 
                 }
             });
 
     /// LOCATION STUFF ///
     boolean pendingNearbyStopsRequest = false;
     boolean locationPermissionGranted, locationPermissionAsked = false;
     AppLocationManager locationManager;
     private final ActivityResultLauncher<String[]> requestPermissionLauncher =
             registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
                 @Override
                 public void onActivityResult(Map<String, Boolean> result) {
                     if(result==null || result.get(Manifest.permission.ACCESS_COARSE_LOCATION) == null
                             ||result.get(Manifest.permission.ACCESS_FINE_LOCATION) == null) return;
 
                     if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) == null ||
                             result.get(Manifest.permission.ACCESS_FINE_LOCATION) == null)
                         return;
                     boolean resCoarse = result.get(Manifest.permission.ACCESS_COARSE_LOCATION);
                     boolean resFine = result.get(Manifest.permission.ACCESS_FINE_LOCATION);
                     Log.d(DEBUG_TAG, "Permissions for location are: "+result);
                     if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) && result.get(Manifest.permission.ACCESS_FINE_LOCATION)){
                         locationPermissionGranted = true;
                         Log.w(DEBUG_TAG, "Starting position");
                         if (mListener!= null && getContext()!=null){
                             if (locationManager==null)
                                 locationManager = AppLocationManager.getInstance(getContext());
                             locationManager.addLocationRequestFor(requester);
                         }
                         // show nearby fragment
                         //showNearbyStopsFragment();
                         Log.d(DEBUG_TAG, "We have location permission");
                         if(pendingNearbyStopsRequest){
                             showNearbyFragmentIfNeeded(cr);
                             pendingNearbyStopsRequest = false;
                         }
                     }
                     if(pendingNearbyStopsRequest) pendingNearbyStopsRequest=false;
                 }
             });
 
 
     private final LocationCriteria cr = new LocationCriteria(2000, 10000);
     //Location
     private AppLocationManager.LocationRequester requester = new AppLocationManager.LocationRequester() {
         @Override
         public void onLocationChanged(Location loc) {
 
         }
 
         @Override
         public void onLocationStatusChanged(int status) {
 
             if(status == AppLocationManager.LOCATION_GPS_AVAILABLE && !isNearbyFragmentShown() && checkLocationPermission()){
                 //request Stops
                 //pendingNearbyStopsRequest = false;
                 if (getContext()!= null && !isNearbyFragmentShown())
                     //mainHandler.post(new NearbyStopsRequester(getContext(), cr));
                     showNearbyFragmentIfNeeded(cr);
             }
         }
 
         @Override
         public long getLastUpdateTimeMillis() {
             return 50;
         }
 
         @Override
         public LocationCriteria getLocationCriteria() {
             return cr;
         }
 
         @Override
         public void onLocationProviderAvailable() {
             //Log.w(DEBUG_TAG, "pendingNearbyStopRequest: "+pendingNearbyStopsRequest);
             if(!isNearbyFragmentShown() && getContext()!=null){
                 // we should have the location permission
                 if(!checkLocationPermission())
                     Log.e(DEBUG_TAG, "Asking to show nearbystopfragment when " +
                             "we have no location permission");
                 pendingNearbyStopsRequest = true;
                 //mainHandler.post(new NearbyStopsRequester(getContext(), cr));
                 showNearbyFragmentIfNeeded(cr);
             }
         }
 
         @Override
         public void onLocationDisabled() {
 
         }
     };
 
 
 
     //// ACTIVITY ATTACHED (LISTENER ///
     private CommonFragmentListener mListener;
 
     private String pendingStopID = null;
     private CoordinatorLayout coordLayout;
 
     public MainScreenFragment() {
         // Required empty public constructor
     }
 
 
     public static MainScreenFragment newInstance() {
         return new MainScreenFragment();
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         if (getArguments() != null) {
             //do nothing
             Log.d(DEBUG_TAG, "ARGS ARE NOT NULL: "+getArguments());
             if (getArguments().getString(PENDING_STOP_SEARCH)!=null)
                 pendingStopID = getArguments().getString(PENDING_STOP_SEARCH);
         }
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
                              Bundle savedInstanceState) {
         // Inflate the layout for this fragment
         View root = inflater.inflate(R.layout.fragment_main_screen, container, false);
         addToFavorites = root.findViewById(R.id.addToFavorites);
         busStopSearchByIDEditText = root.findViewById(R.id.busStopSearchByIDEditText);
         busStopSearchByNameEditText = root.findViewById(R.id.busStopSearchByNameEditText);
         progressBar = root.findViewById(R.id.progressBar);
         howDoesItWorkTextView = root.findViewById(R.id.howDoesItWorkTextView);
         hideHintButton = root.findViewById(R.id.hideHintButton);
         swipeRefreshLayout = root.findViewById(R.id.listRefreshLayout);
         floatingActionButton = root.findViewById(R.id.floatingActionButton);
         resultFrameLayout = root.findViewById(R.id.resultFrame);
         busStopSearchByIDEditText.setSelectAllOnFocus(true);
         busStopSearchByIDEditText
                 .setOnEditorActionListener((v, actionId, event) -> {
                     // IME_ACTION_SEARCH alphabetical option
                     if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                         onSearchClick(v);
                         return true;
                     }
                     return false;
                 });
         busStopSearchByNameEditText
                 .setOnEditorActionListener((v, actionId, event) -> {
                     // IME_ACTION_SEARCH alphabetical option
                     if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                         onSearchClick(v);
                         return true;
                     }
                     return false;
                 });
 
         swipeRefreshLayout
                 .setOnRefreshListener(() -> mainHandler.post(refreshStop));
         swipeRefreshLayout.setColorSchemeResources(R.color.blue_500, R.color.orange_500);
 
         coordLayout = root.findViewById(R.id.coord_layout);
 
         floatingActionButton.setOnClickListener((this::onToggleKeyboardLayout));
         hideHintButton.setOnClickListener(this::onHideHint);
 
         AppCompatImageButton qrButton = root.findViewById(R.id.QRButton);
         qrButton.setOnClickListener(this::onQRButtonClick);
 
         AppCompatImageButton searchButton = root.findViewById(R.id.searchButton);
         searchButton.setOnClickListener(this::onSearchClick);
 
         // Fragment stuff
         fragMan = getChildFragmentManager();
         fragMan.addOnBackStackChangedListener(() -> Log.d("BusTO Main Fragment", "BACK STACK CHANGED"));
 
         fragmentHelper = new FragmentHelper(this, getChildFragmentManager(), getContext(), R.id.resultFrame);
         setSearchModeBusStopID();
 
 
 
         cr.setAccuracy(Criteria.ACCURACY_FINE);
         cr.setAltitudeRequired(false);
         cr.setBearingRequired(false);
         cr.setCostAllowed(true);
         cr.setPowerRequirement(Criteria.NO_REQUIREMENT);
 
         locationManager = AppLocationManager.getInstance(getContext());
 
         Log.d(DEBUG_TAG, "OnCreateView, savedInstanceState null: "+(savedInstanceState==null));
 
 
 
         return root;
     }
 
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         Log.d(DEBUG_TAG, "onViewCreated, SwipeRefreshLayout visible: "+(swipeRefreshLayout.getVisibility()==View.VISIBLE));
         Log.d(DEBUG_TAG, "Saved instance state is: "+savedInstanceState);
         //Restore instance state
         /*if (savedInstanceState!=null){
             Fragment fragment = getChildFragmentManager().getFragment(savedInstanceState, SAVED_FRAGMENT);
             if (fragment!=null){
                 getChildFragmentManager().beginTransaction().add(R.id.resultFrame, fragment).commit();
                 setupOnStart = false;
             }
         }
 
          */
         if (getChildFragmentManager().findFragmentById(R.id.resultFrame)!= null){
             swipeRefreshLayout.setVisibility(View.VISIBLE);
         }
         instanceStateSaved = false;
 
     }
 
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
         super.onSaveInstanceState(outState);
         Log.d(DEBUG_TAG, "Saving instance state");
         Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame);
         if (fragment!=null)
             getChildFragmentManager().putFragment(outState, SAVED_FRAGMENT, fragment);
         if (fragmentHelper!=null) fragmentHelper.setBlockAllActivities(true);
 
         instanceStateSaved = true;
     }
 
     public void setSuppressArrivalsReload(boolean value){
        suppressArrivalsReload = value;
         // we have to suppress the reloading of the (possible) ArrivalsFragment
         /*if(value) {
             Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame);
             if (fragment instanceof ArrivalsFragment) {
                 ArrivalsFragment frag = (ArrivalsFragment) fragment;
                 frag.setReloadOnResume(false);
             }
         }
 
          */
     }
 
 
     /**
      * Cancel the reload of the arrival times
      * because we are going to pop the fragment
      */
     public void cancelReloadArrivalsIfNeeded(){
         if(getContext()==null) return; //we are not attached
 
         //Fragment fr = getChildFragmentManager().findFragmentById(R.id.resultFrame);
         fragmentHelper.stopLastRequestIfNeeded(true);
         toggleSpinner(false);
     }
 
 
     @Override
     public void onAttach(@NonNull Context context) {
         super.onAttach(context);
 
         Log.d(DEBUG_TAG, "OnAttach called, setupOnAttach: "+ setupOnStart);
         mainHandler = new Handler();
         if (context instanceof CommonFragmentListener) {
             mListener = (CommonFragmentListener) context;
         } else {
             throw new RuntimeException(context
                     + " must implement CommonFragmentListener");
         }
 
     }
     @Override
     public void onDetach() {
         super.onDetach();
         mListener = null;
     //    setupOnAttached = true;
     }
 
     @Override
     public void onStart() {
         super.onStart();
         Log.d(DEBUG_TAG, "onStart called, setupOnStart: "+setupOnStart);
         if (setupOnStart) {
             if (pendingStopID==null){
                 //We want the nearby bus stops!
                 //mainHandler.post(new NearbyStopsRequester(getContext(), cr));
                 Log.d(DEBUG_TAG, "Showing nearby stops");
                 if(!checkLocationPermission()){
                     requestLocationPermission();
                     pendingNearbyStopsRequest = true;
                 }
                 else {
                     showNearbyFragmentIfNeeded(cr);
                 }
             }
             else{
                 ///TODO: if there is a stop displayed, we need to hold the update
             }
 
             setupOnStart = false;
         }
     }
 
     @Override
     public void onResume() {
 
         final Context con = getContext();
         Log.w(DEBUG_TAG, "OnResume called, setupOnStart: "+ setupOnStart);
         if (con != null) {
             if(locationManager==null)
                 locationManager = AppLocationManager.getInstance(con);
 
             if(Permissions.locationPermissionGranted(con)){
                 Log.d(DEBUG_TAG, "Location permission OK");
                 if(!locationManager.isRequesterRegistered(requester))
                     locationManager.addLocationRequestFor(requester);
             } //don't request permission
         }
         else {
             Log.w(DEBUG_TAG, "Context is null at onResume");
         }
         super.onResume();
         // if we have a pending stopID request, do it
         Log.d(DEBUG_TAG, "Pending stop ID for arrivals: "+pendingStopID);
         //this is the second time we are attaching this fragment
         Log.d(DEBUG_TAG, "Waiting for new stop request: "+ suppressArrivalsReload);
         if (suppressArrivalsReload){
             // we have to suppress the reloading of the (possible) ArrivalsFragment
             Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame);
             if (fragment instanceof ArrivalsFragment){
                 ArrivalsFragment frag = (ArrivalsFragment) fragment;
                 frag.setReloadOnResume(false);
             }
             //deactivate
             suppressArrivalsReload = false;
         }
 
         if(pendingStopID!=null){
 
             Log.d(DEBUG_TAG, "Pending request for arrivals at stop ID: "+pendingStopID);
             requestArrivalsForStopID(pendingStopID);
             pendingStopID = null;
         }
         mListener.readyGUIfor(FragmentKind.MAIN_SCREEN_FRAGMENT);
 
         fragmentHelper.setBlockAllActivities(false);
 
     }
 
     @Override
     public void onPause() {
         //mainHandler = null;
         locationManager.removeLocationRequestFor(requester);
         super.onPause();
         fragmentHelper.setBlockAllActivities(true);
         fragmentHelper.stopLastRequestIfNeeded(true);
     }
 
 
 
 
     /*
     GUI METHODS
      */
     /**
      * QR scan button clicked
      *
      * @param v View QRButton clicked
      */
     public void onQRButtonClick(View v) {
 
         BarcodeScanOptions scanOptions = new BarcodeScanOptions();
         Intent intent = scanOptions.createScanIntent();
         if(!BarcodeScanUtils.checkTargetPackageExists(getContext(), intent)){
             BarcodeScanUtils.showDownloadDialog(null, this);
         }else {
             barcodeLauncher.launch(scanOptions);
         }
     }
     public void onHideHint(View v) {
 
         hideHints();
         setOption(OPTION_SHOW_LEGEND, false);
     }
     /**
      * OK this is pure shit
      *
      * @param v View clicked
      */
     public void onSearchClick(View v) {
         final StopsFinderByName[] stopsFinderByNames = new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()};
         if (searchMode == SEARCH_BY_ID) {
             String busStopID = busStopSearchByIDEditText.getText().toString();
             fragmentHelper.stopLastRequestIfNeeded(true);
             requestArrivalsForStopID(busStopID);
         } else { // searchMode == SEARCH_BY_NAME
             String query = busStopSearchByNameEditText.getText().toString();
             query = query.trim();
             if(getContext()!=null) {
                 if (query.length() < 1) {
                     Toast.makeText(getContext(), R.string.insert_bus_stop_name_error, Toast.LENGTH_SHORT).show();
                 } else if(query.length()< 2){
                     Toast.makeText(getContext(), R.string.query_too_short, Toast.LENGTH_SHORT).show();
                 }
                 else {
                     fragmentHelper.stopLastRequestIfNeeded(true);
                     new AsyncStopsSearcher(fragmentHelper, stopsFinderByNames).execute(query);
                 }
             }
         }
     }
 
     public void onToggleKeyboardLayout(View v) {
 
         if (searchMode == SEARCH_BY_NAME) {
             setSearchModeBusStopID();
             if (busStopSearchByIDEditText.requestFocus()) {
                 showKeyboard();
             }
         } else { // searchMode == SEARCH_BY_ID
             setSearchModeBusStopName();
             if (busStopSearchByNameEditText.requestFocus()) {
                 showKeyboard();
             }
         }
 
     }
     @Override
     public void enableRefreshLayout(boolean yes) {
         swipeRefreshLayout.setEnabled(yes);
     }
 
     ////////////////////////////////////// GUI HELPERS /////////////////////////////////////////////
     public void showKeyboard() {
         if(getActivity() == null) return;
         InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
         View view = searchMode == SEARCH_BY_ID ? busStopSearchByIDEditText : busStopSearchByNameEditText;
         imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
     }
 
     private void setSearchModeBusStopID() {
         searchMode = SEARCH_BY_ID;
         busStopSearchByNameEditText.setVisibility(View.GONE);
         busStopSearchByNameEditText.setText("");
         busStopSearchByIDEditText.setVisibility(View.VISIBLE);
         floatingActionButton.setImageResource(R.drawable.alphabetical);
     }
 
     private void setSearchModeBusStopName() {
         searchMode = SEARCH_BY_NAME;
         busStopSearchByIDEditText.setVisibility(View.GONE);
         busStopSearchByIDEditText.setText("");
         busStopSearchByNameEditText.setVisibility(View.VISIBLE);
         floatingActionButton.setImageResource(R.drawable.numeric);
     }
     protected boolean isNearbyFragmentShown(){
         Fragment fragment = getChildFragmentManager().findFragmentByTag(NearbyStopsFragment.FRAGMENT_TAG);
         return (fragment!= null && fragment.isVisible());
     }
 
     /**
      * Having that cursor at the left of the edit text makes me cancer.
      *
      * @param busStopID bus stop ID
      */
     private void setBusStopSearchByIDEditText(String busStopID) {
         busStopSearchByIDEditText.setText(busStopID);
         busStopSearchByIDEditText.setSelection(busStopID.length());
     }
 
     private void showHints() {
         howDoesItWorkTextView.setVisibility(View.VISIBLE);
         hideHintButton.setVisibility(View.VISIBLE);
         //actionHelpMenuItem.setVisible(false);
     }
 
     private void hideHints() {
         howDoesItWorkTextView.setVisibility(View.GONE);
         hideHintButton.setVisibility(View.GONE);
         //actionHelpMenuItem.setVisible(true);
     }
 
     @Nullable
     @org.jetbrains.annotations.Nullable
     @Override
     public View getBaseViewForSnackBar() {
         return coordLayout;
     }
 
     @Override
     public void toggleSpinner(boolean enable) {
         if (enable) {
             //already set by the RefreshListener when needed
             //swipeRefreshLayout.setRefreshing(true);
             progressBar.setVisibility(View.VISIBLE);
         } else {
             swipeRefreshLayout.setRefreshing(false);
             progressBar.setVisibility(View.GONE);
         }
     }
 
 
     private void prepareGUIForBusLines() {
         swipeRefreshLayout.setEnabled(true);
         swipeRefreshLayout.setVisibility(View.VISIBLE);
         //actionHelpMenuItem.setVisible(true);
     }
 
     private void prepareGUIForBusStops() {
         swipeRefreshLayout.setEnabled(false);
         swipeRefreshLayout.setVisibility(View.VISIBLE);
         //actionHelpMenuItem.setVisible(false);
     }
 
     private void actuallyShowNearbyStopsFragment(){
         swipeRefreshLayout.setVisibility(View.VISIBLE);
         final Fragment existingFrag = fragMan.findFragmentById(R.id.resultFrame);
         // fragment;
         if (!(existingFrag instanceof NearbyStopsFragment)){
             Log.d(DEBUG_TAG, "actually showing Nearby Stops Fragment");
             //there is no fragment showing
-            final NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.TYPE_STOPS);
+            final NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.FragType.STOPS);
 
             FragmentTransaction ft = fragMan.beginTransaction();
 
             ft.replace(R.id.resultFrame, fragment, NearbyStopsFragment.FRAGMENT_TAG);
             if (getActivity()!=null && !getActivity().isFinishing() &&!instanceStateSaved)
             ft.commit();
             else Log.e(DEBUG_TAG, "Not showing nearby fragment because we saved instanceState");
         }
     }
 
 
     @Override
     public void showFloatingActionButton(boolean yes) {
         mListener.showFloatingActionButton(yes);
     }
 
     /**
      * This provides a temporary fix to make the transition
      * to a single asynctask go smoother
      *
      * @param fragmentType the type of fragment created
      */
     @Override
     public void readyGUIfor(FragmentKind fragmentType) {
 
 
         //if we are getting results, already, stop waiting for nearbyStops
         if (fragmentType == FragmentKind.ARRIVALS || fragmentType == FragmentKind.STOPS) {
             hideKeyboard();
 
             if (pendingNearbyStopsRequest) {
                 locationManager.removeLocationRequestFor(requester);
                 pendingNearbyStopsRequest = false;
             }
         }
 
         if (fragmentType == null) Log.e("ActivityMain", "Problem with fragmentType");
         else
             switch (fragmentType) {
                 case ARRIVALS:
                     prepareGUIForBusLines();
                     if (getOption(OPTION_SHOW_LEGEND, true)) {
                         showHints();
                     }
                     break;
                 case STOPS:
                     prepareGUIForBusStops();
                     break;
                 default:
                     Log.d(DEBUG_TAG, "Fragment type is unknown");
                     return;
             }
         // Shows hints
 
 
     }
 
     @Override
     public void showLineOnMap(String routeGtfsId) {
         //pass to activity
         mListener.showLineOnMap(routeGtfsId);
     }
 
     @Override
     public void showMapCenteredOnStop(Stop stop) {
         if(mListener!=null) mListener.showMapCenteredOnStop(stop);
     }
 
     /**
      * Main method for stops requests
      * @param ID the Stop ID
      */
     @Override
     public void requestArrivalsForStopID(String ID) {
         if (!isResumed()){
             //defer request
             pendingStopID = ID;
             Log.d(DEBUG_TAG, "Deferring update for stop "+ID+ " saved: "+pendingStopID);
             return;
         }
         final boolean delayedRequest = !(pendingStopID==null);
         final FragmentManager framan = getChildFragmentManager();
         if (getContext()==null){
             Log.e(DEBUG_TAG, "Asked for arrivals with null context");
             return;
         }
         ArrivalsFetcher[] fetchers = utils.getDefaultArrivalsFetchers(getContext()).toArray(new ArrivalsFetcher[0]);
         if (ID == null || ID.length() <= 0) {
             // we're still in UI thread, no need to mess with Progress
             showToastMessage(R.string.insert_bus_stop_number_error, true);
             toggleSpinner(false);
         } else  if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
             ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame);
             if (fragment != null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){
                 // Run with previous fetchers
                 //fragment.getCurrentFetchers().toArray()
                 new AsyncArrivalsSearcher(fragmentHelper,fragment.getCurrentFetchersAsArray(), getContext()).execute(ID);
             } else{
                 new AsyncArrivalsSearcher(fragmentHelper, fetchers, getContext()).execute(ID);
             }
         }
         else {
             Log.d(DEBUG_TAG, "This is probably the first arrivals search, preparing GUI");
             prepareGUIForBusLines();
             new AsyncArrivalsSearcher(fragmentHelper,fetchers, getContext()).execute(ID);
             Log.d(DEBUG_TAG, "Started search for arrivals of stop " + ID);
         }
     }
 
     private boolean checkLocationPermission(){
         final Context context = getContext();
         if(context==null) return false;
 
         final boolean isOldVersion = Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
         final boolean noPermission = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
                 ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED;
 
         return isOldVersion || !noPermission;
 
     }
     private void requestLocationPermission(){
         requestPermissionLauncher.launch(LOCATION_PERMISSIONS);
     }
 
     private void showNearbyFragmentIfNeeded(Criteria cr){
         if(isNearbyFragmentShown()) {
             //nothing to do
             Log.w(DEBUG_TAG, "launched nearby fragment request but we already are showing");
             return;
         }
         if(getContext()==null){
             Log.e(DEBUG_TAG, "Wanting to show nearby fragment but context is null");
             return;
         }
 
         AppLocationManager appLocationManager = AppLocationManager.getInstance(getContext());
         final boolean haveProviders = appLocationManager.anyLocationProviderMatchesCriteria(cr);
         if (haveProviders
                 && fragmentHelper.getLastSuccessfullySearchedBusStop() == null
                 && !fragMan.isDestroyed()) {
             //Go ahead with the request
             Log.d("mainActivity", "Recreating stop fragment");
             actuallyShowNearbyStopsFragment();
             pendingNearbyStopsRequest = false;
         } else if(!haveProviders){
             Log.e(DEBUG_TAG, "NO PROVIDERS FOR POSITION");
         }
     }
     /////////// LOCATION METHODS //////////
 
     /*
     private void startStopRequest(String provider) {
         Log.d(DEBUG_TAG, "Provider " + provider + " got enabled");
         if (locmgr != null && mainHandler != null && pendingNearbyStopsRequest && locmgr.getProvider(provider).meetsCriteria(cr)) {
 
         }
     }
 
      */
     /*
 
      * Run location requests separately and asynchronously
 
     class NearbyStopsRequester implements Runnable {
         Context appContext;
         Criteria cr;
 
         public NearbyStopsRequester(Context appContext, Criteria criteria) {
             this.appContext = appContext.getApplicationContext();
             this.cr = criteria;
         }
 
         @Override
         public void run() {
             if(isNearbyFragmentShown()) {
                 //nothing to do
                 Log.w(DEBUG_TAG, "launched nearby fragment request but we already are showing");
                 return;
             }
 
             final boolean isOldVersion = Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
             final boolean noPermission = ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
                     ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED;
 
             //if we don't have the permission, we have to ask for it, if we haven't
             // asked too many times before
             if (noPermission) {
                 if (!isOldVersion) {
                     pendingNearbyStopsRequest = true;
                     //Permissions.assertLocationPermissions(appContext,getActivity());
                     requestPermissionLauncher.launch(LOCATION_PERMISSIONS);
                     Log.w(DEBUG_TAG, "Cannot get position: Asking permission, noPositionFromSys: " + noPermission);
                     return;
                 } else {
                     Toast.makeText(appContext, "Asked for permission position too many times", Toast.LENGTH_LONG).show();
                 }
             } else setOption(LOCATION_PERMISSION_GIVEN, true);
 
             AppLocationManager appLocationManager = AppLocationManager.getInstance(appContext);
             final boolean haveProviders = appLocationManager.anyLocationProviderMatchesCriteria(cr);
             if (haveProviders
                     && fragmentHelper.getLastSuccessfullySearchedBusStop() == null
                     && !fragMan.isDestroyed()) {
                 //Go ahead with the request
                 Log.d("mainActivity", "Recreating stop fragment");
                 showNearbyStopsFragment();
                 pendingNearbyStopsRequest = false;
             } else if(!haveProviders){
                 Log.e(DEBUG_TAG, "NO PROVIDERS FOR POSITION");
             }
 
         }
     }
 
      */
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
index 0c9669a..5b02a66 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MapFragment.java
@@ -1,836 +1,795 @@
 /*
 	BusTO  - Fragments components
     Copyright (C) 2020 Andrea Ugo
     Copyright (C) 2021 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.Manifest;
 import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
 import android.content.Context;
 
 import android.graphics.drawable.Drawable;
 import android.location.Location;
 import android.location.LocationManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageButton;
 import android.widget.Toast;
 
 import androidx.activity.result.ActivityResultLauncher;
 import androidx.activity.result.contract.ActivityResultContracts;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.content.res.ResourcesCompat;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.preference.PreferenceManager;
 
 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 it.reyboz.bustorino.viewmodels.StopsMapViewModel;
 import org.osmdroid.api.IGeoPoint;
 import org.osmdroid.api.IMapController;
 import org.osmdroid.config.Configuration;
 import org.osmdroid.events.DelayedMapListener;
 import org.osmdroid.events.MapListener;
 import org.osmdroid.events.ScrollEvent;
 import org.osmdroid.events.ZoomEvent;
 import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
 import org.osmdroid.util.BoundingBox;
 import org.osmdroid.util.GeoPoint;
 import org.osmdroid.views.MapView;
 import org.osmdroid.views.overlay.FolderOverlay;
 import org.osmdroid.views.overlay.Marker;
 import org.osmdroid.views.overlay.infowindow.InfoWindow;
 import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
 
 import java.lang.ref.WeakReference;
 import java.util.*;
 
 import kotlin.Pair;
 import it.reyboz.bustorino.R;
 import it.reyboz.bustorino.backend.Stop;
 import it.reyboz.bustorino.data.NextGenDB;
 import it.reyboz.bustorino.middleware.GeneralActivity;
 import it.reyboz.bustorino.util.Permissions;
 
 public class MapFragment extends ScreenBaseFragment {
 
     //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";
     private static final String FOLLOWING_LOCAT_KEY ="following";
 
     public static final String BUNDLE_LATIT = "lat";
     public static final String BUNDLE_LONGIT = "lon";
     public static final String BUNDLE_NAME = "name";
     public static final String BUNDLE_ID = "ID";
     public static final String BUNDLE_ROUTES_STOPPING = "routesStopping";
 
     public static final String FRAGMENT_TAG="BusTOMapFragment";
 
 
     private static final double DEFAULT_CENTER_LAT = 45.0708;
     private static final double DEFAULT_CENTER_LON = 7.6858;
     private static final double POSITION_FOUND_ZOOM = 18.3;
     public static final double NO_POSITION_ZOOM = 17.1;
 
     private static final String DEBUG_TAG=FRAGMENT_TAG;
 
     protected FragmentListenerMain listenerMain;
 
     private HashSet<String> shownStops = null;
-    //the asynctask used to get the stops from the database
-    private AsyncStopFetcher stopFetcher = null;
 
 
     private MapView map = null;
     public Context ctx;
     private LocationOverlay mLocationOverlay = null;
     private FolderOverlay stopsFolderOverlay = null;
     private Bundle savedMapState = null;
     protected ImageButton btCenterMap;
     protected ImageButton btFollowMe;
     private boolean hasMapStartFinished = false;
     private boolean followingLocation = false;
 
+    //the ViewModel from which we get the stop to display in the map
+    private StopsMapViewModel stopsViewModel;
+
     //private GTFSPositionsViewModel gtfsPosViewModel; //= new ViewModelProvider(this).get(MapViewModel.class);
     private MQTTPositionsViewModel positionsViewModel;
 
     private final HashMap<String,Marker> busPositionMarkersByTrip = new HashMap<>();
     private FolderOverlay busPositionsOverlay = null;
 
     private final HashMap<String, ObjectAnimator> tripMarkersAnimators = new HashMap<>();
 
     protected final CustomInfoWindow.TouchResponder responder = new CustomInfoWindow.TouchResponder() {
         @Override
         public void onActionUp(@NonNull String stopID, @Nullable String stopName) {
             if (listenerMain!= null){
                 Log.d(DEBUG_TAG, "Asked to show arrivals for stop ID: "+stopID);
                 listenerMain.requestArrivalsForStopID(stopID);
             }
         }
     };
     protected final LocationOverlay.OverlayCallbacks locationCallbacks = new LocationOverlay.OverlayCallbacks() {
         @Override
         public void onDisableFollowMyLocation() {
             updateGUIForLocationFollowing(false);
             followingLocation=false;
         }
 
         @Override
         public void onEnableFollowMyLocation() {
             updateGUIForLocationFollowing(true);
             followingLocation=true;
         }
     };
 
     private final ActivityResultLauncher<String[]> positionRequestLauncher =
             registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
                 if (result == null){
                     Log.w(DEBUG_TAG, "Got asked permission but request is null, doing nothing?");
                 }
                 else if(Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_COARSE_LOCATION)) &&
                         Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))){
 
                     map.getOverlays().remove(mLocationOverlay);
                     startLocationOverlay(true, map);
                     if(getContext()==null || getContext().getSystemService(Context.LOCATION_SERVICE)==null)
                         return;
                     LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
                     @SuppressLint("MissingPermission")
                     Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                     if (userLocation != null) {
                         map.getController().setZoom(POSITION_FOUND_ZOOM);
                         GeoPoint startPoint = new GeoPoint(userLocation);
                         setLocationFollowing(true);
                         map.getController().setCenter(startPoint);
                     }
                 }
                 else Log.w(DEBUG_TAG,"No location permission");
             });
 
     public MapFragment() {
     }
     public static MapFragment getInstance(){
         return new MapFragment();
     }
     public static MapFragment getInstance(@NonNull Stop stop){
         MapFragment fragment= new MapFragment();
         Bundle args = new Bundle();
         args.putDouble(BUNDLE_LATIT, stop.getLatitude());
         args.putDouble(BUNDLE_LONGIT, stop.getLongitude());
         args.putString(BUNDLE_NAME, stop.getStopDisplayName());
         args.putString(BUNDLE_ID, stop.ID);
         args.putString(BUNDLE_ROUTES_STOPPING, stop.routesThatStopHereToString());
         fragment.setArguments(args);
 
         return fragment;
     }
     //public static MapFragment getInstance(@NonNull Stop stop){
      //   return getInstance(stop.getLatitude(), stop.getLongitude(), stop.getStopDisplayName(), stop.ID);
     //}
 
 
     @Nullable
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
         //use the same layout as the activity
         View root = inflater.inflate(R.layout.activity_map, container, false);
         if (getContext() == null){
             throw new IllegalStateException();
         }
         ctx = getContext().getApplicationContext();
         Configuration.getInstance().load(ctx, PreferenceManager.getDefaultSharedPreferences(ctx));
         map = root.findViewById(R.id.map);
         map.setTileSource(TileSourceFactory.MAPNIK);
         //map.setTilesScaledToDpi(true);
         map.setFlingEnabled(true);
 
         // add ability to zoom with 2 fingers
         map.setMultiTouchControls(true);
 
         btCenterMap = root.findViewById(R.id.icon_center_map);
         btFollowMe = root.findViewById(R.id.icon_follow);
 
         //setup FolderOverlay
         stopsFolderOverlay = new FolderOverlay();
         //setup Bus Markers Overlay
         busPositionsOverlay = new FolderOverlay();
         //reset shown bus updates
         busPositionMarkersByTrip.clear();
         tripMarkersAnimators.clear();
         //set map not done
         hasMapStartFinished = false;
 
 
         //Start map from bundle
         if (savedInstanceState !=null)
             startMap(getArguments(), savedInstanceState);
         else startMap(getArguments(), savedMapState);
         //set listeners
         map.addMapListener(new DelayedMapListener(new MapListener() {
 
             @Override
             public boolean onScroll(ScrollEvent paramScrollEvent) {
                 requestStopsToShow();
                 //Log.d(DEBUG_TAG, "Scrolling");
                 //if (moveTriggeredByCode) moveTriggeredByCode =false;
                 //else setLocationFollowing(false);
                 return true;
             }
 
             @Override
             public boolean onZoom(ZoomEvent event) {
                 requestStopsToShow();
                 return true;
             }
 
         }));
 
 
         btCenterMap.setOnClickListener(v -> {
             //Log.i(TAG, "centerMap clicked ");
             if(Permissions.locationPermissionGranted(getContext())) {
                 final GeoPoint myPosition = mLocationOverlay.getMyLocation();
                 map.getController().animateTo(myPosition);
             } else
                 Toast.makeText(getContext(), R.string.enable_position_message_map, Toast.LENGTH_SHORT)
                         .show();
         });
 
         btFollowMe.setOnClickListener(v -> {
             //Log.i(TAG, "btFollowMe clicked ");
             if(Permissions.locationPermissionGranted(getContext()))
                 setLocationFollowing(!followingLocation);
             else
                 Toast.makeText(getContext(), R.string.enable_position_message_map, Toast.LENGTH_SHORT)
                     .show();
         });
 
 
         return root;
     }
 
     @Override
     public void onAttach(@NonNull Context context) {
         super.onAttach(context);
 
         //gtfsPosViewModel = new ViewModelProvider(this).get(GTFSPositionsViewModel.class);
         //viewModel
-        positionsViewModel = new ViewModelProvider(this).get(MQTTPositionsViewModel.class);
+        ViewModelProvider provider = new ViewModelProvider(this);
+        positionsViewModel = provider.get(MQTTPositionsViewModel.class);
+        stopsViewModel = provider.get(StopsMapViewModel.class);
         if (context instanceof FragmentListenerMain) {
             listenerMain = (FragmentListenerMain) context;
         } else {
             throw new RuntimeException(context.toString()
                     + " must implement FragmentListenerMain");
         }
     }
     @Override
     public void onDetach() {
         super.onDetach();
         listenerMain = null;
         //stop animations
 
         //    setupOnAttached = true;
         Log.w(DEBUG_TAG, "Fragment detached");
     }
 
     @Override
     public void onPause() {
         super.onPause();
         Log.w(DEBUG_TAG, "On pause called mapfrag");
         saveMapState();
         for (ObjectAnimator animator : tripMarkersAnimators.values()) {
             if(animator!=null && animator.isRunning()){
                 animator.cancel();
             }
         }
         tripMarkersAnimators.clear();
         positionsViewModel.stopPositionsListening();
 
-        if (stopFetcher!= null)
-            stopFetcher.cancel(true);
     }
 
     /**
      * Save the map state inside the fragment
      * (calls saveMapState(bundle))
      */
     private void saveMapState(){
         savedMapState = new Bundle();
         saveMapState(savedMapState);
     }
 
     /**
      * Save the state of the map to restore it to a later time
      * @param bundle the bundle in which to save the data
      */
     private void saveMapState(Bundle bundle){
         Log.d(DEBUG_TAG, "Saving state, location following: "+followingLocation);
         bundle.putBoolean(FOLLOWING_LOCAT_KEY, followingLocation);
         if (map == null){
             //The map is null, it  can happen?
             Log.e(DEBUG_TAG, "Cannot save map center, map is null");
             return;
         }
         final IGeoPoint loc = map.getMapCenter();
         bundle.putDouble(MAP_CENTER_LAT_KEY, loc.getLatitude());
         bundle.putDouble(MAP_CENTER_LON_KEY, loc.getLongitude());
         bundle.putDouble(MAP_CURRENT_ZOOM_KEY, map.getZoomLevelDouble());
     }
 
     @Override
     public void onResume() {
         super.onResume();
         if(listenerMain!=null) listenerMain.readyGUIfor(FragmentKind.MAP);
         if(positionsViewModel !=null) {
             //gtfsPosViewModel.requestUpdates();
             positionsViewModel.requestPosUpdates(MQTTMatoClient.LINES_ALL);
             //mapViewModel.testCascade();
             positionsViewModel.getTripsGtfsIDsToQuery().observe(this, dat -> {
                 Log.i(DEBUG_TAG, "Have these trips IDs missing from the DB, to be queried: "+dat);
                 //gtfsPosViewModel.downloadTripsFromMato(dat);
                 MatoTripsDownloadWorker.Companion.downloadTripsFromMato(dat,getContext().getApplicationContext(),
                         "BusTO-MatoTripDownload");
             });
         }
     }
 
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
         saveMapState(outState);
 
         super.onSaveInstanceState(outState);
     }
 
     //own methods
 
     /**
      * Switch following the location on and off
      * @param value true if we want to follow location
      */
     public void setLocationFollowing(Boolean value){
         followingLocation = value;
         if(mLocationOverlay==null || getContext() == null || map ==null)
             //nothing else to do
             return;
         if (value){
             mLocationOverlay.enableFollowLocation();
         } else {
             mLocationOverlay.disableFollowLocation();
         }
     }
 
     /**
      * Do all the stuff you need to do on the gui, when parameter is changed to value
      * @param following value
      */
     protected void updateGUIForLocationFollowing(boolean following){
         if (following)
             btFollowMe.setImageResource(R.drawable.ic_follow_me_on);
         else
             btFollowMe.setImageResource(R.drawable.ic_follow_me);
 
     }
 
     /**
      * Build the location overlay. Enable only when
      * a) we know we have the permission
      * b) the location map is set
      */
     private void startLocationOverlay(boolean enableLocation, MapView map){
         if(getActivity()== null) throw new IllegalStateException("Cannot enable LocationOverlay now");
         // Location Overlay
         // from OpenBikeSharing (THANK GOD)
         Log.d(DEBUG_TAG, "Starting position overlay");
         GpsMyLocationProvider imlp = new GpsMyLocationProvider(getActivity().getBaseContext());
         imlp.setLocationUpdateMinDistance(5);
         imlp.setLocationUpdateMinTime(2000);
 
         final LocationOverlay overlay = new LocationOverlay(imlp,map, locationCallbacks);
         if (enableLocation) overlay.enableMyLocation();
         overlay.setOptionsMenuEnabled(true);
 
         //map.getOverlays().add(this.mLocationOverlay);
         this.mLocationOverlay = overlay;
         map.getOverlays().add(mLocationOverlay);
     }
 
     public void startMap(Bundle incoming, Bundle savedInstanceState) {
         //Check that we're attached
         GeneralActivity activity = getActivity() instanceof GeneralActivity ? (GeneralActivity) getActivity() : null;
         if(getContext()==null|| activity==null){
             //we are not attached
             Log.e(DEBUG_TAG, "Calling startMap when not attached");
             return;
         }else{
             Log.d(DEBUG_TAG, "Starting map from scratch");
         }
         //clear previous overlays
         map.getOverlays().clear();
 
 
         //parse incoming bundle
         GeoPoint marker = null;
         String name = null;
         String ID = null;
         String routesStopping = "";
         if (incoming != null) {
             double lat = incoming.getDouble(BUNDLE_LATIT);
             double lon = incoming.getDouble(BUNDLE_LONGIT);
             marker = new GeoPoint(lat, lon);
             name = incoming.getString(BUNDLE_NAME);
             ID = incoming.getString(BUNDLE_ID);
             routesStopping = incoming.getString(BUNDLE_ROUTES_STOPPING, "");
         }
 
 
        //ask for location permission
         if(!Permissions.locationPermissionGranted(activity)){
             if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)){
                 //TODO: show dialog for permission rationale
                 Toast.makeText(activity, R.string.enable_position_message_map, Toast.LENGTH_SHORT).show();
             }
             positionRequestLauncher.launch(Permissions.LOCATION_PERMISSIONS);
 
         }
 
         shownStops = new HashSet<>();
         // move the map on the marker position or on a default view point: Turin, Piazza Castello
         // and set the start zoom
         IMapController mapController = map.getController();
         GeoPoint startPoint = null;
         startLocationOverlay(Permissions.locationPermissionGranted(activity),
                 map);
         // set the center point
         if (marker != null) {
             //startPoint = marker;
             mapController.setZoom(POSITION_FOUND_ZOOM);
             setLocationFollowing(false);
             // put the center a little bit off (animate later)
             startPoint = new GeoPoint(marker);
             startPoint.setLatitude(marker.getLatitude()+ utils.angleRawDifferenceFromMeters(20));
             startPoint.setLongitude(marker.getLongitude()-utils.angleRawDifferenceFromMeters(20));
             //don't need to do all the rest since we want to show a point
         } else if (savedInstanceState != null && savedInstanceState.containsKey(MAP_CURRENT_ZOOM_KEY)) {
             mapController.setZoom(savedInstanceState.getDouble(MAP_CURRENT_ZOOM_KEY));
             mapController.setCenter(new GeoPoint(savedInstanceState.getDouble(MAP_CENTER_LAT_KEY),
                     savedInstanceState.getDouble(MAP_CENTER_LON_KEY)));
             Log.d(DEBUG_TAG, "Location following from savedInstanceState: "+savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY));
             setLocationFollowing(savedInstanceState.getBoolean(FOLLOWING_LOCAT_KEY));
         } else {
             Log.d(DEBUG_TAG, "No position found from intent or saved state");
             boolean found = false;
             LocationManager locationManager =
                     (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
             //check for permission
             if (locationManager != null && Permissions.locationPermissionGranted(activity)) {
 
                 @SuppressLint("MissingPermission")
                 Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
 
                 if (userLocation != null) {
                     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){
                 startPoint = new GeoPoint(DEFAULT_CENTER_LAT, DEFAULT_CENTER_LON);
                 mapController.setZoom(NO_POSITION_ZOOM);
                 setLocationFollowing(false);
             }
         }
 
         // set the minimum zoom level
         map.setMinZoomLevel(15.0);
         //add contingency check (shouldn't happen..., but)
 
         if (startPoint != null) {
             mapController.setCenter(startPoint);
         }
 
 
         //add stops overlay
         //map.getOverlays().add(mLocationOverlay);
         map.getOverlays().add(this.stopsFolderOverlay);
 
         Log.d(DEBUG_TAG, "Requesting stops load");
         // This is not necessary, by setting the center we already move
         // the map and we trigger a stop request
         //requestStopsToShow();
         if (marker != null) {
             // make a marker with the info window open for the searched marker
             //TODO:  make Stop Bundle-able
             Marker stopMarker = makeMarker(marker, ID , name, routesStopping,true);
             map.getController().animateTo(marker);
         }
         //add the overlays with the bus stops
         if(busPositionsOverlay == null){
             //Log.i(DEBUG_TAG, "Null bus positions overlay,redo");
             busPositionsOverlay = new FolderOverlay();
         }
 
 
         if(positionsViewModel !=null){
             //should always be the case
             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())
                 //    gtfsPosViewModel.requestDelayedUpdates(4000);
             });
         } else {
             Log.e(DEBUG_TAG, "PositionsViewModel is null");
         }
+        if(stopsViewModel !=null){
+
+            stopsViewModel.getStopsInBoundingBox().observe(getViewLifecycleOwner(),
+                    this::showStopsMarkers
+                    );
+        } else Log.d(DEBUG_TAG, "Cannot observe new stops in map, stopsViewModel is null");
         map.getOverlays().add(this.busPositionsOverlay);
         //set map as started
         hasMapStartFinished = true;
     }
 
     /**
      * Start a request to load the stops that are in the current view
      * from the database
      */
     private void requestStopsToShow(){
         // get the top, bottom, left and right screen's coordinate
         BoundingBox bb = map.getBoundingBox();
-        double latFrom = bb.getLatSouth();
+        Log.d(DEBUG_TAG, "Requesting stops in bounding box, stopViewModel is null "+(stopsViewModel==null));
+        if(stopsViewModel!=null){
+            stopsViewModel.requestStopsInBoundingBox(bb);
+        }
+        /*double latFrom = bb.getLatSouth();
         double latTo = bb.getLatNorth();
         double lngFrom = bb.getLonWest();
         double lngTo = bb.getLonEast();
         if (stopFetcher!= null && stopFetcher.getStatus()!= AsyncTask.Status.FINISHED)
             stopFetcher.cancel(true);
         stopFetcher = new AsyncStopFetcher(this);
         stopFetcher.execute(
                 new AsyncStopFetcher.BoundingBoxLimit(lngFrom,lngTo,latFrom, latTo));
+
+         */
     }
 
     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 = 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 = new GeoPoint(posUpdate.getLatitude(), posUpdate.getLongitude());
             marker.setPosition(position);
         }
 
         if(posUpdate.getBearing()!=null)
             marker.setRotation(posUpdate.getBearing()*(-1.f));
     }
 
     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<LivePositionUpdate, TripAndPatternWithStops> pair = tripsPatterns.get(tripID);
             if (pair == null) continue;
             final LivePositionUpdate update = pair.getFirst();
             final TripAndPatternWithStops tripWithPatternStops = pair.getSecond();
 
 
             //check if Marker is already created
             if (busPositionMarkersByTrip.containsKey(tripID)){
                 //need to change the position of the marker
                 final Marker marker = busPositionMarkersByTrip.get(tripID);
                 assert marker!=null;
                 updateBusMarker(marker, update, false);
                 if(marker.getInfoWindow()!=null && marker.getInfoWindow() instanceof BusInfoWindow){
                     BusInfoWindow window = (BusInfoWindow) marker.getInfoWindow();
                     if(tripWithPatternStops != null) {
                         //Log.d(DEBUG_TAG, "Update pattern for trip: "+tripID);
                         window.setPatternAndDraw(tripWithPatternStops.getPattern());
                     }
 
                 }
             } else{
                 //marker is not there, need to make it
                 if(map==null) Log.e(DEBUG_TAG, "Creating marker with null map, things will explode");
                 final Marker marker = new Marker(map);
 
                 /*final Drawable mDrawable = DrawableUtils.Companion.getScaledDrawableResources(
                         getResources(),
                         R.drawable.point_heading_icon,
                 R.dimen.map_icons_size, R.dimen.map_icons_size);
 
                  */
                 //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,
                         route,12);
 
                  */
                 assert mdraw != null;
                 //mdraw.setBounds(0,0,28,28);
                 marker.setIcon(mdraw);
                 if(tripWithPatternStops == null){
                     noPatternsTrips.add(tripID);
                 }
                 MatoPattern markerPattern = null;
                 if(tripWithPatternStops != null && tripWithPatternStops.getPattern()!=null)
                     markerPattern = tripWithPatternStops.getPattern();
                 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?5
                 // 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);
         }
     }
 
     /**
      * Add stops as Markers on the map
      * @param stops the list of stops that must be included
      */
     protected void showStopsMarkers(List<Stop> stops){
         if (getContext() == null || stops == null){
             //we are not attached
             return;
         }
         boolean good = true;
 
         for (Stop stop : stops) {
             if (shownStops.contains(stop.ID)){
                 continue;
             }
             if(stop.getLongitude()==null || stop.getLatitude()==null)
                 continue;
 
             shownStops.add(stop.ID);
             if(!map.isShown()){
                 if(good)
                     Log.d(DEBUG_TAG, "Need to show stop but map is not shown, probably detached already");
                 good = false;
                 continue;
             } else if(map.getRepository() == null){
                 Log.e(DEBUG_TAG, "Map view repository is null");
             }
             GeoPoint marker = new GeoPoint(stop.getLatitude(), stop.getLongitude());
 
             Marker stopMarker = makeMarker(marker, stop, false);
             stopsFolderOverlay.add(stopMarker);
             if (!map.getOverlays().contains(stopsFolderOverlay)) {
                 Log.w(DEBUG_TAG, "Map doesn't have folder overlay");
             }
             good=true;
         }
         //Log.d(DEBUG_TAG,"We have " +stopsFolderOverlay.getItems().size()+" stops in the folderOverlay");
         //force redraw of markers
         map.invalidate();
     }
 
     public Marker makeMarker(GeoPoint geoPoint, Stop stop, boolean isStartMarker){
         return  makeMarker(geoPoint,stop.ID,
                 stop.getStopDefaultName(),
                 stop.routesThatStopHereToString(), isStartMarker);
     }
 
     public Marker makeMarker(GeoPoint geoPoint, String stopID, String stopName,
                              String routesStopping, boolean isStartMarker) {
 
         // 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, R.layout.linedetail_stop_infowindow, R.color.red_darker);
         marker.setInfoWindow(popup);
 
         // make the marker clickable
         marker.setOnMarkerClickListener((thisMarker, mapView) -> {
             if (thisMarker.isInfoWindowOpen()) {
                 // on second click
                 Log.w(DEBUG_TAG, "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_CENTER);
         // add to it an icon
         //marker.setIcon(getResources().getDrawable(R.drawable.bus_marker));
 
         marker.setIcon(ResourcesCompat.getDrawable(getResources(), R.drawable.bus_stop, ctx.getTheme()));
         // 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;
     }
 
     @Nullable
     @org.jetbrains.annotations.Nullable
     @Override
     public View getBaseViewForSnackBar() {
         return null;
     }
 
-    /**
-     * Simple asyncTask class to load the stops in the background
-     * Holds a weak reference to the fragment to do callbacks
-     */
-    static class AsyncStopFetcher extends AsyncTask<AsyncStopFetcher.BoundingBoxLimit,Void, List<Stop>>{
-
-        final WeakReference<MapFragment> fragmentWeakReference;
-
-        public AsyncStopFetcher(MapFragment fragment) {
-            this.fragmentWeakReference = new WeakReference<>(fragment);
-        }
-
-        @Override
-        protected List<Stop> doInBackground(BoundingBoxLimit... limits) {
-            if(fragmentWeakReference.get()==null || fragmentWeakReference.get().getContext() == null){
-                Log.w(DEBUG_TAG, "AsyncLoad fragmentWeakreference null");
-
-                return null;
-
-            }
-            final BoundingBoxLimit limit = limits[0];
-            //Log.d(DEBUG_TAG, "Async Stop Fetcher started working");
-
-            NextGenDB dbHelper = NextGenDB.getInstance(fragmentWeakReference.get().getContext());
-            ArrayList<Stop> stops = dbHelper.queryAllInsideMapView(limit.latitFrom, limit.latitTo,
-                    limit.longFrom, limit.latitTo);
-            dbHelper.close();
-            return stops;
-        }
-
-        @Override
-        protected void onPostExecute(List<Stop> stops) {
-            super.onPostExecute(stops);
-            //Log.d(DEBUG_TAG, "Async Stop Fetcher has finished working");
-            if(fragmentWeakReference.get()==null) {
-                Log.w(DEBUG_TAG, "AsyncLoad fragmentWeakreference null");
-                return;
-            }
-            if (stops!=null)
-                Log.d(DEBUG_TAG, "AsyncLoad number of stops: "+stops.size());
-            fragmentWeakReference.get().showStopsMarkers(stops);
-        }
-
-        private static class BoundingBoxLimit{
-            final double longFrom, longTo, latitFrom, latitTo;
-
-            public BoundingBoxLimit(double longFrom, double longTo, double latitFrom, double latitTo) {
-                this.longFrom = longFrom;
-                this.longTo = longTo;
-                this.latitFrom = latitFrom;
-                this.latitTo = latitTo;
-            }
-        }
-
-    }
 }
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
index a42e371..19a44bb 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -1,636 +1,649 @@
 /*
 	BusTO  - Fragments components
     Copyright (C) 2018 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.content.Context;
 
 import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.location.Location;
 import android.net.Uri;
 import android.os.Bundle;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 import androidx.lifecycle.Observer;
 import androidx.loader.app.LoaderManager;
 import androidx.loader.content.CursorLoader;
 import androidx.loader.content.Loader;
 import androidx.core.util.Pair;
 import androidx.preference.PreferenceManager;
 import androidx.appcompat.widget.AppCompatButton;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.work.WorkInfo;
 
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import com.android.volley.*;
 import it.reyboz.bustorino.BuildConfig;
 import it.reyboz.bustorino.R;
 import it.reyboz.bustorino.adapters.ArrivalsStopAdapter;
 import it.reyboz.bustorino.backend.*;
 import it.reyboz.bustorino.backend.FiveTAPIFetcher.QueryType;
 import it.reyboz.bustorino.backend.mato.MapiArrivalRequest;
 import it.reyboz.bustorino.data.DatabaseUpdate;
 import it.reyboz.bustorino.data.NextGenDB;
 import it.reyboz.bustorino.middleware.AppLocationManager;
 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;
 
 import java.util.*;
 
 public class NearbyStopsFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
 
+    public enum FragType{
+        STOPS(1), ARRIVALS(2);
+        private final int num;
+        FragType(int num){
+            this.num = num;
+        }
+        public static FragType fromNum(int i){
+            switch (i){
+                case 1: return STOPS;
+                case 2: return ARRIVALS;
+                default:
+                    throw new IllegalArgumentException("type not recognized");
+            }
+        }
+    }
+
     private FragmentListenerMain mListener;
     private FragmentLocationListener fragmentLocationListener;
 
     private final static String DEBUG_TAG = "NearbyStopsFragment";
     private final static String FRAGMENT_TYPE_KEY = "FragmentType";
-    public final static int TYPE_STOPS = 19, TYPE_ARRIVALS = 20;
-    private int fragment_type;
+    //public final static int TYPE_STOPS = 19, TYPE_ARRIVALS = 20;
+    private FragType fragment_type = FragType.STOPS;
 
     public final static String FRAGMENT_TAG="NearbyStopsFrag";
 
     //data Bundle
     private final String BUNDLE_LOCATION =  "location";
     private final int LOADER_ID = 0;
     private RecyclerView gridRecyclerView;
 
     private SquareStopAdapter dataAdapter;
     private AutoFitGridLayoutManager gridLayoutManager;
     private Location lastReceivedLocation = null;
     private ProgressBar circlingProgressBar,flatProgressBar;
     private int distance;
     protected SharedPreferences globalSharedPref;
     private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;
     private TextView messageTextView,titleTextView;
     private CommonScrollListener scrollListener;
     private AppCompatButton switchButton;
     private boolean firstLocForStops = true,firstLocForArrivals = true;
     public static final int COLUMN_WIDTH_DP = 250;
 
 
     private Integer MAX_DISTANCE = -3;
     private int MIN_NUM_STOPS = -1;
     private int TIME_INTERVAL_REQUESTS = -1;
     private AppLocationManager locManager;
 
     //These are useful for the case of nearby arrivals
     private ArrivalsManager arrivalsManager = null;
     private ArrivalsStopAdapter arrivalsStopAdapter = null;
 
     private boolean dbUpdateRunning = false;
 
     private ArrayList<Stop> currentNearbyStops = new ArrayList<>();
 
     public NearbyStopsFragment() {
         // Required empty public constructor
     }
 
     /**
      * Use this factory method to create a new instance of
      * this fragment using the provided parameters.
      * @return A new instance of fragment NearbyStopsFragment.
      */
-    public static NearbyStopsFragment newInstance(int fragmentType) {
-        if(fragmentType != TYPE_STOPS && fragmentType != TYPE_ARRIVALS )
-            throw new IllegalArgumentException("WRONG KIND OF FRAGMENT USED");
+    public static NearbyStopsFragment newInstance(FragType type) {
+        //if(fragmentType != TYPE_STOPS && fragmentType != TYPE_ARRIVALS )
+        //    throw new IllegalArgumentException("WRONG KIND OF FRAGMENT USED");
         NearbyStopsFragment fragment = new NearbyStopsFragment();
         final Bundle args = new Bundle(1);
-        args.putInt(FRAGMENT_TYPE_KEY,fragmentType);
+        args.putInt(FRAGMENT_TYPE_KEY,type.num);
         fragment.setArguments(args);
         return fragment;
     }
 
 
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         if (getArguments() != null) {
-            setFragmentType(getArguments().getInt(FRAGMENT_TYPE_KEY));
+            setFragmentType(FragType.fromNum(getArguments().getInt(FRAGMENT_TYPE_KEY)));
         }
         locManager = AppLocationManager.getInstance(getContext());
         fragmentLocationListener = new FragmentLocationListener(this);
         if (getContext()!=null) {
             globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE);
             globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
         }
 
 
     }
 
     @Override
     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                              Bundle savedInstanceState) {
         // Inflate the layout for this fragment
         if (getContext() == null) throw new RuntimeException();
         View root = inflater.inflate(R.layout.fragment_nearby_stops, container, false);
         gridRecyclerView = root.findViewById(R.id.stopGridRecyclerView);
         gridLayoutManager = new AutoFitGridLayoutManager(getContext().getApplicationContext(), Float.valueOf(utils.convertDipToPixels(getContext(),COLUMN_WIDTH_DP)).intValue());
         gridRecyclerView.setLayoutManager(gridLayoutManager);
         gridRecyclerView.setHasFixedSize(false);
         circlingProgressBar = root.findViewById(R.id.loadingBar);
         flatProgressBar = root.findViewById(R.id.horizontalProgressBar);
         messageTextView = root.findViewById(R.id.messageTextView);
         titleTextView = root.findViewById(R.id.titleTextView);
         switchButton = root.findViewById(R.id.switchButton);
 
         scrollListener = new CommonScrollListener(mListener,false);
         switchButton.setOnClickListener(v -> switchFragmentType());
         Log.d(DEBUG_TAG, "onCreateView");
 
         DatabaseUpdate.watchUpdateWorkStatus(getContext(), this, new Observer<List<WorkInfo>>() {
             @Override
             public void onChanged(List<WorkInfo> workInfos) {
                 if(workInfos.isEmpty()) return;
 
                 WorkInfo wi = workInfos.get(0);
                 if (wi.getState() == WorkInfo.State.RUNNING && locManager.isRequesterRegistered(fragmentLocationListener)) {
                     locManager.removeLocationRequestFor(fragmentLocationListener);
                     dbUpdateRunning = true;
                 } else if(!locManager.isRequesterRegistered(fragmentLocationListener)){
                     locManager.addLocationRequestFor(fragmentLocationListener);
                     dbUpdateRunning = false;
                 }
             }
         });
         return root;
     }
 
 
     /**
      * Use this method to set the fragment type
      * @param type the type, TYPE_ARRIVALS or TYPE_STOPS
      */
-    private void setFragmentType(int type){
-        if(type!=TYPE_ARRIVALS && type !=TYPE_STOPS)
-            throw new IllegalArgumentException("type not recognized");
+    private void setFragmentType(FragType type){
         this.fragment_type = type;
         switch(type){
-            case TYPE_ARRIVALS:
-
+            case ARRIVALS:
                 TIME_INTERVAL_REQUESTS = 5*1000;
                 break;
-            case TYPE_STOPS:
+            case STOPS:
                 TIME_INTERVAL_REQUESTS = 1000;
 
         }
     }
 
 
     @Override
     public void onAttach(@NonNull Context context) {
         super.onAttach(context);
         /// TODO: RISOLVERE PROBLEMA: il context qui e' l'Activity non il Fragment
         if (context instanceof FragmentListenerMain) {
             mListener = (FragmentListenerMain) context;
         } else {
             throw new RuntimeException(context
                     + " must implement OnFragmentInteractionListener");
         }
         Log.d(DEBUG_TAG, "OnAttach called");
     }
 
     @Override
     public void onPause() {
         super.onPause();
 
         gridRecyclerView.setAdapter(null);
         locManager.removeLocationRequestFor(fragmentLocationListener);
         Log.d(DEBUG_TAG,"On paused called");
     }
 
     @Override
     public void onResume() {
         super.onResume();
         try{
             if(!dbUpdateRunning && !locManager.isRequesterRegistered(fragmentLocationListener))
                     locManager.addLocationRequestFor(fragmentLocationListener);
         } catch (SecurityException ex){
             //ignored
             //try another location provider
         }
         switch(fragment_type){
-            case TYPE_STOPS:
+            case STOPS:
                 if(dataAdapter!=null){
                     gridRecyclerView.setAdapter(dataAdapter);
                     circlingProgressBar.setVisibility(View.GONE);
                 }
                 break;
-            case TYPE_ARRIVALS:
+            case ARRIVALS:
                 if(arrivalsStopAdapter!=null){
                     gridRecyclerView.setAdapter(arrivalsStopAdapter);
                     circlingProgressBar.setVisibility(View.GONE);
                 }
         }
 
         mListener.enableRefreshLayout(false);
         Log.d(DEBUG_TAG,"OnResume called");
         if(getContext()==null){
             Log.e(DEBUG_TAG, "NULL CONTEXT, everything is going to crash now");
             MIN_NUM_STOPS = 5;
             MAX_DISTANCE = 600;
             return;
         }
         //Re-read preferences
         SharedPreferences shpr = PreferenceManager.getDefaultSharedPreferences(getContext().getApplicationContext());
         //For some reason, they are all saved as strings
         MAX_DISTANCE = shpr.getInt(getString(R.string.pref_key_radius_recents),600);
         boolean isMinStopInt = true;
         try{
             MIN_NUM_STOPS = shpr.getInt(getString(R.string.pref_key_num_recents), 5);
         } catch (ClassCastException ex){
             isMinStopInt = false;
         }
         if(!isMinStopInt)
             try {
                 MIN_NUM_STOPS = Integer.parseInt(shpr.getString(getString(R.string.pref_key_num_recents), "5"));
             } catch (NumberFormatException ex){
                 MIN_NUM_STOPS = 5;
             }
         if(BuildConfig.DEBUG)
             Log.d(DEBUG_TAG, "Max distance for stops: "+MAX_DISTANCE+
                     ", Min number of stops: "+MIN_NUM_STOPS);
     }
 
 
     @Override
     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         gridRecyclerView.setVisibility(View.INVISIBLE);
         gridRecyclerView.addOnScrollListener(scrollListener);
     }
 
     @Override
     public void onDetach() {
         super.onDetach();
         mListener = null;
         if(arrivalsManager!=null) arrivalsManager.cancelAllRequests();
     }
 
     @NonNull
     @Override
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         //BUILD URI
         if (args!=null)
             lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
         Uri.Builder builder =  new Uri.Builder();
         builder.scheme("content").authority(AppDataProvider.AUTHORITY)
                 .appendPath("stops").appendPath("location")
                 .appendPath(String.valueOf(lastReceivedLocation.getLatitude()))
                 .appendPath(String.valueOf(lastReceivedLocation.getLongitude()))
                 .appendPath(String.valueOf(distance)); //distance
         CursorLoader cl = new CursorLoader(getContext(),builder.build(),NextGenDB.QUERY_COLUMN_stops_all,null,null,null);
         cl.setUpdateThrottle(2000);
         return cl;
     }
 
 
     @Override
     public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
         if (0 > MAX_DISTANCE) throw new AssertionError();
         //Cursor might be null
         if (cursor == null) {
             Log.e(DEBUG_TAG, "Null cursor, something really wrong happened");
             return;
         }
         Log.d(DEBUG_TAG, "Num stops found: " + cursor.getCount() + ", Current distance: " + distance);
 
         if (!dbUpdateRunning && (cursor.getCount() < MIN_NUM_STOPS && distance <= MAX_DISTANCE)) {
             distance = distance * 2;
             Bundle d = new Bundle();
             d.putParcelable(BUNDLE_LOCATION, lastReceivedLocation);
             getLoaderManager().restartLoader(LOADER_ID, d, this);
             //Log.d(DEBUG_TAG, "Doubling distance now!");
             return;
         }
         Log.d("LoadFromCursor", "Number of nearby stops: " + cursor.getCount());
         ////////
         if(cursor.getCount()>0)
             currentNearbyStops = NextGenDB.getStopsFromCursorAllFields(cursor);
 
         showCurrentStops();
     }
 
     /**
      * Display the stops, or run new set of requests for arrivals
      */
     private void showCurrentStops(){
         if (currentNearbyStops.isEmpty()) {
             setNoStopsLayout();
             return;
         }
 
         double minDistance = Double.POSITIVE_INFINITY;
         for(Stop s: currentNearbyStops){
             minDistance = Math.min(minDistance, s.getDistanceFromLocation(lastReceivedLocation));
         }
 
 
         //quick trial to hopefully always get the stops in the correct order
         Collections.sort(currentNearbyStops,new StopSorterByDistance(lastReceivedLocation));
         switch (fragment_type){
-            case TYPE_STOPS:
+            case STOPS:
                 showStopsInRecycler(currentNearbyStops);
                 break;
-            case TYPE_ARRIVALS:
+            case ARRIVALS:
                 arrivalsManager = new ArrivalsManager(currentNearbyStops);
                 flatProgressBar.setVisibility(View.VISIBLE);
                 flatProgressBar.setProgress(0);
                 flatProgressBar.setIndeterminate(false);
                 //for the moment, be satisfied with only one location
                 //AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
                 break;
             default:
         }
 
     }
 
     @Override
     public void onLoaderReset(@NonNull Loader<Cursor> loader) {
     }
 
     /**
      * To enable targeting from the Button
      */
     public void switchFragmentType(View v){
         switchFragmentType();
     }
 
     /**
      * Call when you need to switch the type of fragment
      */
     private void switchFragmentType(){
-        if(fragment_type==TYPE_ARRIVALS){
-            setFragmentType(TYPE_STOPS);
+        if(fragment_type==FragType.ARRIVALS){
+            setFragmentType(FragType.STOPS);
             switchButton.setText(getString(R.string.show_arrivals));
             titleTextView.setText(getString(R.string.nearby_stops_message));
             if(arrivalsManager!=null)
                 arrivalsManager.cancelAllRequests();
             if(dataAdapter!=null)
                 gridRecyclerView.setAdapter(dataAdapter);
 
-        } else if (fragment_type==TYPE_STOPS){
-            setFragmentType(TYPE_ARRIVALS);
+        } else if (fragment_type==FragType.STOPS){
+            setFragmentType(FragType.ARRIVALS);
             titleTextView.setText(getString(R.string.nearby_arrivals_message));
             switchButton.setText(getString(R.string.show_stops));
             if(arrivalsStopAdapter!=null)
                 gridRecyclerView.setAdapter(arrivalsStopAdapter);
         }
         fragmentLocationListener.lastUpdateTime = -1;
         //locManager.removeLocationRequestFor(fragmentLocationListener);
         //locManager.addLocationRequestFor(fragmentLocationListener);
         showCurrentStops();
     }
 
     //useful methods
 
     /////// GUI METHODS ////////
     private void showStopsInRecycler(List<Stop> stops){
 
         if(firstLocForStops) {
             dataAdapter = new SquareStopAdapter(stops, mListener, lastReceivedLocation);
             gridRecyclerView.setAdapter(dataAdapter);
             firstLocForStops = false;
         }else {
             dataAdapter.setStops(stops);
             dataAdapter.setUserPosition(lastReceivedLocation);
         }
         dataAdapter.notifyDataSetChanged();
 
         //showRecyclerHidingLoadMessage();
         if (gridRecyclerView.getVisibility() != View.VISIBLE) {
             circlingProgressBar.setVisibility(View.GONE);
             gridRecyclerView.setVisibility(View.VISIBLE);
         }
         messageTextView.setVisibility(View.GONE);
 
         if(mListener!=null) mListener.readyGUIfor(FragmentKind.NEARBY_STOPS);
     }
 
     private void showArrivalsInRecycler(List<Palina> palinas){
         Collections.sort(palinas,new StopSorterByDistance(lastReceivedLocation));
 
         final ArrayList<Pair<Stop,Route>> routesPairList = new ArrayList<>(10);
         //int maxNum = Math.min(MAX_STOPS, stopList.size());
         for(Palina p: palinas){
             //if there are no routes available, skip stop
             if(p.queryAllRoutes().size() == 0) continue;
             for(Route r: p.queryAllRoutes()){
                 //if there are no routes, should not do anything
                 if (r.passaggi != null && !r.passaggi.isEmpty())
                     routesPairList.add(new Pair<>(p,r));
             }
         }
         if (getContext()==null){
             Log.e(DEBUG_TAG, "Trying to show arrivals in Recycler but we're not attached");
             return;
         }
         if(firstLocForArrivals){
             arrivalsStopAdapter = new ArrivalsStopAdapter(routesPairList,mListener,getContext(),lastReceivedLocation);
             gridRecyclerView.setAdapter(arrivalsStopAdapter);
             firstLocForArrivals = false;
         } else {
             arrivalsStopAdapter.setRoutesPairListAndPosition(routesPairList,lastReceivedLocation);
         }
 
         //arrivalsStopAdapter.notifyDataSetChanged();
 
         showRecyclerHidingLoadMessage();
         if(mListener!=null) mListener.readyGUIfor(FragmentKind.NEARBY_ARRIVALS);
 
     }
 
     private void setNoStopsLayout(){
         messageTextView.setVisibility(View.VISIBLE);
         messageTextView.setText(R.string.no_stops_nearby);
         circlingProgressBar.setVisibility(View.GONE);
     }
 
     /**
      * Does exactly what is says on the tin
      */
     private void showRecyclerHidingLoadMessage(){
         if (gridRecyclerView.getVisibility() != View.VISIBLE) {
             circlingProgressBar.setVisibility(View.GONE);
             gridRecyclerView.setVisibility(View.VISIBLE);
         }
         messageTextView.setVisibility(View.GONE);
     }
 
     class ArrivalsManager implements Response.Listener<Palina>, Response.ErrorListener{
         final HashMap<String,Palina> palinasDone = new HashMap<>();
         //final Map<String,List<Route>> routesToAdd = new HashMap<>();
         final static String REQUEST_TAG = "NearbyArrivals";
         final NetworkVolleyManager volleyManager;
         int activeRequestCount = 0,reqErrorCount = 0, reqSuccessCount=0;
 
         ArrivalsManager(List<Stop> stops){
             volleyManager = NetworkVolleyManager.getInstance(getContext());
 
             int MAX_ARRIVAL_STOPS = 35;
             Date currentDate = new Date();
             int timeRange = 3600;
             int departures = 10;
             int numreq = 0;
             for(Stop s: stops.subList(0,Math.min(stops.size(), MAX_ARRIVAL_STOPS))){
 
                 final MapiArrivalRequest req = new MapiArrivalRequest(s.ID, currentDate, timeRange, departures, this, this);
                 req.setTag(REQUEST_TAG);
                 volleyManager.addToRequestQueue(req);
                 activeRequestCount++;
                 numreq++;
             }
             flatProgressBar.setMax(numreq);
         }
 
 
 
         @Override
         public void onErrorResponse(VolleyError error) {
             if(error instanceof ParseError){
                 //TODO
                 Log.w(DEBUG_TAG,"Parsing error for stop request");
             } else if (error instanceof NetworkError){
                 String s;
                 if(error.networkResponse!=null)
                     s = new String(error.networkResponse.data);
                 else s="";
                 Log.w(DEBUG_TAG,"Network error: "+s);
             }else {
                 Log.w(DEBUG_TAG,"Volley Error: "+error.getMessage());
             }
             if(error.networkResponse!=null){
                 Log.w(DEBUG_TAG, "Error status code: "+error.networkResponse.statusCode);
             }
             //counters
             activeRequestCount--;
             reqErrorCount++;
             flatProgressBar.setProgress(reqErrorCount+reqSuccessCount);
         }
 
         @Override
         public void onResponse(Palina result) {
             //counter for requests
             activeRequestCount--;
             reqSuccessCount++;
             //final Palina palinaInMap = palinasDone.get(result.ID);
             //palina cannot be null here
             //sorry for the brutal crash when it happens
             //if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
             //add the palina to the successful one
             //TODO: Avoid redoing everything every time a new Result arrives
             palinasDone.put(result.ID, result);
             final ArrayList<Palina> outList = new ArrayList<>();
             for(Palina p: palinasDone.values()){
                 final List<Route> routes = p.queryAllRoutes();
                 if(routes!=null && routes.size()>0) outList.add(p);
             }
             showArrivalsInRecycler(outList);
             flatProgressBar.setProgress(reqErrorCount+reqSuccessCount);
             if(activeRequestCount==0) {
                 flatProgressBar.setIndeterminate(true);
                 flatProgressBar.setVisibility(View.GONE);
             }
         }
         void cancelAllRequests(){
             volleyManager.getRequestQueue().cancelAll(REQUEST_TAG);
             flatProgressBar.setVisibility(View.GONE);
         }
     }
     /**
      * Local locationListener, to use for the GPS
      */
     class FragmentLocationListener implements AppLocationManager.LocationRequester{
 
         LoaderManager.LoaderCallbacks<Cursor> callbacks;
         private int oldLocStatus = -2;
         private LocationCriteria cr;
         private long lastUpdateTime = -1;
 
         public FragmentLocationListener(LoaderManager.LoaderCallbacks<Cursor> callbacks) {
             this.callbacks = callbacks;
         }
 
         @Override
         public void onLocationChanged(Location location) {
             //set adapter
             float accuracy = location.getAccuracy();
             if(accuracy<100 && !dbUpdateRunning) {
                 distance = 20;
                 final Bundle msgBundle = new Bundle();
                 msgBundle.putParcelable(BUNDLE_LOCATION,location);
                 getLoaderManager().restartLoader(LOADER_ID,msgBundle,callbacks);
             }
             lastUpdateTime = System.currentTimeMillis();
             Log.d("BusTO:NearPositListen","can start loader "+ !dbUpdateRunning);
         }
 
         @Override
         public void onLocationStatusChanged(int status) {
             switch(status){
                 case AppLocationManager.LOCATION_GPS_AVAILABLE:
                     messageTextView.setVisibility(View.GONE);
 
                     break;
                 case AppLocationManager.LOCATION_UNAVAILABLE:
                     messageTextView.setText(R.string.enableGpsText);
                     messageTextView.setVisibility(View.VISIBLE);
                     break;
                 default:
                     Log.e(DEBUG_TAG,"Location status not recognized");
             }
         }
 
         @Override
         public LocationCriteria getLocationCriteria() {
 
             return new LocationCriteria(120,TIME_INTERVAL_REQUESTS);
         }
 
         @Override
         public long getLastUpdateTimeMillis() {
             return lastUpdateTime;
         }
         void resetUpdateTime(){
             lastUpdateTime = -1;
         }
 
         @Override
         public void onLocationProviderAvailable() {
 
         }
 
         @Override
         public void onLocationDisabled() {
 
         }
     }
 }
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/StopListFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/StopListFragment.java
index 3a19b49..4ec5795 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/StopListFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/StopListFragment.java
@@ -1,145 +1,150 @@
 /*
 	BusTO  - Fragments components
     Copyright (C) 2018 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.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import androidx.annotation.NonNull;
 import androidx.loader.app.LoaderManager;
 import androidx.loader.content.CursorLoader;
 import androidx.loader.content.Loader;
 import android.util.Log;
 import it.reyboz.bustorino.backend.Route;
 import it.reyboz.bustorino.backend.Stop;
 import it.reyboz.bustorino.data.AppDataProvider;
 import it.reyboz.bustorino.data.NextGenDB.Contract.StopsTable;
 import it.reyboz.bustorino.adapters.StopAdapter;
+import org.jetbrains.annotations.NotNull;
 
 import java.util.Arrays;
 import java.util.List;
 
 public class StopListFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks<Cursor> {
 
     private List<Stop> stopList;
     private StopAdapter mListAdapter;
     private static final String[] dataProjection={StopsTable.COL_LINES_STOPPING,StopsTable.COL_PLACE,StopsTable.COL_TYPE,StopsTable.COL_LOCATION};
     private static final String KEY_STOP_ID = "stopID";
     private static final String WORDS_SEARCHED= "query";
     private static final int EXTRA_ID=160;
 
     private String searchedWords;
     public StopListFragment(){
         //required empty constructor
     }
 
     public static StopListFragment newInstance(String searchQuery) {
 
         Bundle args = new Bundle();
         //TODO: search stops inside the DB
         args.putString(WORDS_SEARCHED,searchQuery);
         StopListFragment fragment = new StopListFragment();
         args.putSerializable(LIST_TYPE,FragmentKind.STOPS);
         fragment.setArguments(args);
         return fragment;
     }
 
     public void setStopList(List<Stop> stopList){
         this.stopList = stopList;
 
     }
 
 
     @Override
     public void onResume() {
         super.onResume();
         LoaderManager loaderManager  = getLoaderManager();
+        mListener.readyGUIfor(FragmentKind.STOPS);
         if(stopList!=null) {
             mListAdapter = new StopAdapter(getContext(),stopList);
             resetListAdapter(mListAdapter);
             for (int i = 0; i < stopList.size(); i++) {
                 final Bundle b = new Bundle();
                 b.putString(KEY_STOP_ID, stopList.get(i).ID);
                 loaderManager.restartLoader(i, b, this);
             }
 
         }
     }
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         searchedWords = getArguments().getString(WORDS_SEARCHED);
 
     }
 
     @Override
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         //The id will be the position of the element in the list
 
         Uri.Builder builder = new Uri.Builder();
         String stopID = args.getString(KEY_STOP_ID);
         //Log.d("StopListLoader","Creating loader for stop "+stopID+" in position: "+id);
         if(stopID!=null) {
             builder.scheme("content").authority(AppDataProvider.AUTHORITY)
                     .appendPath("stop").appendPath(stopID);
              CursorLoader cursorLoader = new CursorLoader(getContext(),builder.build(),dataProjection,null,null,null);
             return cursorLoader;
         } else return null;
 
     }
 
 
     @Override
     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
         //check that we have valid data
         if(data==null) return;
         final int numRows = data.getCount();
         final int elementIdx = loader.getId();
 
         if (numRows==0) {
             Log.w(this.getClass().getName(),"No  info for stop in position "+elementIdx);
             return;
         } else if(numRows>1){
             Log.d("StopLoading","we have "+numRows+" rows, should only have 1. Taking the first...");
         }
         final int linesIndex = data.getColumnIndex(StopsTable.COL_LINES_STOPPING);
         data.moveToFirst();
         Stop stopToModify = stopList.get(elementIdx);
         final String linesStopping = data.getString(linesIndex);
         stopToModify.setRoutesThatStopHere(Arrays.asList(linesStopping.split(",")));
         try {
             final String possibleLocation = data.getString(data.getColumnIndexOrThrow(StopsTable.COL_LOCATION));
 
             if (stopToModify.location == null && possibleLocation != null && !possibleLocation.isEmpty() && !possibleLocation.equals("_")) {
                 stopToModify.location = possibleLocation;
             }
             if (stopToModify.type == null) {
                 stopToModify.type = Route.Type.fromCode(data.getInt(data.getColumnIndex(StopsTable.COL_TYPE)));
             }
         }catch (IllegalArgumentException arg){
             if(arg.getMessage().contains("'location' does not exist")) Log.w("StopLoading","stop with no location found");
         }
         //Log.d("StopListFragmentLoader","Finished parsing data for stop in position "+elementIdx);
         mListAdapter.notifyDataSetChanged();
     }
 
     @Override
     public void onLoaderReset(Loader<Cursor> loader) {
         loader.abandon();
     }
+
 }
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/GTFSPositionsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/GTFSPositionsViewModel.kt
similarity index 99%
rename from app/src/main/java/it/reyboz/bustorino/fragments/GTFSPositionsViewModel.kt
rename to app/src/main/java/it/reyboz/bustorino/viewmodels/GTFSPositionsViewModel.kt
index 3dcbb76..66110d8 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/GTFSPositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/GTFSPositionsViewModel.kt
@@ -1,206 +1,206 @@
 /*
 	BusTO  - View Models 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
+package it.reyboz.bustorino.viewmodels
 
 import android.app.Application
 import android.util.Log
 import androidx.lifecycle.*
 import com.android.volley.Response
 import it.reyboz.bustorino.backend.NetworkVolleyManager
 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
 
 /**
  * View Model for the map. For containing the stops, the trips and whatever
  */
 class GTFSPositionsViewModel(application: Application): AndroidViewModel(application) {
     private val gtfsRepo = GtfsRepository(application)
 
     private val netVolleyManager = NetworkVolleyManager.getInstance(application)
 
 
     val positionsLiveData = MutableLiveData<ArrayList<LivePositionUpdate>>()
     private val positionsRequestRunning = MutableLiveData<Boolean>()
 
 
     private val positionRequestListener = object: GtfsRtPositionsRequest.Companion.RequestListener{
         override fun onResponse(response: ArrayList<LivePositionUpdate>?) {
             Log.i(DEBUG_TI,"Got response from the GTFS RT server")
             response?.let {it:ArrayList<LivePositionUpdate> ->
                 if (it.size == 0) {
                     Log.w(DEBUG_TI,"No position updates from the server")
                     return
                 }
                 else {
                     //Log.i(DEBUG_TI, "Posting value to positionsLiveData")
                     viewModelScope.launch { positionsLiveData.postValue(it) }
 
                 }
             }
             //whatever the result, launch again the update TODO
 
         }
 
     }
     private val positionRequestErrorListener = Response.ErrorListener {
         //error listener, it->VolleyError
         Log.e(DEBUG_TI, "Could not download the update, error:\n"+it.stackTrace)
 
         //TODO: launch again if needed
 
     }
 
     fun requestUpdates(){
         if(positionsRequestRunning.value == null || !positionsRequestRunning.value!!) {
             val request = GtfsRtPositionsRequest(positionRequestErrorListener, positionRequestListener)
             netVolleyManager.requestQueue.add(request)
             Log.i(DEBUG_TI, "Requested GTFS realtime position updates")
             positionsRequestRunning.value = true
         }
 
     }
     /*suspend fun requestDelayedUpdates(timems: Long){
         delay(timems)
         requestUpdates()
     }
      */
     fun requestDelayedUpdates(timems: Long){
         viewModelScope.launch {
             delay(timems)
             requestUpdates()
         }
     }
 
     // TRIPS IDS that have to be queried to the DB
     val tripsIDsInUpdates : LiveData<List<String>> = positionsLiveData.map {
 
         Log.i(DEBUG_TI, "positionsLiveData changed")
         //allow new requests for the positions of buses
         positionsRequestRunning.value = false
         //add "gtt:" prefix because it's implicit in GTFS Realtime API
         return@map it.map { pos -> "gtt:"+pos.tripID  }
     }
     //this holds the trips that have been downloaded but for which we have no pattern
     /*private val gtfsTripsInDBMissingPattern = tripsIDsInUpdates.map { tripsIDs ->
         val tripsInDB = gtfsRepo.gtfsDao.getTripsFromIDs(tripsIDs)
         val tripsPatternCodes = tripsInDB.map { tr -> tr.patternId }
         val codesInDB = gtfsRepo.gtfsDao.getPatternsCodesInTheDB(tripsPatternCodes)
 
         tripsInDB.filter { !(codesInDB.contains(it.patternId)) }
     }*/
     //private val patternsCodesInDB = tripsDBPatterns.map { gtfsRepo.gtfsDao.getPatternsCodesInTheDB(it) }
 
     // trips that are in the DB, together with the pattern. If the pattern is not present in the DB, it's null
     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
      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) }
         else {
             Log.e(DEBUG_TI,"Got results for gtfsTripsInDB but not tripsIDsInUpdates??")
             return@map ArrayList<String>()
         }
     }
 
     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(positionsLiveData.value!=null)
             for(update in positionsLiveData.value!!){
                 val trID = 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))
         }
 
         return@map mdict
     }
     /*
      There are two strategies for the queries, since we cannot query a bunch of tripIDs all together
      to Mato API -> we need to query each separately:
      1 -> wait until they are all queried to insert in the DB
      2 -> after each request finishes, insert it into the DB
 
      Keep in mind that trips DO CHANGE often, and so do the Patterns
      */
     fun downloadTripsFromMato(trips: List<String>): Boolean{
         return MatoTripsDownloadWorker.downloadTripsFromMato(trips,getApplication(), DEBUG_TI)
     }
     private fun downloadMissingPatterns(routeIds: List<String>): Boolean{
         return MatoPatternsDownloadWorker.downloadPatternsForRoutes(routeIds, getApplication())
     }
 
     init {
 
         Log.d(DEBUG_TI, "MapViewModel created")
         Log.d(DEBUG_TI, "Observers of positionsLiveData ${positionsLiveData.hasActiveObservers()}")
 
         positionsRequestRunning.value = false;
     }
     fun testCascade(){
        val n  = ArrayList<LivePositionUpdate>()
         n.add(LivePositionUpdate("22920721U","lala","lalal","lol","ASD",
             1000.0,1000.0, 9000.0f, 21838191, null
             ))
         positionsLiveData.value = n
     }
 
 
     /**
      * Start downloading missing GtfsTrips and Insert them in the DB
      */
 
 
     companion object{
         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/LinesViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
similarity index 97%
rename from app/src/main/java/it/reyboz/bustorino/fragments/LinesViewModel.kt
rename to app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
index 94442f9..38949b1 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesViewModel.kt
@@ -1,107 +1,108 @@
-package it.reyboz.bustorino.fragments
+package it.reyboz.bustorino.viewmodels
 
 import android.app.Application
 import android.util.Log
 import androidx.lifecycle.*
+import it.reyboz.bustorino.backend.Result
 import it.reyboz.bustorino.backend.Stop
 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
 import it.reyboz.bustorino.data.gtfs.GtfsRoute
 import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
 import it.reyboz.bustorino.data.gtfs.PatternStop
 import java.util.concurrent.Executors
 
 class LinesViewModel(application: Application) : AndroidViewModel(application) {
 
     private val gtfsRepo: GtfsRepository
     private val oldRepo: OldDataRepository
     //val patternsByRouteLiveData: LiveData<List<MatoPattern>>
 
     private val routeIDToSearch = MutableLiveData<String>()
     private var lastShownPatternStops = ArrayList<String>()
 
     val currentPatternStops = MutableLiveData<List<PatternStop>>()
     val selectedPatternLiveData = MutableLiveData<MatoPatternWithStops>()
 
 
     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)
 
         oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
 
     }
 
 
     val routesGTTLiveData: LiveData<List<GtfsRoute>> by lazy{
         gtfsRepo.getLinesLiveDataForFeed("gtt")
     }
     val patternsWithStopsByRouteLiveData = routeIDToSearch.switchMap {
         gtfsRepo.getPatternsWithStopsForRouteID(it)
 
     }
 
 
     fun setRouteIDQuery(routeID: String){
         routeIDToSearch.value = routeID
     }
 
     fun getRouteIDQueried(): String?{
         return routeIDToSearch.value
     }
     var shouldShowMessage = true
 
     fun setPatternToDisplay(patternStops: MatoPatternWithStops){
 
         selectedPatternLiveData.value = patternStops
     }
     /**
      * Find the
      */
     private fun requestStopsForGTFSIDs(gtfsIDs: List<String>){
         if (gtfsIDs.equals(lastShownPatternStops)){
             //nothing to do
             return
         }
         oldRepo.requestStopsWithGtfsIDs(gtfsIDs) {
             if (it.isSuccess) {
                 stopsForPatternLiveData.postValue(it.result)
             } else {
                 Log.e("BusTO-LinesVM", "Got error on callback with stops for gtfsID")
                 it.exception?.printStackTrace()
             }
         }
         lastShownPatternStops.clear()
         for(id in gtfsIDs)
             lastShownPatternStops.add(id)
     }
 
     fun requestStopsForPatternWithStops(patternStops: MatoPatternWithStops){
         val gtfsIDs = ArrayList<String>()
         for(pat in patternStops.stopsIndices){
             gtfsIDs.add(pat.stopGtfsId)
         }
         requestStopsForGTFSIDs(gtfsIDs)
     }
 
 
     /*fun getLinesGTT(): MutableLiveData<List<GtfsRoute>> {
         val routesData = MutableLiveData<List<GtfsRoute>>()
             viewModelScope.launch {
                  val routes=gtfsRepo.getLinesForFeed("gtt")
                 routesData.postValue(routes)
             }
         return routesData
     }*/
 }
\ 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
index 618784e..0b3f477 100644
--- a/app/src/main/java/it/reyboz/bustorino/viewmodels/MQTTPositionsViewModel.kt
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/MQTTPositionsViewModel.kt
@@ -1,165 +1,164 @@
 /*
 	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/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
new file mode 100644
index 0000000..fced032
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/StopsMapViewModel.kt
@@ -0,0 +1,55 @@
+package it.reyboz.bustorino.viewmodels
+
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.map
+import it.reyboz.bustorino.backend.Result
+import it.reyboz.bustorino.backend.Stop
+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
+import org.osmdroid.util.BoundingBox
+import java.util.ArrayList
+import java.util.concurrent.Executors
+
+class StopsMapViewModel(application: Application): AndroidViewModel(application) {
+
+
+    private val executor = Executors.newFixedThreadPool(2)
+    private val oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
+    /*
+    private val boundingBoxLiveData = MutableLiveData<BoundingBox>()
+
+    fun setStopBoundingBox(bb: BoundingBox){
+        boundingBoxLiveData.value = bb
+    }
+
+     */
+
+    val stopsInBoundingBox = MutableLiveData<ArrayList<Stop>>()
+
+    private val callback =
+        OldDataRepository.Callback<ArrayList<Stop>> { result ->
+            result.let {
+                if(it.isSuccess){
+                    stopsInBoundingBox.postValue(it.result)
+                    Log.d(DEBUG_TAG, "Setting value of stops in bounding box")
+                }
+
+            }
+        }
+
+    fun requestStopsInBoundingBox(bb: BoundingBox) {
+        bb.let {
+            Log.d(DEBUG_TAG, "Launching stop request")
+            oldRepo.requestStopsInArea(it.latSouth, it.latNorth, it.lonWest, it.lonEast, callback)
+        }
+    }
+    companion object{
+        private const val DEBUG_TAG = "BusTOStopMapViewModel"
+    }
+}
\ No newline at end of file