Bimba.git

commit eb8ae8d22844c0eeebf4ac698c63eed9b4b93f11

Author: Adam Pioterek <adam.pioterek@protonmail.ch>

favourites with vm

%!v(PANIC=String method: strings: negative Repeat count)


diff --git a/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt b/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt
index 74c0a57f9d4a927528767567f96afe77bb248194..ac29fc4fceb1e21e7bb2767cf6d0572bf16d8264 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt
@@ -3,6 +3,7 @@
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
+import android.util.Log
 import ml.adamsprogs.bimba.models.Departure
 
 class MessageReceiver : BroadcastReceiver() {
@@ -10,6 +11,7 @@     val onTimetableDownloadListeners: HashSet = HashSet()
     val onVmListeners: HashSet<OnVmListener> = HashSet()
 
     override fun onReceive(context: Context?, intent: Intent?) {
+        Log.i("Recv", "${intent?.action}")
         if (intent?.action == TimetableDownloader.ACTION_DOWNLOADED) {
             val result = intent.getStringExtra(TimetableDownloader.EXTRA_RESULT)
             for (listener in onTimetableDownloadListeners) {
@@ -18,13 +20,17 @@             }
         }
         if (intent?.action == VmClient.ACTION_DEPARTURES_CREATED) {
             val departures = intent.getStringArrayListExtra(VmClient.EXTRA_DEPARTURES).map { Departure.fromString(it) } as ArrayList<Departure>
+            val requester = intent.getStringExtra(VmClient.EXTRA_REQUESTER)
+            Log.i("VmRecv", "Got Vm for $requester")
             for (listener in onVmListeners) {
-                listener.onVm(departures)
+                listener.onVm(departures, requester)
             }
         }
         if (intent?.action == VmClient.ACTION_NO_DEPARTURES) {
+            val requester = intent.getStringExtra(VmClient.EXTRA_REQUESTER)
+            Log.i("VmRecv", "Got null for $requester")
             for (listener in onVmListeners) {
-                listener.onVm(null)
+                listener.onVm(null, requester)
             }
         }
     }
@@ -50,6 +56,6 @@         fun onTimetableDownload(result: String?)
     }
 
     interface OnVmListener {
-        fun onVm(vmDepartures: ArrayList<Departure>?)
+        fun onVm(vmDepartures: ArrayList<Departure>?, requester: String)
     }
 }
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt b/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt
index 86ab7c5b3c2becd0e5031c1af21e2930be46a83d..7a3dadf8b864f53b49addeddb997df0bd9c49cfc 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/VmClient.kt
@@ -2,6 +2,7 @@ package ml.adamsprogs.bimba
 
 import android.app.IntentService
 import android.content.Intent
+import android.util.Log
 import ml.adamsprogs.bimba.models.*
 import okhttp3.*
 import com.google.gson.Gson
@@ -15,13 +16,19 @@         val ACTION_DEPARTURES_CREATED = "ml.adamsprogs.bimba.departuresCreated"
         val ACTION_NO_DEPARTURES = "ml.adamsprogs.bimba.noVM"
         val EXTRA_STOP_SYMBOL = "stopSymbol"
         val EXTRA_LINE_NUMBER = "lineNumber"
+        val EXTRA_REQUESTER = "requester"
         val EXTRA_DEPARTURES = "departures"
     }
 
     override fun onHandleIntent(intent: Intent?) {
         if (intent != null) {
+            val requester = intent.getStringExtra(EXTRA_REQUESTER)
+
+            Log.i("VMClient", "starting vm for $requester")
+
             if (!NetworkStateReceiver.isNetworkAvailable(this)) {
-                sendNullResult()
+                Log.i("VMClient", "offline")
+                sendNullResult(requester)
                 return
             }
 
@@ -42,7 +49,8 @@             val responseBody: String?
             try {
                 responseBody = client.newCall(request).execute().body()?.string()
             } catch(e: IOException) {
-                sendNullResult()
+                Log.i("VMClient", "IO Err")
+                sendNullResult(requester)
                 return
             }
             val javaRootMapObject = Gson().fromJson(responseBody, HashMap::class.java)
@@ -64,26 +72,29 @@                             departureDay != todayDay, t["onStopPoint"] as Boolean)
                     departuresToday.add(departure)
                 }
             }
+            Log.i("VMClient", "Sending")
+            departuresToday.forEach {Log.i("VMClient", "send: $it")}
             if (departuresToday.isEmpty())
-                sendNullResult()
+                sendNullResult(requester)
             else
-                sendResult(departuresToday)
-
+                sendResult(departuresToday, requester)
         }
     }
 
-    private fun sendNullResult() {
+    private fun sendNullResult(requester: String) {
         val broadcastIntent = Intent()
         broadcastIntent.action = ACTION_NO_DEPARTURES
         broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
+        broadcastIntent.putExtra(EXTRA_REQUESTER, requester)
         sendBroadcast(broadcastIntent)
     }
 
-    private fun sendResult(departures: ArrayList<Departure>) {
+    private fun sendResult(departures: ArrayList<Departure>, requester: String) {
         val broadcastIntent = Intent()
         broadcastIntent.action = ACTION_DEPARTURES_CREATED
         broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
         broadcastIntent.putStringArrayListExtra(EXTRA_DEPARTURES, departures.map { it.toString() } as java.util.ArrayList<String>)
+        broadcastIntent.putExtra(EXTRA_REQUESTER, requester)
         sendBroadcast(broadcastIntent)
     }
 }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
index 321c32db86b8d30e9a51ab80bdd300df2d59dbd5..4f2f7d19ea02042e944d687c3025a1f936a988ad 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
@@ -148,9 +148,22 @@
     private fun createTimerTask() {
         timerTask = object : TimerTask() {
             override fun run() {
+                for (fav in favourites) {
+                    fav.registerOnVm(receiver)
+                    for (t in fav.timetables) {
+                        val symbol = timetable.getStopSymbol(t[Favourite.TAG_STOP]!!)
+                        val line = timetable.getLineNumber(t[Favourite.TAG_LINE]!!)
+                        val intent = Intent(context, VmClient::class.java)
+                        intent.putExtra(VmClient.EXTRA_STOP_SYMBOL, symbol)
+                        intent.putExtra(VmClient.EXTRA_LINE_NUMBER, line)
+                        intent.putExtra(VmClient.EXTRA_REQUESTER,
+                                "${fav.name};${t[Favourite.TAG_STOP]}${t[Favourite.TAG_LINE]}")
+                        context.startService(intent)
+                    }
+                }
+
                 runOnUiThread {
                     favouritesList.adapter.notifyDataSetChanged()
-                    //todo vm
                 }
             }
         }
@@ -163,6 +176,8 @@     }
 
     private fun prepareOnDownloadListener() {
         val filter = IntentFilter(TimetableDownloader.ACTION_DOWNLOADED)
+        filter.addAction(VmClient.ACTION_DEPARTURES_CREATED)
+        filter.addAction(VmClient.ACTION_NO_DEPARTURES)
         filter.addCategory(Intent.CATEGORY_DEFAULT)
         registerReceiver(receiver, filter)
         receiver.addOnTimetableDownloadListener(context as MessageReceiver.OnTimetableDownloadListener)




diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
index 3f1c27bd741334890ffc27d29a3015314e7d8b7c..3d8a2d320d9d10d1c84afa08d9e54022be81f941 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
@@ -22,6 +22,7 @@
     companion object {
         val EXTRA_STOP_ID = "stopId"
         val EXTRA_STOP_SYMBOL = "stopSymbol"
+        val REQUESTER_ID = "stopActivity"
     }
 
     private lateinit var stopId: String
@@ -51,7 +52,7 @@
         prepareOnDownloadListener()
 
         timetable = Timetable.getTimetable()
-        supportActionBar?.title = timetable.getStopName(stopId) ?: "Stop"
+        supportActionBar?.title = timetable.getStopName(stopId)
 
         viewPager = findViewById(R.id.container) as ViewPager
         tabLayout = findViewById(R.id.tabs) as TabLayout
@@ -96,6 +97,7 @@         timerTask = object : TimerTask() {
             override fun run() {
                 val vmIntent = Intent(context, VmClient::class.java)
                 vmIntent.putExtra(VmClient.EXTRA_STOP_SYMBOL, stopSymbol)
+                vmIntent.putExtra(VmClient.EXTRA_REQUESTER, REQUESTER_ID)
                 startService(vmIntent)
             }
         }
@@ -109,8 +111,8 @@         registerReceiver(receiver, filter)
         receiver.addOnVmListener(context as MessageReceiver.OnVmListener)
     }
 
-    override fun onVm(vmDepartures: ArrayList<Departure>?) {
-        if (timetableType == "departure") {
+    override fun onVm(vmDepartures: ArrayList<Departure>?, requester:String) {
+        if (timetableType == "departure" && requester == REQUESTER_ID) {
             val fullDepartures = Departure.createDepartures(stopId)
             if (vmDepartures != null) {
                 fullDepartures[today.getMode()] = vmDepartures
@@ -147,7 +149,7 @@         if (id == R.id.action_change_type) {
             if (timetableType == "departure") {
                 timetableType = "full"
                 item.icon = (ResourcesCompat.getDrawable(resources, R.drawable.ic_timetable_departure, this.theme))
-                sectionsPagerAdapter?.departures = timetable.getStopDepartures(stopId)!!
+                sectionsPagerAdapter?.departures = timetable.getStopDepartures(stopId)
                 sectionsPagerAdapter?.relativeTime = false
                 sectionsPagerAdapter?.notifyDataSetChanged()
                 timer.cancel()




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt
index 97a303f74d08250922cae1bc1c2d08b16a0ad609..a5d9aec9bc7885788f50b9708ed9522134122f12 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt
@@ -43,11 +43,11 @@             val departures = timetable.getStopDepartures(stopId)
             val moreDepartures = timetable.getStopDepartures(stopId)
             val rolledDepartures = HashMap<String, ArrayList<Departure>>()
 
-            for ((_, tomorrowDepartures) in moreDepartures!!) {
+            for ((_, tomorrowDepartures) in moreDepartures) {
                 tomorrowDepartures.forEach { it.tomorrow = true }
             }
 
-            for ((mode, _) in departures!!) {
+            for ((mode, _) in departures) {
                 rolledDepartures[mode] = (departures[mode] as ArrayList<Departure> +
                         moreDepartures[mode] as ArrayList<Departure>) as ArrayList<Departure>
                 rolledDepartures[mode] = filterDepartures(rolledDepartures[mode])
@@ -61,5 +61,17 @@             val array = string.split("|")
             return Departure(array[0], array[1], array[2], array[3] == "1", array[4], array[5],
                     array[6] == "true", array[7] == "true", array[8] == "true")
         }
+    }
+
+    fun timeTill(): Long {
+        val time = Calendar.getInstance()
+        time.set(Calendar.HOUR_OF_DAY, Integer.parseInt(this.time.split(":")[0]))
+        time.set(Calendar.MINUTE, Integer.parseInt(this.time.split(":")[1]))
+        time.set(Calendar.SECOND, 0)
+        time.set(Calendar.MILLISECOND, 0)
+        val now = Calendar.getInstance()
+        if (this.tomorrow)
+            time.add(Calendar.DAY_OF_MONTH, 1)
+        return (time.timeInMillis - now.timeInMillis) / (1000 * 60)
     }
 }
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt
index 976c01548a1079143e8f4ccbe5e8270fa4e1d462..828f537146df436aa4ec9c59e56ee1da81ad00e5 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt
@@ -3,15 +3,39 @@
 import android.os.Parcel
 import android.os.Parcelable
 import android.util.Log
+import ml.adamsprogs.bimba.MessageReceiver
 import ml.adamsprogs.bimba.getMode
 import java.util.*
 import kotlin.collections.ArrayList
 import kotlin.collections.HashMap
 
-class Favourite : Parcelable {
+class Favourite : Parcelable, MessageReceiver.OnVmListener {
+    override fun onVm(vmDepartures: ArrayList<Departure>?, requester: String) {
+        val requesterName = requester.split(";")[0]
+        var requesterTimetable: String
+        try {
+            requesterTimetable = requester.split(";")[1]
+        } catch (e: IndexOutOfBoundsException) {
+            requesterTimetable = ""
+        }
+        Log.i("VM", "got vm for $requesterName and my name is $name")
+        if (vmDepartures != null && requesterName == name) {
+            Log.i("VM", "so I’m adding")
+            vmDeparturesMap[requesterTimetable] = vmDepartures
+            this.vmDepartures = vmDeparturesMap.flatMap { it.value } as ArrayList<Departure>
+        }
+        Log.i("VM", "so I’m not adding")
+        filterVmDepartures()
+    }
+
+    private var isRegisteredOnVmListener: Boolean = false
     var name: String
+        private set
     var timetables: ArrayList<HashMap<String, String>>
+        private set
     private var oneDayDepartures: ArrayList<HashMap<String, ArrayList<Departure>>>? = null
+    private val vmDeparturesMap = HashMap<String, ArrayList<Departure>>()
+    private var vmDepartures = ArrayList<Departure>()
 
     constructor(parcel: Parcel) {
         val array = ArrayList<String>()
@@ -30,6 +54,7 @@
     constructor(name: String, timetables: ArrayList<HashMap<String, String>>) {
         this.name = name
         this.timetables = timetables
+
     }
 
     override fun describeContents(): Int {
@@ -48,19 +73,28 @@         get() = timetables.size
 
     var nextDeparture: Departure? = null
         get() {
-            if (timetables.isEmpty())
+            filterVmDepartures()
+            if (timetables.isEmpty() && vmDepartures.isEmpty())
                 return null
+
+            Log.i("FAV", "vmDeps is empty? ${vmDepartures.isEmpty()} so")
+            if (vmDepartures.isNotEmpty()) {
+                val d = vmDepartures.minBy { it.timeTill() }
+                Log.i("FAV", "using vm: ${d.toString()}")
+                return d
+            }
+
+            Log.i("FAV", "using offline")
+
             val twoDayDepartures = ArrayList<Departure>()
-            val now = Calendar.getInstance()
-            val departureTime = Calendar.getInstance()
-            val today = now.getMode()
+            val today = Calendar.getInstance().getMode()
             val tomorrowCal = Calendar.getInstance()
             tomorrowCal.add(Calendar.DAY_OF_MONTH, 1)
             val tomorrow = tomorrowCal.getMode()
 
             if (oneDayDepartures == null) {
                 oneDayDepartures = ArrayList<HashMap<String, ArrayList<Departure>>>()
-                timetables.mapTo(oneDayDepartures!!) { timetable.getStopDepartures(it[TAG_STOP] as String, it[TAG_LINE])!! }
+                timetables.mapTo(oneDayDepartures!!) { timetable.getStopDepartures(it[TAG_STOP] as String, it[TAG_LINE]) }
             }
 
             oneDayDepartures!!.forEach {
@@ -79,29 +113,35 @@
             if (twoDayDepartures.isEmpty())
                 return null
 
-            var minDeparture: Departure = twoDayDepartures[0]
-            var minInterval = 24 * 60L
-            for (departure in twoDayDepartures) {
-                departureTime.set(Calendar.HOUR_OF_DAY, Integer.parseInt(departure.time.split(":")[0]))
-                departureTime.set(Calendar.MINUTE, Integer.parseInt(departure.time.split(":")[1]))
-                if (departure.tomorrow)
-                    departureTime.add(Calendar.DAY_OF_MONTH, 1)
-                val interval = (departureTime.timeInMillis - now.timeInMillis) / (1000 * 60)
-                if (interval in 0..(minInterval - 1)) {
-                    minInterval = (departureTime.timeInMillis - now.timeInMillis) / (1000 * 60)
-                    minDeparture = departure
-                }
-            }
-
-            Log.i("preInterval", "$minInterval")
-            return minDeparture
+            return twoDayDepartures
+                    .filter { it.timeTill() >= 0 }
+                    .minBy { it.timeTill() }
         }
         private set
 
+    fun filterVmDepartures() {
+        this.vmDepartures
+                .filter { it.timeTill() < 0 }
+                .forEach { this.vmDepartures.remove(it) }
+    }
+
     fun delete(stop: String, line: String) {
         Log.i("ROW", "Favourite deleting $stop, $line")
         timetables.remove(timetables.find { it[TAG_STOP] == stop && it[TAG_LINE] == line })
         Log.i("ROW", timetables.toString())
+    }
+
+    fun registerOnVm(receiver: MessageReceiver) {
+        Log.i("FAV", "Shall register? ${!isRegisteredOnVmListener}")
+        if (!isRegisteredOnVmListener) {
+            Log.i("FAV", "registering")
+            receiver.addOnVmListener(this)
+            isRegisteredOnVmListener = true
+        }
+    }
+
+    fun rename(newName: String) {
+        name = newName
     }
 
     companion object CREATOR : Parcelable.Creator<Favourite> {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt
index 336c547c2784a8796b9a6a02c2840ecca6016eb0..5672e0a5d124d93edd93d69fe76ac873a0c78c2f 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteEditRowAdapter.kt
@@ -22,7 +22,7 @@                 favourite.timetables[position][Favourite.TAG_LINE]!!)
         holder?.rowTextView?.text = favouriteElement
         holder?.splitButton?.setOnClickListener {
             favourites.detach(favourite.name, favourite.timetables[position][Favourite.TAG_STOP]!!,
-                    favourite.timetables[position][Favourite.TAG_LINE]!!, favouriteElement!!)
+                    favourite.timetables[position][Favourite.TAG_LINE]!!, favouriteElement)
             favourite = favourites.favourites[favourite.name]!!
             notifyDataSetChanged()
         }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt
index 533ed3c316d3fbec75ca55c2163f07da656a54f9..2949ffb25807d7320416e2bf7247b6b6a8d35127 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt
@@ -8,7 +8,7 @@ import com.google.gson.JsonArray
 import com.google.gson.JsonObject
 
 
-class FavouriteStorage private constructor(context: Context) {
+class FavouriteStorage private constructor(context: Context) : Iterable<Favourite> {
     companion object {
         private var favouriteStorage: FavouriteStorage? = null
         fun getFavouriteStorage(context: Context? = null): FavouriteStorage {
@@ -23,6 +23,7 @@             } else
                 return favouriteStorage as FavouriteStorage
         }
     }
+
     val favourites = HashMap<String, Favourite>()
     val preferences: SharedPreferences = context.getSharedPreferences("ml.adamsprogs.bimba.prefs", Context.MODE_PRIVATE)
     val favouritesList: List<Favourite>
@@ -44,6 +45,8 @@             }
             favourites[name] = Favourite(name, timetables)
         }
     }
+
+    override fun iterator(): Iterator<Favourite> = favourites.values.iterator()
 
     fun has(name: String): Boolean = favourites.contains(name)
 
@@ -103,7 +106,7 @@         delete(name, stop, line)
     }
 
     fun merge(names: ArrayList<String>) {
-        if (names.size < 2 )
+        if (names.size < 2)
             return
         val newFavourite = Favourite(names[0], ArrayList<HashMap<String, String>>())
         for (name in names) {
@@ -117,7 +120,7 @@     }
 
     fun rename(oldName: String, newName: String) {
         val favourite = favourites[oldName] ?: return
-        favourite.name = newName
+        favourite.rename(newName)
         favourites.remove(oldName)
         favourites[newName] = favourite
         serialize()




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt
index 4d3a236492c6fab3f6506a271f6bfb7aabc8ae4e..be2da2c0ebedf4b2a452871c3a8e73ff3b937627 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt
@@ -3,14 +3,12 @@
 import android.app.Activity
 import android.content.Context
 import android.os.Build
-import android.support.v7.widget.CardView
+import android.support.v4.content.res.ResourcesCompat
+import android.support.v7.widget.*
 import android.support.v7.widget.PopupMenu
-import android.support.v7.widget.RecyclerView
 import android.util.Log
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
+import android.view.*
+import android.widget.*
 import ml.adamsprogs.bimba.R
 import android.view.LayoutInflater
 import java.util.*
@@ -61,17 +59,14 @@             }
             val nextDepartureText: String
             val nextDepartureLineText: String
             if (nextDeparture != null) {
-                val now = Calendar.getInstance()
-                val departureTime = Calendar.getInstance()
-                departureTime.set(Calendar.HOUR_OF_DAY, Integer.parseInt(nextDeparture.time.split(":")[0]))
-                departureTime.set(Calendar.MINUTE, Integer.parseInt(nextDeparture.time.split(":")[1]))
-                if (nextDeparture.tomorrow)
-                    departureTime.add(Calendar.DAY_OF_MONTH, 1)
-                val interval = ((departureTime.timeInMillis - now.timeInMillis) / (1000 * 60))
-                Log.i("Interval", "$interval")
-                nextDepartureText = context.getString(Declinator.decline(interval), interval.toString()) //fixme -1
+                Log.i("NEXT DEP", nextDeparture.toString())
+                val interval = nextDeparture.timeTill()
+                if (interval < 0)
+                    return@thread
+                nextDepartureText = context.getString(Declinator.decline(interval), interval.toString())
                 nextDepartureLineText = context.getString(R.string.departure_to_line, nextDeparture.line, nextDeparture.direction)
             } else {
+                //fixme too early
                 nextDepartureText = context.getString(R.string.no_next_departure)
                 nextDepartureLineText = ""
             }
@@ -83,6 +78,12 @@                 }
                 holder?.nameTextView?.text = favourite.name
                 holder?.timeTextView?.text = nextDepartureText
                 holder?.lineTextView?.text = nextDepartureLineText
+                if(nextDeparture!=null) {
+                    if (nextDeparture.vm)
+                        holder?.typeIcon?.setImageDrawable(ResourcesCompat.getDrawable(context.resources, R.drawable.ic_departure_vm, context.theme))
+                    else
+                        holder?.typeIcon?.setImageDrawable(ResourcesCompat.getDrawable(context.resources, R.drawable.ic_departure_timetable, context.theme))
+                }
                 holder?.moreButton?.setOnClickListener {
                     unSelect(holder.root, position)
                     val popup = PopupMenu(context, it)
@@ -173,6 +174,7 @@         val nameTextView = itemView.findViewById(R.id.favourite_name) as TextView
         val timeTextView = itemView.findViewById(R.id.favourite_time) as TextView
         val lineTextView = itemView.findViewById(R.id.favourite_line) as TextView
         val moreButton = itemView.findViewById(R.id.favourite_more_button) as ImageView
+        val typeIcon = itemView.findViewById(R.id.departureTypeIcon) as ImageView
     }
 
     interface OnMenuItemClickListener {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
index fb449ce4a9022308d23a7305aa12aeae0546c4cf..d77862ff8b8c28ad2712b5a33d985c280aa46963 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
@@ -14,7 +14,7 @@         val MODE_SATURDAYS = "saturdays"
         val MODE_SUNDAYS = "sundays"
         private var timetable: Timetable? = null
 
-        fun getTimetable(context: Context? = null, force: Boolean = false): Timetable{
+        fun getTimetable(context: Context? = null, force: Boolean = false): Timetable {
             if (timetable == null || force)
                 if (context != null) {
                     val db: SQLiteDatabase?
@@ -66,7 +66,7 @@         _stops = stops
         return stops
     }
 
-    fun getStopName(stopId: String): String? {
+    fun getStopName(stopId: String): String {
         val cursor = db.rawQuery("select name from nodes join stops on(stops.symbol = nodes.symbol) where id = ?;",
                 listOf(stopId).toTypedArray())
         val name: String
@@ -76,7 +76,25 @@         cursor.close()
         return name
     }
 
-    fun getStopDepartures(stopId: String, lineId: String? = null, tomorrow: Boolean = false): HashMap<String, ArrayList<Departure>>? {
+    fun getStopSymbol(stopId: String): String {
+        val cursor = db.rawQuery("select symbol||number from stops where id = ?", listOf(stopId).toTypedArray())
+        val symbol: String
+        cursor.moveToNext()
+        symbol = cursor.getString(0)
+        cursor.close()
+        return symbol
+    }
+
+    fun getLineNumber(lineId: String): String {
+        val cursor = db.rawQuery("select number from lines where id = ?", listOf(lineId).toTypedArray())
+        val number: String
+        cursor.moveToNext()
+        number = cursor.getString(0)
+        cursor.close()
+        return number
+    }
+
+    fun getStopDepartures(stopId: String, lineId: String? = null, tomorrow: Boolean = false): HashMap<String, ArrayList<Departure>> {
         val andLine: String
         if (lineId == null)
             andLine = ""
@@ -111,7 +129,7 @@         cursor.close()
         return lines
     }
 
-    fun getFavouriteElement(stop: String, line: String): String? {
+    fun getFavouriteElement(stop: String, line: String): String {
         val cursor = db.rawQuery("select name || ' (' || stops.symbol || stops.number || '): \n' " +
                 "|| lines.number || ' → ' || headsign from timetables join stops on (stops.id = stop_id) " +
                 "join lines on(lines.id = line_id) join nodes on(nodes.symbol = stops.symbol) where " +




diff --git a/app/src/main/res/layout/row_departure.xml b/app/src/main/res/layout/row_departure.xml
index d6b0a4371cce9c0c482bfb3c53e52a97724d6028..82ad610c4a49c3ee73231d57eddbc75f142d2815 100644
--- a/app/src/main/res/layout/row_departure.xml
+++ b/app/src/main/res/layout/row_departure.xml
@@ -1,57 +1,56 @@
 <?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    tools:layout_editor_absoluteY="25dp"
-    tools:layout_editor_absoluteX="0dp">
+    tools:layout_editor_absoluteX="0dp"
+    tools:layout_editor_absoluteY="25dp">
 
     <TextView
         android:id="@+id/lineNumber"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginBottom="8dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
         android:text=""
         android:textAppearance="@style/TextAppearance.AppCompat.Title"
-        app:layout_constraintStart_toStartOf="parent"
-        android:layout_marginStart="8dp"
-        app:layout_constraintTop_toTopOf="parent"
-        android:layout_marginTop="8dp"
         app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="8dp"
         app:layout_constraintEnd_toStartOf="@+id/departureTime"
-        android:layout_marginEnd="8dp" />
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
 
     <ImageView
         android:id="@+id/departureTypeIcon"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:layout_marginTop="8dp"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
-        android:contentDescription="@string/departure_type_icon_description" />
+        android:layout_marginBottom="16dp"
+        android:layout_marginEnd="16dp"
+        android:contentDescription="@string/departure_type_icon_description"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
 
     <TextView
         android:id="@+id/departureTime"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginStart="64dp"
+        android:layout_marginTop="8dp"
         android:text=""
         android:textAppearance="@style/TextAppearance.AppCompat.Headline"
-        android:layout_marginTop="8dp"
-        app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toEndOf="parent"
-        android:layout_marginStart="64dp" />
+        app:layout_constraintTop_toTopOf="parent" />
 
     <TextView
         android:id="@+id/departureDirection"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginBottom="8dp"
+        android:layout_marginStart="64dp"
         android:text=""
         app:layout_constraintBottom_toBottomOf="parent"
-        android:layout_marginBottom="8dp"
-        app:layout_constraintTop_toBottomOf="@+id/departureTime"
         app:layout_constraintStart_toEndOf="parent"
-        android:layout_marginStart="64dp" />
+        app:layout_constraintTop_toBottomOf="@+id/departureTime" />
 </android.support.constraint.ConstraintLayout>
\ No newline at end of file




diff --git a/app/src/main/res/layout/row_favourite.xml b/app/src/main/res/layout/row_favourite.xml
index e2cae1cdc02502bde3c53a7f88fe934c64c36796..977dae12323f363cc40bd7491a237c147cbee81a 100644
--- a/app/src/main/res/layout/row_favourite.xml
+++ b/app/src/main/res/layout/row_favourite.xml
@@ -51,6 +51,16 @@             tools:layout_editor_absoluteX="16dp"
             tools:layout_editor_absoluteY="78dp" />
 
         <ImageView
+            android:id="@+id/departureTypeIcon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="16dp"
+            android:layout_marginEnd="16dp"
+            android:contentDescription="@string/departure_type_icon_description"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent" />
+
+        <ImageView
             android:id="@+id/favourite_more_button"
             android:layout_width="24dp"
             android:layout_height="24dp"