Bimba.git

commit 567e584a38a597bbe86bd69f6672ca3c3deb0e2f

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

Merge branch 'v2.0' into develop

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


diff --git a/app/build.gradle b/app/build.gradle
index 94a3c010062455ec443d5f3d5ca0d3ec3df3b65b..60d7ff4dd1cc7f634f2ea63bade2a44142788dd3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@     defaultConfig {
         applicationId "ml.adamsprogs.bimba"
         minSdkVersion 19
         targetSdkVersion 27
-        versionCode 14
+        versionCode 15
         versionName "2.0"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
         vectorDrawables.useSupportLibrary = true




diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2ea63538f98ed731cd5d77c8791694fa2fc2c7cc..e0b120550519b6a66af16dd4c18b7fe0c5606fcc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,6 +9,7 @@
     <application
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
+        android:roundIcon="@mipmap/ic_launcher_round"
         android:label="@string/app_name"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">




diff --git a/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt b/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt
index c40abec7cc2d1edd1e714a8a4f24dbe6afb6d577..faedcc757de71be595d9cd42158d306e3d44964d 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/MessageReceiver.kt
@@ -32,8 +32,9 @@         if (intent?.action == VmService.ACTION_READY) {
             val departures = intent.getStringArrayListExtra(VmService.EXTRA_DEPARTURES)?.map { Departure.fromString(it) }?.toSet()
             val plateId = intent.getSerializableExtra(VmService.EXTRA_PLATE_ID) as Plate.ID?
             val stopCode = intent.getSerializableExtra(VmService.EXTRA_STOP_CODE) as String
+            val code = intent.getIntExtra(VmService.EXTRA_CODE, 0)
             for (listener in onVmListeners) {
-                listener.onVm(departures, plateId, stopCode)
+                listener.onVm(departures, plateId, stopCode, code)
             }
         }
     }
@@ -59,6 +60,6 @@         fun onTimetableDownload(result: String?)
     }
 
     interface OnVmListener {
-        fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID?, stopCode: String)
+        fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID?, stopCode: String, code: Int)
     }
 }
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/ProviderProxy.kt b/app/src/main/java/ml/adamsprogs/bimba/ProviderProxy.kt
index 32dddcd358b1801b29be9440b9a0c0322cdb8460..45a60fc90e45adb61e3dd09c5a5ed12bbe9b7059 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/ProviderProxy.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/ProviderProxy.kt
@@ -205,7 +205,7 @@         return timetable.getServiceFirstDay(service)
     }
 
     interface OnDeparturesReadyListener {
-        fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?)
+        fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?, code: Int)
     }
 
     inner class Request(private val listener: OnDeparturesReadyListener, private val segments: Set<StopSegment>) : MessageReceiver.OnVmListener {
@@ -221,20 +221,24 @@                 cache = constructSegmentDepartures(segments)
             }
         }
 
-        override fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID?, stopCode: String) {
+        override fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID?, stopCode: String, code: Int) {
             launch(UI) {
+                if ((plateId == null || vmDepartures == null) and (timetable.isEmpty())) {
+                    listener.onDeparturesReady(emptyList(), null, code)
+                    return@launch
+                }
                 if (plateId == null) {
-                    listener.onDeparturesReady(filterDepartures(cache!!.await()), null)
+                    listener.onDeparturesReady(filterDepartures(cache!!.await()), null, code)
                 } else {
                     if (segments.any { plateId in it }) {
                         if (vmDepartures != null) {
-                            listener.onDeparturesReady(vmDepartures.toList(), plateId)
+                            listener.onDeparturesReady(vmDepartures.toList(), plateId, code)
                             if (plateId !in receivedPlates)
                                 receivedPlates.add(plateId)
                         } else {
                             receivedPlates.remove(plateId)
                             if (receivedPlates.isEmpty()) {
-                                listener.onDeparturesReady(filterDepartures(cache!!.await()), null)
+                                listener.onDeparturesReady(filterDepartures(cache!!.await()), null, code)
                             }
                         }
                     }




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 9320e22aef6596e5757970c7ba44d98b0c0afcf8..e29da9681bc17c52d83898bf9589e5371d4c6a6a 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
@@ -251,8 +251,9 @@         favouritesList.itemAnimator = DefaultItemAnimator()
         favouritesList.layoutManager = layoutManager
     }
 
-    override fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?) {
+    override fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?, code: Int) {
         favouritesList.adapter.notifyDataSetChanged()
+        showError(drawer_layout, code, this)
     }
 
     private fun getSuggestions() {
@@ -298,7 +299,7 @@     }
 
     override fun onTimetableDownload(result: String?) {
         val message: String = when (result) {
-            TimetableDownloader.RESULT_NO_CONNECTIVITY -> getString(R.string.no_connectivity)
+            TimetableDownloader.RESULT_NO_CONNECTIVITY -> getString(R.string.no_connectivity_cant_update)
             TimetableDownloader.RESULT_UP_TO_DATE -> getString(R.string.timetable_up_to_date)
             TimetableDownloader.RESULT_FINISHED -> getString(R.string.timetable_downloaded)
             else -> getString(R.string.error_try_later)
@@ -333,6 +334,10 @@                     val positionAfter = favourites.indexOf(name)
                     favouritesList.adapter.notifyItemChanged(positionBefore)
                     favouritesList.adapter.notifyItemMoved(positionBefore, positionAfter)
                 }
+                adapter[name]?.let {
+                    it.unsubscribeFromDepartures(context)
+                    it.subscribeForDepartures(this, context)
+                }
             }
         }
     }
@@ -403,12 +408,19 @@             return when (item.itemId) {
                 R.id.action_merge -> {
                     val selectedPositions = adapter.getSelectedItems()
                     val selectedNames = selectedPositions.map { favourites[it]?.name }.filter { it != null }.map { it!! }
-                    favourites.merge(selectedNames, this@DashActivity)
 
-                    adapter.notifyItemChanged(selectedPositions.min()!!)
-                    (1 until selectedPositions.size).forEach {
-                        adapter.notifyItemRemoved(it)
+                    (1 until selectedNames.size).forEach {
+                        selectedNames[it].let { name ->
+                            adapter.notifyItemRemoved(adapter.indexOf(name))
+                            adapter[name]?.unsubscribeFromDepartures(context)
+                        }
                     }
+                    favourites.merge(selectedNames, context)
+                    adapter[selectedNames[0]]?.let {
+                        it.unsubscribeFromDepartures(context)
+                        it.subscribeForDepartures(this@DashActivity, context)
+                    }
+                    adapter.notifyItemChanged(adapter.indexOf(selectedNames[0]))
 
                     clearSelection()
                     true




diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt
index 372f6448099ab199590f3e2e2241039aff3c3af1..d5c812f0e1dccc101297f852d23cd0756d39f300 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/EditFavouriteActivity.kt
@@ -42,7 +42,7 @@         val layoutManager = LinearLayoutManager(this)
         recyclerView!!.layoutManager = layoutManager
         val dividerItemDecoration = DividerItemDecoration(this, layoutManager.orientation)
         recyclerView.addItemDecoration(dividerItemDecoration)
-        recyclerView.adapter = FavouriteEditRowAdapter(favourite!!)
+        recyclerView.adapter = FavouriteEditRowAdapter(favourite!!, favourite_edit_loading, favourite_edit_list)
         setSupportActionBar(toolbar)
         supportActionBar?.title = getString(R.string.edit_favourite_title, favourite!!.name)
         nameEdit = favourite_name_edit




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 b6dbb3eeab4dbf3ca1b8ef2bd3077c7276377fd9..8aeed16739529be5f2f5f927660fa12f76fd659f 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
@@ -137,7 +137,8 @@         } else
             favourite!!.subscribeForDepartures(this, context)
     }
 
-    override fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?) {
+    override fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?, code: Int) {
+        showError(stop_layout, code, this)
         if (plateId == null) {
             this.departures.clear()
             this.departures[Plate.ID.dummy] = departures
@@ -164,7 +165,7 @@     }
 
     override fun onTimetableDownload(result: String?) {
         val message: String = when (result) {
-            TimetableDownloader.RESULT_NO_CONNECTIVITY -> getString(R.string.no_connectivity)
+            TimetableDownloader.RESULT_NO_CONNECTIVITY -> getString(R.string.no_connectivity_cant_update)
             TimetableDownloader.RESULT_UP_TO_DATE -> getString(R.string.timetable_up_to_date)
             TimetableDownloader.RESULT_FINISHED -> getString(R.string.timetable_downloaded)
             else -> getString(R.string.error_try_later)




diff --git a/app/src/main/java/ml/adamsprogs/bimba/collections/FavouriteStorage.kt b/app/src/main/java/ml/adamsprogs/bimba/collections/FavouriteStorage.kt
index d937e039aa9cc926d197f7bebe913883845281a5..b7d96b89b3fd738796ade8db57b8539e794ba59e 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/collections/FavouriteStorage.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/collections/FavouriteStorage.kt
@@ -29,28 +29,23 @@     private val preferences: SharedPreferences = context.getSharedPreferences("ml.adamsprogs.bimba.prefs", Context.MODE_PRIVATE)
 
     init {
         val favouritesString = preferences.getString("favourites", "{}")
-        val favouritesMap = Gson().fromJson(favouritesString, JsonObject::class.java)
-        for ((name, jsonTimetables) in favouritesMap.entrySet()) {
-            val timetables = HashSet<StopSegment>()
-            jsonTimetables.asJsonArray.mapTo(timetables) {
-                val stopSegment = StopSegment(it.asJsonObject["stop"].asString, null)
-                val plates = it.asJsonObject["plates"].let { jsonPlates ->
-                    if (jsonPlates == null || jsonPlates.isJsonNull)
+        JsonParser().parse(favouritesString).asJsonObject.entrySet().forEach { (name, timetables) ->
+            timetables.asJsonArray.map {
+                val plates = it.asJsonObject["plates"].let {
+                    if (it == null || it.isJsonNull)
                         null
                     else {
-                        HashSet<Plate.ID>().apply {
-                            jsonPlates.asJsonArray.map {
-                                Plate.ID(it.asJsonObject["line"].asString,
-                                        it.asJsonObject["stop"].asString,
-                                        it.asJsonObject["headsign"].asString)
+                        it.asJsonArray.map {
+                            it.asJsonObject.let {
+                                Plate.ID(it["line"].asString, it["stop"].asString, it["headsign"].asString)
                             }
-                        }
+                        }.toHashSet()
                     }
                 }
-                stopSegment.plates = plates
-                stopSegment
+                StopSegment(it.asJsonObject["stop"].asString, plates)
+            }.toHashSet().let {
+                favourites[name] = Favourite(name, it, context)
             }
-            favourites[name] = Favourite(name, timetables, context)
             positionIndex.add(name)
         }
     }
@@ -121,7 +116,6 @@         val favouritesString = Gson().toJson(rootObject)
         val editor = preferences.edit()
         editor.putString("favourites", favouritesString)
         editor.apply()
-
     }
 
     fun merge(names: List<String>, context: Context) {
@@ -168,6 +162,11 @@     }
 
     operator fun get(position: Int): Favourite? {
         return favourites[positionIndex[position]]
+    }
+
+    operator fun set(name: String, value: Favourite) {
+        favourites[name] = value
+        serialize()
     }
 
     fun indexOf(name: String): Int {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/datasources/VmClient.kt b/app/src/main/java/ml/adamsprogs/bimba/datasources/VmClient.kt
index af652ab3dad6fec82a7bb93d85733aac2cc10f56..51f7ceb59d2c1d06b955e977950e04c463317fe8 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/datasources/VmClient.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/datasources/VmClient.kt
@@ -24,7 +24,7 @@         }
     }
 
     suspend fun getSheds(name: String): Map<String, Set<String>> {
-        val response = makeRequest("getBollardsByStopPoint", """{"name": "$name"}""")
+        val (_, response) = makeRequest("getBollardsByStopPoint", """{"name": "$name"}""")
         if (!response.has("success"))
             return emptyMap()
         val rootObject = response["success"].asJsonObject["bollards"].asJsonArray
@@ -55,7 +55,7 @@         }.toSet()
     }*/
 
     suspend fun getStops(pattern: String): List<StopSuggestion> {
-        val response = withContext(DefaultDispatcher) {
+        val (_, response) = withContext(DefaultDispatcher) {
             makeRequest("getStopPoints", """{"pattern": "$pattern"}""")
         }
 
@@ -74,9 +74,9 @@
         return names.map { StopSuggestion(it, "", "") }
     }
 
-    suspend fun makeRequest(method: String, data: String): JsonObject {
+    suspend fun makeRequest(method: String, data: String): Pair<Int, JsonObject> {
         if (!NetworkStateReceiver.isNetworkAvailable())
-            return JsonObject()
+            return Pair(0, JsonObject())
 
         val client = OkHttpClient()
         val url = "http://www.peka.poznan.pl/vm/method.vm?ts=${Calendar.getInstance().timeInMillis}"
@@ -88,24 +88,28 @@                 .post(body)
                 .build()
 
 
-        val responseBody: String?
+        var responseBody: String? = null
+        var responseCode = 0
         try {
-            responseBody = withContext(CommonPool) {
-                client.newCall(request).execute().body()?.string()
+            withContext(CommonPool) {
+                client.newCall(request).execute().let {
+                    responseCode = it.code()
+                    responseBody = it.body()?.string()
+                }
             }
         } catch (e: IOException) {
-            return JsonObject()
+            return Pair(0, JsonObject())
         }
 
         return try {
-            Gson().fromJson(responseBody, JsonObject::class.java)
+            Pair(responseCode, Gson().fromJson(responseBody, JsonObject::class.java))
         } catch (e: JsonSyntaxException) {
-            JsonObject()
+            Pair(responseCode, JsonObject())
         }
     }
 
     suspend fun getName(symbol: String): String? {
-        val timesResponse = withContext(DefaultDispatcher) {
+        val (_, timesResponse) = withContext(DefaultDispatcher) {
             makeRequest("getTimes", """{"symbol": "$symbol"}""")
         }
         if (!timesResponse.has("success"))
@@ -116,7 +120,7 @@     }
 
     suspend fun getDirections(symbol: String): StopSegment? {
         val name = getName(symbol)
-        val directionsResponse = makeRequest("getBollardsByStopPoint", """{"name": "$name"}""")
+        val (_, directionsResponse) = makeRequest("getBollardsByStopPoint", """{"name": "$name"}""")
 
         if (!directionsResponse.has("success"))
             return null




diff --git a/app/src/main/java/ml/adamsprogs/bimba/datasources/VmService.kt b/app/src/main/java/ml/adamsprogs/bimba/datasources/VmService.kt
index 710aa49f6b3ad7a7f661864ea4231306aa403f20..82e4f38941d51829f3e42771115968d48869b25a 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/datasources/VmService.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/datasources/VmService.kt
@@ -20,6 +20,7 @@         const val ACTION_READY = "ml.adamsprogs.bimba.action.vm.ready"
         const val EXTRA_DEPARTURES = "ml.adamsprogs.bimba.extra.vm.departures"
         const val EXTRA_PLATE_ID = "ml.adamsprogs.bimba.extra.vm.plate"
         const val EXTRA_STOP_CODE = "ml.adamsprogs.bimba.extra.vm.stop"
+        const val EXTRA_CODE = "ml.adamsprogs.bimba.extra.vm.code"
         const val TICK_6_ZINA_TIM = 12500L
         const val TICK_6_ZINA_TIM_WITH_MARGIN = TICK_6_ZINA_TIM * 3 / 4
     }
@@ -76,9 +77,10 @@         return START_STICKY
     }
 
     private fun cleanRequests() {
-        requests.forEach {
-            if (it.value <= 0)
-                requests.remove(it.key)
+        val newRequests = requests.filter { it.value > 0 }
+        requests.clear()
+        newRequests.forEach {
+            requests[it.key] = it.value
         }
     }
 
@@ -117,14 +119,14 @@
     private suspend fun downloadVM(stopCode: String) {
         if (!NetworkStateReceiver.isNetworkAvailable()) {
             vms[stopCode] = emptySet()
-            sendResult(stopCode, null, null)
+            sendResult(stopCode, null, null, 0)
             return
         }
 
-        val javaRootMapObject = VmClient.getVmClient().makeRequest("getTimes", """{"symbol": "$stopCode"}""")
+        val (code, javaRootMapObject) = VmClient.getVmClient().makeRequest("getTimes", """{"symbol": "$stopCode"}""")
 
         if (!javaRootMapObject.has("success")) {
-            sendResult(stopCode, null, null)
+            sendResult(stopCode, null, null, code)
             return
         }
 
@@ -173,12 +175,13 @@         }
 
     }
 
-    private fun sendResult(stopCode: String, plateId: Plate.ID?, departures: HashSet<Departure>?) {
+    private fun sendResult(stopCode: String, plateId: Plate.ID?, departures: HashSet<Departure>?, code: Int = 200) {
         val broadcastIntent = Intent()
         broadcastIntent.action = ACTION_READY
         broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT)
         if (departures != null)
             broadcastIntent.putStringArrayListExtra(EXTRA_DEPARTURES, departures.map { it.toString() } as ArrayList)
+        broadcastIntent.putExtra(EXTRA_CODE, code)
         broadcastIntent.putExtra(EXTRA_PLATE_ID, plateId)
         broadcastIntent.putExtra(EXTRA_STOP_CODE, stopCode)
         sendBroadcast(broadcastIntent)




diff --git a/app/src/main/java/ml/adamsprogs/bimba/extensions.kt b/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
index a349dc85e76661e7745d7245c11cf93a3d450e75..9b51907278628c82c89e90b50b4f7b8c2bc241a9 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/extensions.kt
@@ -4,7 +4,9 @@ import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.os.Build
+import android.support.design.widget.Snackbar
 import android.text.format.DateFormat
+import android.view.View
 import ml.adamsprogs.bimba.activities.StopActivity
 import java.io.*
 import java.text.SimpleDateFormat
@@ -126,4 +128,14 @@         val time = timeFormat.format(this.time)
         "$date, $time"
     } else
         date
+}
+
+fun showError(view: View, code: Int, context: Context) {
+    val message = when {
+        code == 0 -> context.getString(R.string.no_connectivity)
+        (code >= 500) and (code < 600) -> context.getString(R.string.server_error)
+        else -> ""
+    }
+    if (message != "")
+        Snackbar.make(view, message, Snackbar.LENGTH_LONG).show()
 }
\ 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 8e2a99c5f289cabf1caacd451ff3bd91cb26d5d1..ee3c5ab21c3d525f85f3e52b59ab83c33f560a88 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Favourite.kt
@@ -16,7 +16,7 @@         private set
     var segments: HashSet<StopSegment>
         private set
     private var fullDepartures: Map<String, List<Departure>> = HashMap()
-    private var cache: List<Departure> = ArrayList()
+    private val cache = HashMap<Plate.ID, List<Departure>>()
     private var listenerId = ""
 
     val size
@@ -121,7 +121,12 @@     fun nextDeparture() =
             if (cache.isEmpty())
                 null
             else
-                cache.sortedBy { it.time }[0]
+                cache.flatMap { it.value }.let {
+                    if (it.isEmpty())
+                        null
+                    else
+                        it.sortedBy { it.time }[0]
+                }
 
 
     fun fullTimetable(): Map<String, List<Departure>> {
@@ -145,9 +150,15 @@         listenerId = providerProxy.subscribeForDepartures(segments, this, context)
         return listenerId
     }
 
-    override fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?) {
-        cache = departures
-        listener.onDeparturesReady(departures, plateId)
+    override fun onDeparturesReady(departures: List<Departure>, plateId: Plate.ID?, code: Int) {
+        if (plateId == null) {
+            cache.clear()
+            cache[Plate.ID.dummy] = departures
+        } else {
+            cache.remove(Plate.ID.dummy)
+            cache[plateId] = departures
+        }
+        listener.onDeparturesReady(departures, plateId, code)
     }
 
     fun unsubscribeFromDepartures(context: Context) {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/StopSegment.kt b/app/src/main/java/ml/adamsprogs/bimba/models/StopSegment.kt
index 50f2fb4860b39f77025541cb341a46a18ab93e2c..e6ef8860315c2d2aca5a481267fb924954bf9ceb 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/StopSegment.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/StopSegment.kt
@@ -65,7 +65,9 @@
     fun remove(plateId: Plate.ID): Boolean {
         if (plates == null)
             return false
-        return (plates as HashSet).remove(plateId)
+
+        plates = plates!!.asSequence().filter { it != plateId }.toSet()
+        return true
     }
 
     override fun toString(): String {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/adapters/FavouriteEditRowAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/adapters/FavouriteEditRowAdapter.kt
index d9a29e8872e4a7f99c68c282ef20cdf823791fb4..cb78df9d44a97addec4f60b2c1539551311584aa 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/adapters/FavouriteEditRowAdapter.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/adapters/FavouriteEditRowAdapter.kt
@@ -18,46 +18,63 @@ import ml.adamsprogs.bimba.models.Plate
 import ml.adamsprogs.bimba.models.StopSegment
 
 
-class FavouriteEditRowAdapter(private var favourite: Favourite) :
+class FavouriteEditRowAdapter(private var favourite: Favourite, private val loadingView: View, private val listView: View) :
         RecyclerView.Adapter<FavouriteEditRowAdapter.ViewHolder>() {
 
     private val segments = HashMap<String, StopSegment>()
     private val providerProxy = ProviderProxy()
+    private val favourites = FavouriteStorage.getFavouriteStorage()
+    private val platesList = ArrayList<Plate.ID>()
+    private val namesList = HashMap<Plate.ID, String>()
 
     init {
         launch(UI) {
             withContext(DefaultDispatcher) {
                 favourite.segments.forEach {
-                    segments[it.stop] = providerProxy.fillStopSegment(it) ?: it
+                    if (it.plates == null) {
+                        (providerProxy.fillStopSegment(it) ?: it).let { segment ->
+                            segments[segment.stop] = segment
+                            it.plates = segment.plates
+                        }
+                    } else {
+                        segments[it.stop] = it
+                    }
+                }
+                favourites[favourite.name] = favourite
+
+                segments.flatMap {
+                    it.value.plates ?: emptyList<Plate.ID>()
+                }.sortedBy { "${it.line}${it.stop}" }.forEach {
+                    platesList.add(it)
+                    namesList[it] = providerProxy.getStopName(it.stop).let { name ->
+                        "${name ?: ""} (${it.stop}):\n${it.line} → ${it.headsign}"
+                    }
+                }
+                launch(UI) {
+                    loadingView.visibility = View.GONE
+                    listView.visibility = View.VISIBLE
+                    this@FavouriteEditRowAdapter.notifyDataSetChanged()
                 }
             }
-            this@FavouriteEditRowAdapter.notifyDataSetChanged()
         }
     }
 
 
-    override fun getItemCount(): Int {
-        return segments.flatMap { it.value.plates ?: emptyList<Plate.ID>() }.size
-    }
+    override fun getItemCount(): Int = platesList.size
 
     override fun onBindViewHolder(holder: ViewHolder, position: Int) {
         launch(UI) {
-            val plates = segments.flatMap { it.value.plates ?: emptyList<Plate.ID>() }
-            val favourites = FavouriteStorage.getFavouriteStorage()
-            val id = plates.sortedBy { "${it.line}${it.stop}" }[position]
-            val favouriteElement = withContext(DefaultDispatcher) {
-                providerProxy.getStopName(id.stop).let {
-                    "${it ?: ""} (${id.stop}):\n${id.line} → ${id.headsign}"
-                }
-            }
+            val id = platesList[position]
+            val favouriteElement = namesList[id]
+
             holder.rowTextView.text = favouriteElement
             holder.deleteButton.setOnClickListener {
                 launch(UI) {
-                    favourite.segments.clear()
-                    favourite.segments.addAll(segments.map { it.value })
                     favourites.delete(favourite.name, id)
                     favourite = favourites.favourites[favourite.name]!!
-                    notifyDataSetChanged()
+                    notifyItemRemoved(platesList.indexOf(id))
+                    platesList.remove(id)
+                    namesList.remove(id)
                 }
             }
         }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/adapters/FavouritesAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/adapters/FavouritesAdapter.kt
index 76206a2b5e93f9683ff8dc07aa7011fa48c2a29e..a30130f8c29ad35d2a33a8cefa0ef22786ca5282 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/adapters/FavouritesAdapter.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/adapters/FavouritesAdapter.kt
@@ -14,10 +14,11 @@ import kotlinx.coroutines.experimental.*
 import java.util.*
 import ml.adamsprogs.bimba.Declinator
 import ml.adamsprogs.bimba.collections.FavouriteStorage
+import ml.adamsprogs.bimba.models.Favourite
 import ml.adamsprogs.bimba.secondsAfterMidnight
 
 
-class FavouritesAdapter(val appContext: Context, var favourites: FavouriteStorage,
+class FavouritesAdapter(private val appContext: Context, var favourites: FavouriteStorage,
                         private val onMenuItemClickListener: OnMenuItemClickListener,
                         private val onClickListener: ViewHolder.OnClickListener) :
         RecyclerView.Adapter<FavouritesAdapter.ViewHolder>() {
@@ -36,11 +37,8 @@         notifyItemChanged(position)
     }
 
     fun clearSelection() {
-        val selection = getSelectedItems()
         selectedItems.clear()
-        for (i in selection) {
-            notifyItemChanged(i)
-        }
+        notifyDataSetChanged()
     }
 
     fun getSelectedItemCount() = selectedItems.size()
@@ -107,6 +105,14 @@         val inflater = LayoutInflater.from(context)
 
         val rowView = inflater.inflate(R.layout.row_favourite, parent, false)
         return ViewHolder(rowView, onClickListener)
+    }
+
+    fun indexOf(name: String): Int {
+        return favourites.indexOf(name)
+    }
+
+    operator fun get(index: String): Favourite? {
+        return favourites[index]
     }
 
     class ViewHolder(itemView: View, private val listener: OnClickListener) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {




diff --git a/app/src/main/play/de-DE/listing/phoneScreenshots/StopActivity.png b/app/src/main/play/de-DE/listing/phoneScreenshots/StopActivity.png
index ca0c078c9290838696b65e1672443186f8dcc7e0..6cde859fa2cdc925aa05d2d04158bfae0eb34301 100644
Binary files a/app/src/main/play/de-DE/listing/phoneScreenshots/StopActivity.png and b/app/src/main/play/de-DE/listing/phoneScreenshots/StopActivity.png differ




diff --git a/app/src/main/play/de-DE/listing/phoneScreenshots/StopSpecify.png b/app/src/main/play/de-DE/listing/phoneScreenshots/StopSpecify.png
index 92a38f8cb0d7c4947705252470745f93ca30d885..86c00aa109026e9cb98759dc1825df3705a12589 100644
Binary files a/app/src/main/play/de-DE/listing/phoneScreenshots/StopSpecify.png and b/app/src/main/play/de-DE/listing/phoneScreenshots/StopSpecify.png differ




diff --git a/app/src/main/play/de-DE/listing/phoneScreenshots/modification.png b/app/src/main/play/de-DE/listing/phoneScreenshots/modification.png
index 325188c99fca92bb5d52b00025b0d7a81d8cab27..c3648d2dca7047ec3d3f8d5e83047d8a97bf593f 100644
Binary files a/app/src/main/play/de-DE/listing/phoneScreenshots/modification.png and b/app/src/main/play/de-DE/listing/phoneScreenshots/modification.png differ




diff --git a/app/src/main/play/de-DE/whatsnew b/app/src/main/play/de-DE/whatsnew
index 08ac12d40165f105c20e3da48cae7ec7b66e0a85..cbd4b8733278f81740a965e5c432ce3ec40c15fe 100644
--- a/app/src/main/play/de-DE/whatsnew
+++ b/app/src/main/play/de-DE/whatsnew
@@ -1,6 +1,8 @@
 - offizieller Fahrplan von ZTM
+- VM kann ohne Offline-Fahrplan verwendet werden
+- Offline-Fahrplan verwendet genaue Daten (statt Arbeitstagen/Samstagen/Feiertagen)
 - VM ist schneller und mehr zuverlässig (da es genauso berechnet wird wie Offline-Abfahrten)
 - Lieblingshaltestellen von Grund auf umgeschrieben
 - App ist auf externen Speicher bewegbar
 - neue Farben — grau und grün — die zum neuen Posener Stil passen
-- mehrere Fehler behoben 
+- mehrere Fehler behoben




diff --git a/app/src/main/play/en-GB/listing/phoneScreenshots/StopActivity.png b/app/src/main/play/en-GB/listing/phoneScreenshots/StopActivity.png
index 7ebd7846e24ddd62c650e885df31cb1f4206ae3b..a85d7e9dfdcb7b81b3a8a7d5097923c885356065 100644
Binary files a/app/src/main/play/en-GB/listing/phoneScreenshots/StopActivity.png and b/app/src/main/play/en-GB/listing/phoneScreenshots/StopActivity.png differ




diff --git a/app/src/main/play/en-GB/listing/phoneScreenshots/modification.png b/app/src/main/play/en-GB/listing/phoneScreenshots/modification.png
index 702e7c0993cf83ede23321091947d75bb5790370..2f536117feeb2ef180b3be65e36fee4154c94e4f 100644
Binary files a/app/src/main/play/en-GB/listing/phoneScreenshots/modification.png and b/app/src/main/play/en-GB/listing/phoneScreenshots/modification.png differ




diff --git a/app/src/main/play/en-GB/listing/phoneScreenshots/stopSpecify.png b/app/src/main/play/en-GB/listing/phoneScreenshots/stopSpecify.png
index 8217c6d31d35b74ba85c6c008df803a56167d579..a67350f5939563ab794be2ec1ef89be4f6f5e5f5 100644
Binary files a/app/src/main/play/en-GB/listing/phoneScreenshots/stopSpecify.png and b/app/src/main/play/en-GB/listing/phoneScreenshots/stopSpecify.png differ




diff --git a/app/src/main/play/en-GB/whatsnew b/app/src/main/play/en-GB/whatsnew
index 90bdcdb797fa4283af773200af88790e233fbb7b..177b822ae2ab045622785f35ecda675d18fd48c9 100644
--- a/app/src/main/play/en-GB/whatsnew
+++ b/app/src/main/play/en-GB/whatsnew
@@ -1,4 +1,6 @@
 - official timetable from ZTM
+- VM can be used without offline timetable
+- offline timetable uses exact dates (instead of workdays/saturdays/holidays)
 - VM is quicker and is more reliable (as it’s computed in the same way as offline departures)
 - favourites rewritten from scratch
 - app is movable to external storage




diff --git a/app/src/main/play/en-US/listing/phoneScreenshots/StopActivity.png b/app/src/main/play/en-US/listing/phoneScreenshots/StopActivity.png
index 7ebd7846e24ddd62c650e885df31cb1f4206ae3b..a85d7e9dfdcb7b81b3a8a7d5097923c885356065 100644
Binary files a/app/src/main/play/en-US/listing/phoneScreenshots/StopActivity.png and b/app/src/main/play/en-US/listing/phoneScreenshots/StopActivity.png differ




diff --git a/app/src/main/play/en-US/listing/phoneScreenshots/modification.png b/app/src/main/play/en-US/listing/phoneScreenshots/modification.png
index 702e7c0993cf83ede23321091947d75bb5790370..2f536117feeb2ef180b3be65e36fee4154c94e4f 100644
Binary files a/app/src/main/play/en-US/listing/phoneScreenshots/modification.png and b/app/src/main/play/en-US/listing/phoneScreenshots/modification.png differ




diff --git a/app/src/main/play/en-US/listing/phoneScreenshots/stopSpecify.png b/app/src/main/play/en-US/listing/phoneScreenshots/stopSpecify.png
index 8217c6d31d35b74ba85c6c008df803a56167d579..a67350f5939563ab794be2ec1ef89be4f6f5e5f5 100644
Binary files a/app/src/main/play/en-US/listing/phoneScreenshots/stopSpecify.png and b/app/src/main/play/en-US/listing/phoneScreenshots/stopSpecify.png differ




diff --git a/app/src/main/play/en-US/whatsnew b/app/src/main/play/en-US/whatsnew
index 93ea29b4f529f8032860db46233be0133b909333..177b822ae2ab045622785f35ecda675d18fd48c9 100644
--- a/app/src/main/play/en-US/whatsnew
+++ b/app/src/main/play/en-US/whatsnew
@@ -1,6 +1,8 @@
 - official timetable from ZTM
+- VM can be used without offline timetable
+- offline timetable uses exact dates (instead of workdays/saturdays/holidays)
 - VM is quicker and is more reliable (as it’s computed in the same way as offline departures)
-- favorites rewritten from scratch
+- favourites rewritten from scratch
 - app is movable to external storage
-- new colors—gray and green—fitting new Poznań style
+- new colours—grey and green—fitting new Poznań style
 - multiple bug fixes




diff --git a/app/src/main/play/it-IT/listing/phoneScreenshots/StopActivity.png b/app/src/main/play/it-IT/listing/phoneScreenshots/StopActivity.png
index 2e15bc7054690c2255babfa8fb177fff2614843e..de1163003b4205b0c6063228029912d5830efd72 100644
Binary files a/app/src/main/play/it-IT/listing/phoneScreenshots/StopActivity.png and b/app/src/main/play/it-IT/listing/phoneScreenshots/StopActivity.png differ




diff --git a/app/src/main/play/it-IT/listing/phoneScreenshots/StopSpecify.png b/app/src/main/play/it-IT/listing/phoneScreenshots/StopSpecify.png
index 124ba54ea124aa35f2d1ed69d51927ee5e1daaa3..77d36a1d0fe4a002881d3206a6b1e1218562183d 100644
Binary files a/app/src/main/play/it-IT/listing/phoneScreenshots/StopSpecify.png and b/app/src/main/play/it-IT/listing/phoneScreenshots/StopSpecify.png differ




diff --git a/app/src/main/play/it-IT/listing/phoneScreenshots/modification.png b/app/src/main/play/it-IT/listing/phoneScreenshots/modification.png
index df0a4a35105043b3b535333d7b96d45659b992e7..defccce5b66623b7dc6d0e27aa415238fc11d70b 100644
Binary files a/app/src/main/play/it-IT/listing/phoneScreenshots/modification.png and b/app/src/main/play/it-IT/listing/phoneScreenshots/modification.png differ




diff --git a/app/src/main/play/it-IT/whatsnew b/app/src/main/play/it-IT/whatsnew
index 5bba7d071638f7a133ed4d05fe26b64e4a9ff343..c5e305a6e9aa21fb45523afd12e177be5c6c279e 100644
--- a/app/src/main/play/it-IT/whatsnew
+++ b/app/src/main/play/it-IT/whatsnew
@@ -1,4 +1,6 @@
 - ufficiale orario da ZTM
+- VM può essere utilizzato senza orario offline
+- l’orario offline utilizza le date esatte (invece di giorni lavorativi/sabato/festività)
 - VM è più veloce e più affidabile (in quanto è calcolato allo stesso modo delle partenze offline)
 - favoriti rescritti da capo
 - app è trasferibile su una memoria esterna




diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/StopActivity.png b/app/src/main/play/pl-PL/listing/phoneScreenshots/StopActivity.png
index 6a7addd5e3bc04fb6fe5676933d9f8dc8a56f342..4df78339578128289b152e676c58051fded2c12f 100644
Binary files a/app/src/main/play/pl-PL/listing/phoneScreenshots/StopActivity.png and b/app/src/main/play/pl-PL/listing/phoneScreenshots/StopActivity.png differ




diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/StopSpecify.png b/app/src/main/play/pl-PL/listing/phoneScreenshots/StopSpecify.png
index e4430fd9d8cfb0a2b001aeaa0d1b409c99745255..67b81dc87ef810057208d330df299de0a3f9bd50 100644
Binary files a/app/src/main/play/pl-PL/listing/phoneScreenshots/StopSpecify.png and b/app/src/main/play/pl-PL/listing/phoneScreenshots/StopSpecify.png differ




diff --git a/app/src/main/play/pl-PL/listing/phoneScreenshots/modification.png b/app/src/main/play/pl-PL/listing/phoneScreenshots/modification.png
index f909547736638f297ad26ddfabf2162224dde75a..f15b3b0ab78817a7c0ab9aeff43dcdb034e7e8cb 100644
Binary files a/app/src/main/play/pl-PL/listing/phoneScreenshots/modification.png and b/app/src/main/play/pl-PL/listing/phoneScreenshots/modification.png differ




diff --git a/app/src/main/play/pl-PL/whatsnew b/app/src/main/play/pl-PL/whatsnew
index a2a83d24b5331ec136609bbf071f29604dea93ac..4133aac968c67993723579f25b0b86495d58850c 100644
--- a/app/src/main/play/pl-PL/whatsnew
+++ b/app/src/main/play/pl-PL/whatsnew
@@ -1,4 +1,6 @@
 - oficjalny rozkład od ZTM
+- VM może być używany bez rozkładu offline
+- rozkład offline operuje na dokładnych datach (zamiast dni powszednie/soboty/święta)
 - VM jest szybszy i pewniejszy (ponieważ jest generowany w ten sam sposób, co odjazdy offline)
 - ulubione przepisane od zera
 - aplikacja może być przenoszona do pamięci zewnętrznej




diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index c7bd21dbd86990cde81fea8abd3bf904b4546749..0000000000000000000000000000000000000000
--- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:aapt="http://schemas.android.com/aapt"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportHeight="108"
-    android:viewportWidth="108">
-    <path
-        android:fillType="evenOdd"
-        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
-        android:strokeColor="#00000000"
-        android:strokeWidth="1">
-        <aapt:attr name="android:fillColor">
-            <gradient
-                android:endX="78.5885"
-                android:endY="90.9159"
-                android:startX="48.7653"
-                android:startY="61.0927"
-                android:type="linear">
-                <item
-                    android:color="#44000000"
-                    android:offset="0.0" />
-                <item
-                    android:color="#00000000"
-                    android:offset="1.0" />
-            </gradient>
-        </aapt:attr>
-    </path>
-    <path
-        android:fillColor="#FFFFFF"
-        android:fillType="nonZero"
-        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
-        android:strokeColor="#00000000"
-        android:strokeWidth="1" />
-</vector>




diff --git a/app/src/main/res/layout/activity_edit_favourite.xml b/app/src/main/res/layout/activity_edit_favourite.xml
index b93a989607cf125b465c9631cbb173bec3502693..8788c2dcff597cb72bf74e8478c1040bde28a941 100644
--- a/app/src/main/res/layout/activity_edit_favourite.xml
+++ b/app/src/main/res/layout/activity_edit_favourite.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <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:id="@+id/dialog_favourite"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -38,28 +39,36 @@              android:id="@+id/favourite_name_edit"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
         android:layout_marginStart="8dp"
         android:ems="10"
         android:inputType="text"
         android:text=""
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/name_label"
         app:layout_constraintEnd_toEndOf="parent"
-        android:layout_marginEnd="8dp" />
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/name_label" />
 
     <android.support.v7.widget.RecyclerView
         android:id="@+id/favourite_edit_list"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        android:layout_marginBottom="8dp"
-        android:layout_marginEnd="8dp"
-        android:layout_marginStart="8dp"
         android:layout_marginTop="8dp"
+        android:visibility="gone"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@+id/favourite_name_edit">
 
     </android.support.v7.widget.RecyclerView>
+
+    <ProgressBar
+        android:id="@+id/favourite_edit_loading"
+        style="@style/Widget.AppCompat.ProgressBar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="@+id/favourite_edit_list"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/favourite_name_edit" />
 
 </android.support.constraint.ConstraintLayout>
\ No newline at end of file




diff --git a/app/src/main/res/layout/row_favourite_edit.xml b/app/src/main/res/layout/row_favourite_edit.xml
index 7de8a6a53aa15b716770b036dba2906bd855bfe1..1d48cfacec0056c94b5756ed01064e09d3de2e6e 100644
--- a/app/src/main/res/layout/row_favourite_edit.xml
+++ b/app/src/main/res/layout/row_favourite_edit.xml
@@ -28,15 +28,13 @@         android:id="@+id/favourite_edit_delete"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_marginBottom="16dp"
-        android:layout_marginStart="9dp"
+        android:layout_marginEnd="8dp"
         android:layout_marginTop="16dp"
+        android:contentDescription="@string/favourite_element_delete_button"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:srcCompat="@drawable/ic_delete"
-        tools:layout_editor_absoluteX="335dp"
-        tools:layout_editor_absoluteY="16dp"
-        android:contentDescription="@string/favourite_element_delete_button" />
+        app:srcCompat="@drawable/ic_delete" />
 
     <!--<ImageView-->
         <!--android:id="@+id/favourite_edit_split"-->




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b9385f070698ba94c13d54a19aa103dee644cf0b..513e4652283dcb5cd773feefafbc6ce810b49bcd 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,7 +18,8 @@     Downloading timetable
     <string name="timetable_downloading_progress" translatable="false">%1$1.2f MiB/%2$1.2f MiB</string>
     <string name="timetable_decompressing">Decompressing timetable</string>
     <string name="search_placeholder">Stop…</string>
-    <string name="no_connectivity">No connectivity – can’t update timetable</string>
+    <string name="no_connectivity_cant_update">No connectivity – can’t update timetable</string>
+    <string name="no_connectivity">No connectivity</string>
     <string name="timetable_up_to_date">Timetable is up-to-date</string>
     <string name="validity_failed">Downloaded timetable is corrupted – can’t update</string>
     <string name="error_try_later">Error. Try again later</string>
@@ -89,4 +90,5 @@     Fri
     <string name="Sat">Sat</string>
     <string name="Sun">Sun</string>
     <string name="summary_timetable_automatic_update">Automatically check for and download timetable updates</string>
+    <string name="server_error">Server error</string>
 </resources>




diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 0d89712827a98db6c34e2a4b7faac7f0647f0956..094229af232761e69b00552d2a3022dca6161e84 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -17,7 +17,8 @@     Verbind mit dem Internet, um den Fahrplan herunterzuladen
     <string name="no_db_downloading">Fahrplan wird heruntergeladen…</string>
     <string name="timetable_downloading">Fahrplan wird heruntergeladen</string>
     <string name="search_placeholder">Haltestelle…</string>
-    <string name="no_connectivity">Kein Verbindung – kann nicht den Fahrplan aktualisieren</string>
+    <string name="no_connectivity_cant_update">Kein Verbindung – kann nicht den Fahrplan aktualisieren</string>
+    <string name="no_connectivity">Kein Verbindung</string>
     <string name="timetable_up_to_date">Fahrplan ist aktuell</string>
     <string name="validity_failed">Der heruntergeladene Fahrplan ist geschädigt – kann nicht aktualisieren</string>
     <string name="error_try_later">Fehler. Versuch später noch einmal</string>
@@ -70,4 +71,5 @@     Sa.
     <string name="Sun">So.</string>
     <string name="title_timetable_automatic_update">Automatische Updates</string>
     <string name="summary_timetable_automatic_update">Automatisch nach Fahrplanaktualisierungen suchen und diese herunterladen</string>
+    <string name="server_error">Serverfehler</string>
 </resources>




diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 16ed32fc1cb98d9bd718d396ff7d53ff47c4d766..2269942ded34a1ddcc980175b7536da1b0b10ede 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -20,7 +20,8 @@     Connetti a Internet per scaricare l’orario
     <string name="no_db_downloading">L’orario è stando scaricato</string>
     <string name="timetable_downloading">Scaricando l’orario</string>
     <string name="search_placeholder">Fermata…</string>
-    <string name="no_connectivity">Nessuna connettività – non si riesce aggiornare l’orario</string>
+    <string name="no_connectivity_cant_update">Nessuna connettività – non si riesce aggiornare l’orario</string>
+    <string name="no_connectivity">Nessuna connettività</string>
     <string name="timetable_up_to_date">L’orario sta aggiornato</string>
     <string name="validity_failed">L’orario scaricato sta corrotto – non si riesce aggiornare</string>
     <string name="error_try_later">Errore. Riprova più tardi</string>
@@ -69,4 +70,5 @@     sab
     <string name="Sun">dom</string>
     <string name="summary_timetable_automatic_update">Controlla e scarica automaticamente gli aggiornamenti dell’orario</string>
     <string name="title_timetable_automatic_update">Aggiornamenti automatici</string>
+    <string name="server_error">Errore del server</string>
 </resources>




diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index b221b994f403382f9f5d11fb7aeb7cd93a3dbf09..e3a00a950c096b82a64501d0a55b6649e1cccce8 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -12,7 +12,8 @@     Maak verbinding met internet om de dienstregeling te downloaden.
     <string name="no_db_downloading">De dienstregeling wordt gedownload…</string>
     <string name="timetable_downloading">Bezig met downloaden van dienstregeling</string>
     <string name="search_placeholder">Halte…</string>
-    <string name="no_connectivity">Geen internetverbinding – de dienstregeling kan niet worden bijgewerkt.</string>
+    <string name="no_connectivity_cant_update">Geen internetverbinding – de dienstregeling kan niet worden bijgewerkt.</string>
+    <string name="no_connectivity">Geen internetverbinding</string>
     <string name="timetable_up_to_date">De dienstregeling is volledig bijgewerkt.</string>
     <string name="validity_failed">De gedownloade dienstregeling bevat fouten – bijwerken is niet mogelijk.</string>
     <string name="error_try_later">Fout; probeer het later opnieuw.</string>
@@ -71,4 +72,5 @@     za
     <string name="Sun">zo</string>
     <string name="title_timetable_automatic_update">Automatische updates</string>
     <string name="summary_timetable_automatic_update">Automatisch controleren en download dienstregeling updates</string>
+    <string name="server_error">Serverfout</string>
 </resources>




diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index b7e12ec5d2b6da0c24c679dab4db23edda1e645b..d627a692c607c5efb7198a47e16ecda5d285f774 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -14,7 +14,8 @@     Pobieranie rozkładu
     <string name="search_placeholder">Przystanek…</string>
     <string name="timetable_up_to_date">Rozkład jest aktualny</string>
     <string name="validity_failed">Pobrany rozkład jest uszkodzony – nie można zaktualizować</string>
-    <string name="no_connectivity">Brak połączenia z Internetem – nie można zaktualizować rozkładu</string>
+    <string name="no_connectivity_cant_update">Brak połączenia z Internetem – nie można zaktualizować rozkładu</string>
+    <string name="no_connectivity">Brak połączenia z Internetem</string>
     <string name="error_try_later">Błąd. Spróbuj ponownie później</string>
     <string name="now">Teraz</string>
     <string name="stop_already_fav">Ten przystanek już jest pośród ulubionych</string>
@@ -70,4 +71,5 @@     sob.
     <string name="Sun">niedz.</string>
     <string name="summary_timetable_automatic_update">Automatycznie sprawdzaj i pobieraj aktualizacje rozkładu</string>
     <string name="title_timetable_automatic_update">Automatyczne aktualizacje</string>
+    <string name="server_error">Błąd servera</string>
 </resources>
\ No newline at end of file




diff --git a/build.gradle b/build.gradle
index 9dd962ea072e40838a705c23178e0f7c41df1161..46a60aa5ed0c08de693c8ed41f436fa47d145972 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
-    ext.kotlin_version = '1.2.61'
+    ext.kotlin_version = '1.2.70'
     repositories {
         jcenter()
         maven { url 'https://maven.google.com' }