Bimba.git

commit b96ea951adb4c6659f64668075b3e49eeb771c4d

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

faster favourites --- now use same code as stops

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


diff --git a/app/build.gradle b/app/build.gradle
index 32d9dd851397095766b302faa0bacf997d79bdc8..2e38408235a731b14219e2edd70096a7be39d188 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -38,6 +38,8 @@     implementation 'com.google.code.gson:gson:2.8.1'
     implementation 'com.squareup.okhttp3:okhttp:3.8.1'
     implementation 'com.github.ghost1372:Mzip-Android:0.4.0'
     implementation 'io.requery:sqlite-android:3.22.0'
+    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.22.5'
+    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.22.5"
     testImplementation 'junit:junit:4.12'
 }
 repositories {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/Declinator.kt b/app/src/main/java/ml/adamsprogs/bimba/Declinator.kt
index 27b3d806e0d1aa507e44faf18ebb8083263f600f..9f456f1352381e2bdf0b57333094c665dffea751 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/Declinator.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/Declinator.kt
@@ -2,14 +2,14 @@ package ml.adamsprogs.bimba
 
 class Declinator {
     companion object {
-        fun decline(number: Long): Int {
+        fun decline(number: Int): Int {
             return when {
-                number == 0L -> R.string.now
-                number % 10 == 0L -> R.string.departure_in__plural_genitive
-                number == 1L -> R.string.departure_in__singular_genitive
-                number in listOf<Long>(12,13,14) -> R.string.departure_in__plural_genitive
-                number % 10 in listOf<Long>(2, 3, 4) -> R.string.departure_in__plural_nominative
-                number % 10 in listOf<Long>(1,5,6,7,8,9) -> R.string.departure_in__plural_genitive
+                number == 0 -> R.string.now
+                number % 10 == 0 -> R.string.departure_in__plural_genitive
+                number == 1 -> R.string.departure_in__singular_genitive
+                number in listOf(12,13,14) -> R.string.departure_in__plural_genitive
+                number % 10 in listOf(2, 3, 4) -> R.string.departure_in__plural_nominative
+                number % 10 in listOf(1,5,6,7,8,9) -> R.string.departure_in__plural_genitive
                 else -> -1
             }
         }




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 af04bc1b7093bbfe79b6cc8dc371bffa983ef5c6..eb8293a96bbe4e7a59e9e6f42ff67e1618779f0c 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
@@ -24,7 +24,8 @@
 import com.arlib.floatingsearchview.FloatingSearchView
 import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
 
-//todo searchView integration
+//todo<p:1> searchView integration
+//todo something devours RAM
 class DashActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener,
         FavouritesAdapter.OnMenuItemClickListener, Favourite.OnVmPreparedListener,
         FavouritesAdapter.ViewHolder.OnClickListener {
@@ -145,7 +146,7 @@     }
 
     private fun filterSuggestions(newQuery: String) {
         thread {
-            val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(newQuery), true) } //todo sorted by similarity
+            val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(newQuery), true) } //todo<p:2> sorted by similarity
             runOnUiThread { searchView.swapSuggestions(newStops) }
         }
     }
@@ -299,7 +300,7 @@     }
 
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
         if (requestCode == REQUEST_EDIT_FAVOURITE) {
-            if (resultCode == Activity.RESULT_OK) { // todo change favourite content (shown) immediately
+            if (resultCode == Activity.RESULT_OK) { // todo change favourite content (shown) immediately [applies to other situations as well]
                 val name = data.getStringExtra(EditFavouriteActivity.EXTRA_NEW_NAME)
                 val positionBefore = data.getIntExtra(EditFavouriteActivity.EXTRA_POSITION_BEFORE, -1)
                 //adapter.favourites = favourites.favouritesList




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 56ab89ab5c2d3fb831c73d19e5d3e27827b22611..ef44e2ef84cdfb1f893105b565177592f11dfa05 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
@@ -30,11 +30,13 @@ import ml.adamsprogs.bimba.datasources.VmClient
 import ml.adamsprogs.bimba.getMode
 import ml.adamsprogs.bimba.models.gtfs.AgencyAndId
 import ml.adamsprogs.bimba.models.*
+import ml.adamsprogs.bimba.secondsAfterMidnight
 import java.util.*
 import kotlin.collections.ArrayList
 import kotlin.concurrent.thread
 
 //todo<p:1> on click show time (HH:MM)
+//todo does not show departures when favourite (force works, not by default)
 class StopActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener, MessageReceiver.OnVmListener, Favourite.OnVmPreparedListener {
 
     private var sectionsPagerAdapter: SectionsPagerAdapter? = null
@@ -105,9 +107,10 @@         }
     }
 
     private fun refreshAdapterFromStop() {
+        val now = Calendar.getInstance().secondsAfterMidnight()
         val departures = HashMap<AgencyAndId, List<Departure>>()
         if (this.vmDepartures.isNotEmpty()) {
-            departures[timetable.getServiceForToday()] = this.vmDepartures.flatMap { it.value }.sortedBy { it.timeTill(true) }
+            departures[timetable.getServiceForToday()] = this.vmDepartures.flatMap { it.value }.sortedBy { it.timeTill(now) }
             refreshAdapter(departures)
         } else {
             refreshAdapter(Departure.createDepartures(stopSegment!!.stop))
@@ -170,8 +173,10 @@             favourite!!.registerOnVm(receiver, context)
     }
 
     override fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID) {
+        println("OnVM")
         if (vmDepartures == null && this.vmDepartures.isEmpty() && hasDepartures) {
             if (ticked()) {
+                println("tick")
                 refreshAdapterFromStop()
             }
             return




diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/StopSpecifyActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/StopSpecifyActivity.kt
index 49a53ef03dfa1f546a5c110986c3249f43a6b52d..091fa87ccc6f69955ce5ea3d5be52f139a928760 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopSpecifyActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopSpecifyActivity.kt
@@ -15,7 +15,7 @@ import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.RecyclerView
 import android.view.LayoutInflater
 
-
+//todo in night dark on dark
 class StopSpecifyActivity : AppCompatActivity() {
 
     companion object {
@@ -42,7 +42,7 @@         setSupportActionBar(toolbar)
         supportActionBar?.title = name
     }
 
-    class ShedAdapter(val context: Context, val values: Map<AgencyAndId, Pair<String, Set<String>>>) :
+    class ShedAdapter(val context: Context, private val values: Map<AgencyAndId, Pair<String, Set<String>>>) :
             RecyclerView.Adapter<ShedAdapter.ViewHolder>() {
         override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
             val context = parent.context




diff --git a/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt b/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
index cad776b9fd5704eaa69c0979307298bacd3f6942..2221481db9e931417d6ecd74c40202347fe6d6d9 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
@@ -146,7 +146,7 @@
         parser.beginParsing(File(getSecondaryExternalFilesDir(), "gtfs_files/stop_times.txt"))
         var line: Array<String>? = null
         while ({ line = parser.parseNext(); line }() != null) {
-            val lineNumber = parser.context.currentLine()
+            val lineNumber = parser.appContext.currentLine()
             (tripsIndex[line!![0]] as ArrayList).add(lineNumber)
             (stopsIndex[line!![3]] as ArrayList).add(lineNumber)
             if (lineNumber % 10_300 == 0L)




diff --git a/app/src/main/java/ml/adamsprogs/bimba/extensions.kt b/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
index 67673a586af83fae70adb6197996172676bc7dea..936c1475dcc3dff606bfe123ed1fae3ad14e7b1f 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
@@ -1,5 +1,6 @@
 package ml.adamsprogs.bimba
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.os.Build
@@ -36,11 +37,11 @@ }
 
 const val ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
 
-fun calendarFromIso(iso: String): Calendar { // check
+@SuppressLint("SimpleDateFormat")
+fun calendarFromIso(iso: String): Calendar {
     val calendar = Calendar.getInstance()
     val dateFormat = SimpleDateFormat(ISO_8601_DATE_FORMAT)
     val date = dateFormat.parse(iso)
-    //date.hours = date.getHours() - 1 //fixme why?
     calendar.time = date
     return calendar
 }
@@ -79,4 +80,4 @@ internal fun Context.getSecondaryExternalFilesDir(): File {
     val dirs = this.getExternalFilesDirs(null)
     return dirs[0]
 //    return dirs[dirs.size - 1]
-}
\ No newline at end of file
+}




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 5824d99b9df4e6bfeb5cb8d05dbb0a34845f44da..9989a5a13126f8a19094985c47c289382071c70b 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt
@@ -1,14 +1,13 @@
 package ml.adamsprogs.bimba.models
 
-import ml.adamsprogs.bimba.rollTime
 import ml.adamsprogs.bimba.models.gtfs.AgencyAndId
 import ml.adamsprogs.bimba.safeSplit
+import ml.adamsprogs.bimba.secondsAfterMidnight
 import java.io.Serializable
 import java.util.*
 import kotlin.collections.ArrayList
 import kotlin.collections.HashMap
 
-//todo may show just departed as HH:MM
 data class Departure(val line: AgencyAndId, val mode: List<Int>, val time: Int, val lowFloor: Boolean, //time in seconds since midnight
                      val modification: List<String>, val headsign: String, val vm: Boolean = false,
                      var tomorrow: Boolean = false, val onStop: Boolean = false) {
@@ -27,13 +26,14 @@         return Departure.fromString(this.toString())
     }
 
     companion object {
-        private fun filterDepartures(departures: List<Departure>, relative: Boolean = true): Array<Serializable> {
+        private fun filterDepartures(departures: List<Departure>, relativeTo: Int = Calendar.getInstance().secondsAfterMidnight()): Array<Serializable> {
             val filtered = ArrayList<Departure>()
             val lines = HashMap<AgencyAndId, Int>()
-            val sortedDepartures = departures.sortedBy { it.timeTill(relative) }
+            val sortedDepartures = departures.sortedBy { it.timeTill(relativeTo) }
             for (departure in sortedDepartures) {
+                val timeTill = departure.timeTill(relativeTo)
                 var lineExistedTimes = lines[departure.line]
-                if (departure.timeTill(relative) >= 0 && lineExistedTimes ?: 0 < 3) {
+                if (timeTill >= 0 && lineExistedTimes ?: 0 < 3) {
                     lineExistedTimes = (lineExistedTimes ?: 0) + 1
                     lines[departure.line] = lineExistedTimes
                     filtered.add(departure)
@@ -57,7 +57,7 @@                 if (isFull as Boolean) {
                     @Suppress("UNCHECKED_CAST")
                     rolledDepartures[it] = filtered as List<Departure>
                 } else {
-                    val (filteredTomorrow, _) = filterDepartures(departures[it]!!, false)
+                    val (filteredTomorrow, _) = filterDepartures(departures[it]!!, 0)
                     val departuresTomorrow = ArrayList<Departure>()
                     @Suppress("UNCHECKED_CAST")
                     (filteredTomorrow as List<Departure>).forEach {
@@ -68,8 +68,9 @@                     }
                     val (result, _) =
                             @Suppress("UNCHECKED_CAST")
                             filterDepartures((filtered as List<Departure>) + departuresTomorrow)
+                    val now = Calendar.getInstance().secondsAfterMidnight()
                     @Suppress("UNCHECKED_CAST")
-                    rolledDepartures[it] = (result as List<Departure>).sortedBy { it.timeTill(true) }
+                    rolledDepartures[it] = (result as List<Departure>).sortedBy { it.timeTill(now) }
                 }
             }
             return rolledDepartures
@@ -88,14 +89,11 @@                     array[7] == "true", array[8] == "true")
         }
     }
 
-    fun timeTill(relative: Boolean = true): Long {
-        val time = Calendar.getInstance().rollTime(this.time)
-        var now = Calendar.getInstance()
-        if (!relative)
-            now = now.rollTime(0)
+    fun timeTill(relativeTo: Int): Int {
+        var time = this.time
         if (this.tomorrow)
-            time.add(Calendar.DAY_OF_MONTH, 1)
-        return (time.timeInMillis - now.timeInMillis) / (1000 * 60)
+            time += 24 * 60 * 60
+        return (time - relativeTo) / 60
     }
 
     val lineText: String = line.id




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt
index fef6828f9526fb7ae1ec403e3b4ef41d2b72df54..93ffa0f16a9f84693cc0a70e4706d11108356e98 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt
@@ -62,7 +62,7 @@         val departureTime = Calendar.getInstance().rollTime(departure.time)
         if (departure.tomorrow)
             departureTime.add(Calendar.DAY_OF_MONTH, 1)
 
-        val departureIn = (departureTime.timeInMillis - now.timeInMillis) / (1000 * 60)
+        val departureIn = ((departureTime.timeInMillis - now.timeInMillis) / (1000 * 60)).toInt()
         val timeString: String
 
         timeString = if (departureIn > 60 || departureIn < 0 || !relativeTime)




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 ddb83ca5ab2770ba195ebd500488fa3badb5f9eb..750f193d77c56b994749737190f24ecfe46bf5b5 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt
@@ -7,6 +7,7 @@ import android.os.Parcelable
 import ml.adamsprogs.bimba.MessageReceiver
 import ml.adamsprogs.bimba.datasources.VmClient
 import ml.adamsprogs.bimba.models.gtfs.AgencyAndId
+import ml.adamsprogs.bimba.secondsAfterMidnight
 import java.util.*
 import kotlin.collections.ArrayList
 import kotlin.collections.HashMap
@@ -64,9 +65,10 @@         dest?.writeParcelableArray(parcelableSegments, flags)
     }
 
     private fun filterVmDepartures() {
+        val now = Calendar.getInstance().secondsAfterMidnight()
         this.vmDepartures.forEach {
             val newVms = it.value
-                    .filter { it.timeTill(true) >= 0 }.sortedBy { it.timeTill() }
+                    .filter { it.timeTill(now) >= 0 }.sortedBy { it.timeTill(now) }
             this.vmDepartures[it.key] = newVms
         }
     }
@@ -121,6 +123,7 @@         }
     }
 
     fun nextDeparture(): Departure? {
+        val now = Calendar.getInstance().secondsAfterMidnight()
         filterVmDepartures()
         if (segments.isEmpty() && vmDepartures.isEmpty())
             return null
@@ -128,40 +131,34 @@
         if (vmDepartures.isNotEmpty()) {
             return vmDepartures.flatMap { it.value }
                     .minBy {
-                        it.timeTill(true)
+                        it.timeTill(now)
                     }
         }
 
-        val twoDayDepartures = nowDepartures()
-
-        if (twoDayDepartures.isEmpty())
-            return null
+        val full = fullTimetable()
+        /*println("full:")
+        full.forEach {
+            println("${it.key}:")
+            it.value.forEach {
+                println("\t$it")
+            }
+        }*/
 
-        return twoDayDepartures
-                .filter { it.timeTill(true) >= 0 }
-                .minBy { it.timeTill(true) }
-    }
+        val twoDayDepartures = Departure.rollDepartures(full)[timetable.getServiceForToday()]
 
-    private fun nowDepartures(): List<Departure> {
-        val today = timetable.getServiceForToday()
-        val tomorrowCal = Calendar.getInstance()
-        tomorrowCal.add(Calendar.DAY_OF_MONTH, 1)
-        val tomorrow = try {
-            timetable.getServiceForTomorrow()
-        } catch (e: IllegalArgumentException) {
-            -1
-        }
+        println("NextDep: is empty? ${twoDayDepartures?.isEmpty()}")
 
-        val departures = fullTimetable()
+        if (twoDayDepartures?.isEmpty() != false)
+            return null
 
-        val todayDepartures = departures[today]!!
-        val tomorrowDepartures = ArrayList<Departure>() /** todo as in {@link Departure.rollDeparture rollDeparture} **/
-        if (tomorrow != -1) {
-            departures[tomorrow]!!.mapTo(tomorrowDepartures) { it.copy() }
-            tomorrowDepartures.forEach { it.tomorrow = true }
+        println("NextDep: twoDays:")
+        twoDayDepartures.forEach {
+            println("\t$it")
         }
 
-        return todayDepartures + tomorrowDepartures
+        println("NextDep: ${twoDayDepartures[0]}")
+
+        return twoDayDepartures[0]
     }
 
     fun allDepartures(): Map<AgencyAndId, List<Departure>> {
@@ -179,11 +176,12 @@
     fun fullTimetable() = timetable.getStopDeparturesBySegments(segments)
 
     override fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID) {
+        val now = Calendar.getInstance().secondsAfterMidnight()
         if (segments.any { it.contains(plateId) }) {
             if (vmDepartures == null)
                 this.vmDepartures.remove(plateId)
             else
-                this.vmDepartures[plateId] = vmDepartures.sortedBy { it.timeTill() }
+                this.vmDepartures[plateId] = vmDepartures.sortedBy { it.timeTill(now) }
         }
         filterVmDepartures()
         //todo<p:1> think about tick




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 dd8b3a8b0b317c5234e6a68f30999ca0fbe39985..669568ef29c6b2cfe6325a7c23d70ac05130dae5 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouriteStorage.kt
@@ -15,7 +15,7 @@         private var favouriteStorage: FavouriteStorage? = null
         fun getFavouriteStorage(context: Context? = null): FavouriteStorage {
             return if (favouriteStorage == null) {
                 if (context == null)
-                    throw IllegalArgumentException("requested new storage context not given")
+                    throw IllegalArgumentException("requested new storage appContext not given")
                 else {
                     favouriteStorage = FavouriteStorage(context)
                     favouriteStorage as FavouriteStorage




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 1903991cebdc22a2fc2dc00723106fe6ac515cd7..597c578f95ae64e941145c04284bbc1dcb61a85d 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/FavouritesAdapter.kt
@@ -1,6 +1,5 @@
 package ml.adamsprogs.bimba.models
 
-import android.app.Activity
 import android.content.Context
 import android.support.v4.content.res.ResourcesCompat
 import android.support.v7.widget.*
@@ -10,19 +9,23 @@ import android.view.*
 import android.widget.*
 import ml.adamsprogs.bimba.R
 import android.view.LayoutInflater
+import kotlinx.coroutines.experimental.CommonPool
+import kotlinx.coroutines.experimental.android.UI
+import kotlinx.coroutines.experimental.async
+import kotlinx.coroutines.experimental.launch
 import java.util.*
-import kotlin.concurrent.thread
 import ml.adamsprogs.bimba.Declinator
+import ml.adamsprogs.bimba.secondsAfterMidnight
 
 
-class FavouritesAdapter(val context: Context, var favourites: FavouriteStorage,
+class FavouritesAdapter(val appContext: Context, var favourites: FavouriteStorage,
                         private val onMenuItemClickListener: OnMenuItemClickListener,
                         private val onClickListener: ViewHolder.OnClickListener) :
         RecyclerView.Adapter<FavouritesAdapter.ViewHolder>() {
 
     private val selectedItems = SparseBooleanArray()
 
-    fun isSelected(position: Int) = getSelectedItems().contains(position)
+    private fun isSelected(position: Int) = getSelectedItems().contains(position)
 
     fun toggleSelection(position: Int) {
         if (selectedItems.get(position, false)) {
@@ -52,51 +55,49 @@
     override fun getItemCount() = favourites.size
 
     override fun onBindViewHolder(holder: ViewHolder, position: Int) {
-        holder.selectedOverlay.visibility = if (isSelected(position)) View.VISIBLE else View.INVISIBLE
-
-        thread {
+        launch(UI) {
             val favourite = favourites[position]!!
-            val nextDeparture: Departure?
-            try {
-                nextDeparture = favourite.nextDeparture()
-            } catch (e: ConcurrentModificationException) {
-                return@thread
+
+            holder.selectedOverlay.visibility = if (isSelected(position)) View.VISIBLE else View.INVISIBLE
+            holder.moreButton.setOnClickListener {
+                val popup = PopupMenu(appContext, it)
+                val inflater = popup.menuInflater
+                popup.setOnMenuItemClickListener {
+                    when (it.itemId) {
+                        R.id.favourite_edit -> onMenuItemClickListener.edit(favourite.name)
+                        R.id.favourite_delete -> onMenuItemClickListener.delete(favourite.name)
+                        else -> false
+                    }
+                }
+                inflater.inflate(R.menu.favourite_actions, popup.menu)
+                popup.show()
             }
+
+            val nextDeparture = async(CommonPool) {
+                favourite.nextDeparture()
+            }.await()
+
             val nextDepartureText: String
             val nextDepartureLineText: String
             if (nextDeparture != null) {
-                val interval = nextDeparture.timeTill(true)
-                if (interval < 0)
-                    return@thread
-                nextDepartureText = context.getString(Declinator.decline(interval), interval.toString())
-                nextDepartureLineText = context.getString(R.string.departure_to_line, nextDeparture.line, nextDeparture.headsign)
+                val interval = nextDeparture.timeTill(Calendar.getInstance().secondsAfterMidnight())
+                nextDepartureLineText = appContext.getString(R.string.departure_to_line, nextDeparture.line, nextDeparture.headsign)
+                nextDepartureText = if (interval < 0)
+                    appContext.getString(R.string.just_departed)
+                else
+                    appContext.getString(Declinator.decline(interval), interval.toString())
             } else {
-                nextDepartureText = context.getString(R.string.no_next_departure)
+                nextDepartureText = appContext.getString(R.string.no_next_departure)
                 nextDepartureLineText = ""
             }
-            (context as Activity).runOnUiThread {
-                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 {
-                    val popup = PopupMenu(context, it)
-                    val inflater = popup.menuInflater
-                    popup.setOnMenuItemClickListener {
-                        when (it.itemId) {
-                            R.id.favourite_edit -> onMenuItemClickListener.edit(favourite.name)
-                            R.id.favourite_delete -> onMenuItemClickListener.delete(favourite.name)
-                            else -> false
-                        }
-                    }
-                    inflater.inflate(R.menu.favourite_actions, popup.menu)
-                    popup.show()
-                }
+            holder.nameTextView.text = favourite.name
+            holder.timeTextView.text = nextDepartureText
+            holder.lineTextView.text = nextDepartureLineText
+            if (nextDeparture != null) {
+                if (nextDeparture.vm)
+                    holder.typeIcon.setImageDrawable(ResourcesCompat.getDrawable(appContext.resources, R.drawable.ic_departure_vm, appContext.theme))
+                else
+                    holder.typeIcon.setImageDrawable(ResourcesCompat.getDrawable(appContext.resources, R.drawable.ic_departure_timetable, appContext.theme))
             }
         }
     }




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 9bac859e4e93f8dd60b4cdd8e4017e9371238983..a4b88546cd37d9fb2df9f599cd53f2d23b851b61 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
@@ -24,7 +24,7 @@                     val dbFile = File(timetable!!.filesDir, "timetable.db")
                     timetable!!.db = SQLiteDatabase.openDatabase(dbFile.path, null, SQLiteDatabase.OPEN_READONLY)
                     timetable!!
                 } else
-                    throw IllegalArgumentException("new timetable requested and no context given")
+                    throw IllegalArgumentException("new timetable requested and no appContext given")
             else
                 timetable!!
         }




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 48f82d3da3b2f187fe4b897dc73c85a31c89ee6a..42ffd2a2b02df0482f2b00767b902ea66d809e34 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -81,4 +81,5 @@     Fro
     <string name="timetable_validity_finished">Timetable validity has ended. Connect to the Internet to download a new one in order to continue.</string>
     <string name="timetable_validity_today">Timetable validity ends today.</string>
     <string name="timetable_validity_tomorrow">Timetable validity ends tomorrow.</string>
+    <string name="just_departed">Just departed</string>
 </resources>




diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 2b4277b945fb4c63257f0851ee51482a2a822b1c..e9534bf4467d28c55290d78ef6b12726781c273c 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -67,4 +67,5 @@     Her
     <string name="timetable_validity_today">Fahrplan gilt nur bis heute.</string>
     <string name="timetable_validity_finished">Die Gültigkeit des Zeitplans ist beendet. Verbind mit dem Internet, um eine neue herunterzuladen und um fortzufahren.</string>
     <string name="timetable_validity_tomorrow">Fahrplan gilt nur bis morgen.</string>
+    <string name="just_departed">Gerade gegangen</string>
 </resources>




diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 689856d152ff3c9a9845abb8d6e379666da1e02e..dff70c86c95fb00f7f973232d2766e713e55a31c 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -65,4 +65,5 @@     Indietro
     <string name="timetable_validity_today">L’orario è valido solo fino ad oggi.</string>
     <string name="timetable_validity_finished">"La validità dell’orario è terminata. Connetti a Internet per scaricarne uno nuovo e continuare. "</string>
     <string name="timetable_validity_tomorrow">L’orario è valido solo fino a domani.</string>
+    <string name="just_departed">Appena partito</string>
 </resources>




diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index b01fe4826ede75950ac49d588fd6b78eda161c82..2871fa773db94ca328522d1a8b71a9e06e5c3921 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -67,4 +67,5 @@     Z powrotem
     <string name="timetable_validity_today">Rozkład obowiązuje tylko do dzisiaj.</string>
     <string name="timetable_validity_finished">Rozkład przestał obowiązywać. Połącz się z Internetem, aby pobrać nowy i kontynuować.</string>
     <string name="timetable_validity_tomorrow">Rozkład obowiązuje tylko do jutra.</string>
+    <string name="just_departed">Właśnie odjechał</string>
 </resources>
\ No newline at end of file




diff --git a/gradle.properties b/gradle.properties
index aac7c9b4614ccfde6c721f24994cf30885a791d0..033d059b45874e6886b917294a6e1f97371b37f3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,6 +10,7 @@
 # Specifies the JVM arguments used for the daemon process.
 # The setting is particularly useful for tweaking memory settings.
 org.gradle.jvmargs=-Xmx1536m
+kotlin.coroutines=enable
 
 # When configured, Gradle will run in incubating parallel mode.
 # This option should only be used with decoupled projects. More details, visit