Bimba.git

commit 161e88895a382409bbb3c332bc2f904026d87e43

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

offline timetable and VM works (quite fast)

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


diff --git a/.idea/misc.xml b/.idea/misc.xml
index ba7052b8197ddf8ba8756022d905d03055c7ad60..635999df1e86791ad3787e455b4524e4d8879b93 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -24,7 +24,7 @@         
       </value>
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">




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 a324a7e458eeb6e4f35bd27f7055dc45f440af2f..b468c5c6a00ffbb89e7324e729f38ec0ffb1ac7b 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
@@ -38,7 +38,7 @@         FavouritesAdapter.ViewHolder.OnClickListener {
     val context: Context = this
     private val receiver = MessageReceiver.getMessageReceiver()
     lateinit var timetable: Timetable
-    var suggestions: List<GtfsSuggestion>? = null
+    private var suggestions: List<GtfsSuggestion>? = null
     private lateinit var drawerLayout: DrawerLayout
     private lateinit var drawerView: NavigationView
     lateinit var favouritesList: RecyclerView
@@ -98,10 +98,7 @@
         searchView.setOnFocusChangeListener(object : FloatingSearchView.OnFocusChangeListener {
             override fun onFocus() {
                 favouritesList.visibility = View.GONE
-                thread {
-                    val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(searchView.query), true) } //todo sorted by similarity
-                    runOnUiThread { searchView.swapSuggestions(newStops) }
-                }
+                filterSuggestions(searchView.query)
             }
 
             override fun onFocusCleared() {
@@ -112,10 +109,7 @@
         searchView.setOnQueryChangeListener({ oldQuery, newQuery ->
             if (oldQuery != "" && newQuery == "")
                 searchView.clearSuggestions()
-            thread {
-                val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(newQuery), true) } //todo sorted by similarity
-                runOnUiThread { searchView.swapSuggestions(newStops) }
-            }
+            filterSuggestions(newQuery)
         })
 
         searchView.setOnSearchListener(object : FloatingSearchView.OnSearchListener {
@@ -156,6 +150,13 @@
         searchView.attachNavigationDrawerToMenuButton(drawer_layout as DrawerLayout)
     }
 
+    private fun filterSuggestions(newQuery: String) {
+        thread {
+            val newStops = suggestions!!.filter { deAccent(it.name).contains(deAccent(newQuery), true) } //todo sorted by similarity
+            runOnUiThread { searchView.swapSuggestions(newStops) }
+        }
+    }
+
     private fun warnTimetableValidity() {
         val validTill = timetable.getValidTill()
         val today = Calendar.getInstance().toIsoDate()
@@ -165,13 +166,17 @@         }.toIsoDate()
 
         try {
             timetable.getServiceForToday()
-            if (today >= validTill) {
-                notifyTimetableValidity()
+            if (today > validTill) {
+                notifyTimetableValidity(-1)
                 suggestions = ArrayList()
                 return
             }
+            if (today == validTill) {
+                notifyTimetableValidity(0)
+                return
+            }
         } catch (e: IllegalArgumentException) {
-            notifyTimetableValidity()
+            notifyTimetableValidity(-1)
             suggestions = ArrayList()
             return
         }
@@ -179,20 +184,22 @@
         try {
             timetable.getServiceForTomorrow()
             if (tomorrow == validTill) {
-                notifyTimetableValidity(true)
+                notifyTimetableValidity(1)
                 return
             }
         } catch (e: IllegalArgumentException) {
-            notifyTimetableValidity(true)
+            notifyTimetableValidity(1)
             return
         }
     }
 
-    private fun notifyTimetableValidity(warning: Boolean = false) {
-        val message = if (warning)
-            getString(R.string.timetable_validity_warning)
-        else
-            getString(R.string.timetable_validity_finished)
+    private fun notifyTimetableValidity(daysTillInvalid: Int) {
+        val message = when(daysTillInvalid) {
+            -1 -> getString (R.string.timetable_validity_finished)
+            0 -> getString(R.string.timetable_validity_today)
+            1 -> getString(R.string.timetable_validity_tomorrow)
+            else -> return
+        }
         AlertDialog.Builder(context)
                 .setPositiveButton(context.getText(android.R.string.ok),
                         { dialog: DialogInterface, _: Int -> dialog.cancel() })




diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt
index 04da7ada55bdb13f87789f6fec5dccf0a11e118f..9731a065e2b2754d6f0d52cdb01b74f74bbd9e90 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/SplashActivity.kt
@@ -19,6 +19,7 @@                 startActivity(Intent(this, NoDbActivity::class.java))
             else
                 startActivity(Intent(this, DashActivity::class.java))
         } catch(e: Exception) {
+            e.printStackTrace()
             startActivity(Intent(this, NoDbActivity::class.java))
         }
         finish()




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 cdf185e0a4daef0c479be5834e16551845eb8572..56ab89ab5c2d3fb831c73d19e5d3e27827b22611 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/StopActivity.kt
@@ -3,7 +3,6 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
-import android.icu.util.JapaneseCalendar
 import android.support.design.widget.TabLayout
 import android.support.design.widget.Snackbar
 import android.support.v7.app.AppCompatActivity
@@ -35,6 +34,7 @@ import java.util.*
 import kotlin.collections.ArrayList
 import kotlin.concurrent.thread
 
+//todo<p:1> on click show time (HH:MM)
 class StopActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener, MessageReceiver.OnVmListener, Favourite.OnVmPreparedListener {
 
     private var sectionsPagerAdapter: SectionsPagerAdapter? = null
@@ -59,6 +59,7 @@     private val context = this
     private val receiver = MessageReceiver.getMessageReceiver()
     private val vmDepartures = HashMap<Plate.ID, Set<Departure>>()
     private var hasDepartures = false
+    private var lastUpdated = 0L
 
     private lateinit var sourceType: String
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -73,8 +74,7 @@         setSupportActionBar(toolbar)
 
         when (sourceType) {
             SOURCE_TYPE_STOP -> {
-                stopSegment = StopSegment(intent.getSerializableExtra(EXTRA_STOP_ID) as AgencyAndId, null)
-                stopSegment!!.fillPlates()
+                stopSegment = StopSegment(intent.getSerializableExtra(EXTRA_STOP_ID) as AgencyAndId, null).apply { fillPlates() }
                 supportActionBar?.title = timetable.getStopName(stopSegment!!.stop)
             }
             SOURCE_TYPE_FAV -> {
@@ -121,6 +121,7 @@             sectionsPagerAdapter?.departures = departures
         runOnUiThread {
             sectionsPagerAdapter?.notifyDataSetChanged()
             selectTodayPage()
+            lastUpdated = Calendar.getInstance().timeInMillis
         }
     }
 
@@ -170,6 +171,9 @@     }
 
     override fun onVm(vmDepartures: Set<Departure>?, plateId: Plate.ID) {
         if (vmDepartures == null && this.vmDepartures.isEmpty() && hasDepartures) {
+            if (ticked()) {
+                refreshAdapterFromStop()
+            }
             return
         }
         if (timetableType == "departure" && stopSegment!!.contains(plateId)) {
@@ -180,6 +184,8 @@                 this.vmDepartures.remove(plateId)
             refreshAdapterFromStop()
         }
     }
+
+    private fun ticked() = Calendar.getInstance().timeInMillis - lastUpdated >= VmClient.TICK_6_ZINA_TIM
 
     override fun onTimetableDownload(result: String?) {
         val message: String = when (result) {
@@ -254,18 +260,24 @@             if (departures == null)
                 return PlaceholderFragment.newInstance(null, relativeTime)
             if (departures!!.isEmpty())
                 return PlaceholderFragment.newInstance(ArrayList(), relativeTime)
-            val sat = timetable.getServiceFor(Calendar.SATURDAY)
-            val sun = timetable.getServiceFor(Calendar.SUNDAY)
+            val sat = try {
+                timetable.getServiceFor(Calendar.SATURDAY)
+            } catch (e: IllegalArgumentException) {
+                null
+            }
+            val sun = try {
+                timetable.getServiceFor(Calendar.SUNDAY)
+            } catch (e: IllegalArgumentException) {
+                null
+            }
             val list: List<Departure> = when (position) {
                 1 -> departures!![sat] ?: ArrayList()
                 2 -> departures!![sun] ?: ArrayList()
                 0 -> try {
                     departures!!
                             .filter { it.key != sat && it.key != sun }
-                            .filter { it.value.isNotEmpty() }.toList()[0].second
+                            .toList()[0].second
                 } catch (e: IndexOutOfBoundsException) {
-                    departures!!.filter { it.key != sat && it.key != sun }.toList()[0].second
-                } catch (e: Exception) {
                     ArrayList<Departure>()
                 }
                 else -> throw IndexOutOfBoundsException("No tab at index $position")
@@ -292,7 +304,7 @@             val layoutManager = LinearLayoutManager(activity)
             val departuresList: RecyclerView = rootView.findViewById(R.id.departuresList)
             val departures = arguments?.getStringArrayList("departures")?.map { Departure.fromString(it) }
             if (departures != null && departures.isNotEmpty())
-            departuresList.addItemDecoration(DividerItemDecoration(departuresList.context, layoutManager.orientation))
+                departuresList.addItemDecoration(DividerItemDecoration(departuresList.context, layoutManager.orientation))
 
 
             departuresList.adapter = DeparturesAdapter(activity as Context, departures,
@@ -305,7 +317,7 @@         companion object {
             fun newInstance(departures: List<Departure>?, relativeTime: Boolean): PlaceholderFragment {
                 val fragment = PlaceholderFragment()
                 val args = Bundle()
-                if (departures != null){
+                if (departures != null) {
                     if (departures.isNotEmpty()) {
                         val d = ArrayList<String>()
                         departures.mapTo(d) { it.toString() }




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 330f14d8b2a941a6bf8af290bba2a4b4f2d52ffe..012c1a1a943fcd85a366c4d3cf6538c91bfaa6d2 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/datasources/TimetableDownloader.kt
@@ -20,8 +20,10 @@ import ml.adamsprogs.bimba.NotificationChannels
 import ml.adamsprogs.bimba.R
 import ml.adamsprogs.bimba.getSecondaryExternalFilesDir
 import ml.adamsprogs.bimba.models.Timetable
+import java.net.ConnectException
+import java.net.URL
 import java.util.Calendar
-import java.net.*
+import javax.net.ssl.HttpsURLConnection
 import kotlin.collections.*
 
 class TimetableDownloader : IntentService("TimetableDownloader") {
@@ -48,11 +50,18 @@                 sendResult(RESULT_NO_CONNECTIVITY)
                 return
             }
 
-            val httpCon: HttpURLConnection
+            sendResult(RESULT_UP_TO_DATE)
+            return
+
+            val httpCon: HttpsURLConnection
             try {
-                val url = URL("http://ztm.poznan.pl/pl/dla-deweloperow/getGTFSFile") //todo download proper file
-                httpCon = url.openConnection() as HttpURLConnection
-                if (httpCon.responseCode != HttpURLConnection.HTTP_OK) {
+                val url = URL("https://adamsprogs.ml/gtfs")
+                httpCon = url.openConnection() as HttpsURLConnection
+                if (httpCon.responseCode == HttpsURLConnection.HTTP_NOT_MODIFIED) {
+                    sendResult(RESULT_UP_TO_DATE)
+                    return
+                }
+                if (httpCon.responseCode != HttpsURLConnection.HTTP_OK) {
                     sendResult(RESULT_NO_CONNECTIVITY)
                     return
                 }




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 48ea70cbb6bfa857d14247788331f2775aac048c..2cd8d9151394dc88ddc6fa01411f15edf001a85f 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/datasources/VmClient.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/datasources/VmClient.kt
@@ -6,7 +6,6 @@ import android.os.Handler
 import android.os.HandlerThread
 import android.os.IBinder
 import android.os.Process.THREAD_PRIORITY_BACKGROUND
-import android.util.Log
 import com.google.gson.Gson
 import ml.adamsprogs.bimba.NetworkStateReceiver
 import ml.adamsprogs.bimba.calendarFromIso
@@ -26,29 +25,36 @@     companion object {
         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 TICK_6_ZINA_TIM = 12500L
     }
 
     private var handler: Handler? = null
     private val tick6ZinaTim: Runnable = object : Runnable {
         override fun run() {
-            handler!!.postDelayed(this, (12.5 * 1000).toLong())
+            handler!!.postDelayed(this, TICK_6_ZINA_TIM)
             for (plateId in requests.keys)
                 downloadVM()
         }
     }
     private val requests = HashMap<AgencyAndId, Set<Request>>()
     private val vms = HashMap<AgencyAndId, HashSet<Plate>>() //HashSet<Departure>?
-    private val timetable = Timetable.getTimetable(this)
+    private val timetable = try {
+        Timetable.getTimetable(this)
+    } catch (e: NullPointerException) {
+        null
+    }
 
 
     override fun onCreate() {
         val thread = HandlerThread("ServiceStartArguments", THREAD_PRIORITY_BACKGROUND)
         thread.start()
         handler = Handler(thread.looper)
-        handler!!.postDelayed(tick6ZinaTim, (12.5 * 1000).toLong())
+        handler!!.postDelayed(tick6ZinaTim, TICK_6_ZINA_TIM)
     }
 
     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        if (timetable == null)
+            return START_NOT_STICKY
         val stopSegment = intent?.getParcelableExtra<StopSegment>("stop")!!
         if (stopSegment.plates == null)
             throw EmptyStopSegmentException()
@@ -107,7 +113,7 @@         }?.forEach { sendResult(it.id, it.departures?.get(today())) }
     }
 
     private fun today(): AgencyAndId {
-        return timetable.getServiceForToday()
+        return timetable!!.getServiceForToday()
     }
 
     private fun incrementRequest(stopSegment: StopSegment) {
@@ -148,13 +154,14 @@     }
 
     private fun downloadVM(stopSegment: StopSegment) {
         if (!NetworkStateReceiver.isNetworkAvailable(this)) {
+            vms[stopSegment.stop] = stopSegment.plates!!.map { Plate(it, null) }.toSet() as HashSet<Plate>
             stopSegment.plates!!.forEach {
                 sendResult(it, null)
             }
             return
         }
 
-        val stopSymbol = timetable.getStopCode(stopSegment.stop)
+        val stopSymbol = timetable!!.getStopCode(stopSegment.stop)
         val client = OkHttpClient()
         val url = "http://www.peka.poznan.pl/vm/method.vm?ts=${Calendar.getInstance().timeInMillis}"
         val formBody = FormBody.Builder()
@@ -165,8 +172,6 @@         val request = okhttp3.Request.Builder()
                 .url(url)
                 .post(formBody)
                 .build()
-
-        Log.i("VM", "created http request")
 
         val responseBody: String?
         try {
@@ -178,8 +183,6 @@             }
             return
         }
 
-        Log.i("VM", "received http response")
-
         if (responseBody?.get(0) == '<') {
             stopSegment.plates!!.forEach {
                 sendResult(it, null)
@@ -196,12 +199,12 @@
     private fun downloadVM(plateId: Plate.ID, times: List<*>) {
         val date = Calendar.getInstance()
         val todayDay = "${date.get(Calendar.DATE)}".padStart(2, '0')
-        val todayMode = timetable.calendarToMode(AgencyAndId(timetable.getServiceForToday().id))
+        val todayMode = timetable!!.calendarToMode(AgencyAndId(timetable.getServiceForToday().id))
 
         val departures = HashSet<Departure>()
 
         times.forEach {
-            val thisLine = timetable.getLineForNumber((it as Map<*, *>)["line"] as String)
+            val thisLine = AgencyAndId((it as Map<*, *>)["line"] as String)
             val thisHeadsign = it["direction"] as String
             val thisPlateId = Plate.ID(thisLine, plateId.stop, thisHeadsign)
             if (plateId == thisPlateId) {




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 0fef8f5355d832c1888eb0fd17787f46d3e5b3c0..5824d99b9df4e6bfeb5cb8d05dbb0a34845f44da 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Departure.kt
@@ -8,6 +8,7 @@ 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) {
@@ -80,7 +81,7 @@             if (array.size != 9)
                 throw IllegalArgumentException()
             val modification = array[4].safeSplit(";")
             return Departure(AgencyAndId.convertFromString(array[0]),
-                    array[1].split(";").map { Integer.parseInt(it) },
+                    array[1].safeSplit(";").map { Integer.parseInt(it) },
                     Integer.parseInt(array[2]), array[3] == "true",
                     modification, array[5], array[6] == "true",
                     array[7] == "true", array[8] == "true")
@@ -97,5 +98,5 @@             time.add(Calendar.DAY_OF_MONTH, 1)
         return (time.timeInMillis - now.timeInMillis) / (1000 * 60)
     }
 
-    val lineText: String = Timetable.getTimetable().getLineNumber(line)
+    val lineText: String = line.id
 }
\ No newline at end of file




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 26792a4a3bd07e01b312a53c4c4efb4532c66546..fef6828f9526fb7ae1ec403e3b4ef41d2b72df54 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/DeparturesAdapter.kt
@@ -24,6 +24,12 @@         const val VIEW_TYPE_CONTENT: Int = 1
         const val VIEW_TYPE_EMPTY: Int = 2
     }
 
+//    init {
+//        departures?.forEach {
+//            println("${it.line} -> ${it.headsign} @${it.time} (${if (it.isModified) it.modification[0] else{} })")
+//        }
+//    }
+
     override fun getItemCount(): Int {
         if (departures == null || departures.isEmpty())
             return 1
@@ -50,6 +56,7 @@             time.text = context.getString(R.string.no_departures)
             return
         }
         val departure = departures[position]
+        //println("${departure.line} -> ${departure.headsign} @${departure.time} (${if (departure.isModified) departure.modification[0] else {}})")
         val now = Calendar.getInstance()
         val departureTime = Calendar.getInstance().rollTime(departure.time)
         if (departure.tomorrow)




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 2448eecc8384ea7219b5ff3b990dd3351c9f5d1f..5197e6e17237a3b8ab3e28880b875321ddb6fda3 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/StopSegment.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/StopSegment.kt
@@ -3,11 +3,12 @@
 import android.os.Parcel
 import android.os.Parcelable
 import ml.adamsprogs.bimba.models.gtfs.AgencyAndId
+import ml.adamsprogs.bimba.safeSplit
 
 data class StopSegment(val stop: AgencyAndId, var plates: Set<Plate.ID>?) : Parcelable {
     constructor(parcel: Parcel) : this(
             parcel.readSerializable() as AgencyAndId,
-            parcel.readString().split(";").map { Plate.ID.fromString(it) }.toSet()
+            parcel.readString().safeSplit(";").map { Plate.ID.fromString(it) }.toSet()
     )
 
     companion object CREATOR : Parcelable.Creator<StopSegment> {
@@ -28,6 +29,8 @@     override fun writeToParcel(dest: Parcel?, flags: Int) {
         dest?.writeSerializable(stop)
         if (plates != null)
             dest?.writeString(plates!!.joinToString(";") { it.toString() })
+        else
+            dest?.writeString("")
     }
 
     override fun describeContents(): Int {




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 a374294a2e5013f22d61dafad0ec497b685080d4..b22b2f48b568c1569d7f6f920ef74ea0dddf115e 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
@@ -1,6 +1,9 @@
 package ml.adamsprogs.bimba.models
 
 import android.content.Context
+import android.database.Cursor
+import android.database.CursorIndexOutOfBoundsException
+import android.database.sqlite.SQLiteDatabase
 import com.google.gson.Gson
 import com.google.gson.JsonObject
 import com.univocity.parsers.csv.CsvParser
@@ -11,7 +14,6 @@ import ml.adamsprogs.bimba.getSecondaryExternalFilesDir
 import ml.adamsprogs.bimba.models.gtfs.AgencyAndId
 import ml.adamsprogs.bimba.models.gtfs.Route
 import ml.adamsprogs.bimba.models.gtfs.Trip
-import ml.adamsprogs.bimba.models.gtfs.Calendar
 import ml.adamsprogs.bimba.models.suggestions.LineSuggestion
 import ml.adamsprogs.bimba.models.suggestions.StopSuggestion
 import ml.adamsprogs.bimba.secondsAfterMidnight
@@ -23,9 +25,10 @@ import java.io.FileReader
 import kotlin.collections.ArrayList
 import kotlin.collections.HashMap
 import kotlin.collections.HashSet
+import kotlin.system.measureTimeMillis
 import java.util.Calendar as JCalendar
 
-class Timetable private constructor() { //fixme uses much too much RAM
+class Timetable private constructor() {
     companion object {
         private var timetable: Timetable? = null
 
@@ -34,18 +37,8 @@             return if (timetable == null || force)
                 if (context != null) {
                     timetable = Timetable()
                     timetable!!.filesDir = context.getSecondaryExternalFilesDir()
-                    val gtfsDir = File(timetable!!.filesDir, "gtfs_dir")
-                    timetable!!.agencyFile = File(gtfsDir, "agency.txt")
-                    timetable!!.calendarFile = File(gtfsDir, "calendar.txt")
-                    timetable!!.calendarDatesFile = File(gtfsDir, "calendar_dates.txt")
-                    timetable!!.feedInfoFile = File(gtfsDir, "feed_info.txt")
-                    timetable!!.routesFile = File(gtfsDir, "routes.txt")
-                    timetable!!.shapesFile = File(gtfsDir, "shapes.txt")
-                    timetable!!.stopsFile = File(gtfsDir, "stops.txt")
-                    timetable!!.stopTimesFile = File(gtfsDir, "stop_times.txt")
-                    timetable!!.tripsFile = File(gtfsDir, "trips.txt")
-                    timetable!!.stopsIndexFile = File(gtfsDir, "stop_index.txt")
-                    timetable!!.tripsIndexFile = File(gtfsDir, "trip_index.txt")
+                    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")
@@ -54,17 +47,7 @@                 timetable!!
         }
     }
 
-    private lateinit var agencyFile: File
-    private lateinit var calendarFile: File
-    private lateinit var calendarDatesFile: File
-    private lateinit var feedInfoFile: File
-    private lateinit var routesFile: File
-    private lateinit var shapesFile: File
-    private lateinit var stopsFile: File
-    private lateinit var stopTimesFile: File
-    private lateinit var tripsFile: File
-    private lateinit var stopsIndexFile: File
-    private lateinit var tripsIndexFile: File
+    private lateinit var db: SQLiteDatabase
     private var _stops: List<StopSuggestion>? = null
     private lateinit var filesDir: File
     private val tripsCache = HashMap<String, Array<String>>()
@@ -75,25 +58,23 @@
     fun getStopSuggestions(context: Context, force: Boolean = false): List<StopSuggestion> {
         if (_stops != null && !force)
             return _stops!!
-
-
-        val settings = CsvParserSettings()
-        settings.format.setLineSeparator("\r\n")
-        settings.format.quote = '"'
-        settings.isHeaderExtractionEnabled = true
-        val parser = CsvParser(settings)
 
         val ids = HashMap<String, HashSet<AgencyAndId>>()
         val zones = HashMap<String, String>()
 
-        val stopsFile = File(filesDir, "gtfs_files/stops.txt")
-        parser.parseAll(stopsFile).forEach {
-            if (it[2] !in ids)
-                ids[it[2]] = HashSet()
-            ids[it[2]]!!.add(AgencyAndId(it[0]))
-            zones[it[2]] = it[5]
+        val cursor = db.rawQuery("select stop_name, stop_id, zone_id from stops", null)
+
+        while (cursor.moveToNext()) {
+            val name = cursor.getString(0)
+            val id = cursor.getInt(1)
+            val zone = cursor.getString(2)
+            if (name !in ids)
+                ids[name] = HashSet()
+            ids[name]!!.add(AgencyAndId(id.toString()))
+            zones[name] = zone
         }
 
+        cursor.close()
 
         _stops = ids.map {
             val colour = when (zones[it.key]) {
@@ -109,60 +90,49 @@     }
 
     fun getLineSuggestions(): List<LineSuggestion> {
         val routes = ArrayList<LineSuggestion>()
-        val file = File(filesDir, "gtfs_files/routes.txt")
-        val settings = CsvParserSettings()
-        settings.format.setLineSeparator("\r\n")
-        settings.format.quote = '"'
-        settings.isHeaderExtractionEnabled = true
-        val parser = CsvParser(settings)
-        parser.parseAll(file).forEach {
-            routes.add(LineSuggestion(it[2], createRoute(
-                    it[0],
-                    it[1],
-                    it[2],
-                    it[3],
-                    it[4],
-                    Integer.parseInt(it[5]),
-                    Integer.parseInt(it[6], 16),
-                    Integer.parseInt(it[7], 16)
-            )))
+        val cursor = db.rawQuery("select * from routes", null)
+
+        while (cursor.moveToNext()) {
+            val routeId = cursor.getString(0)
+
+            routes.add(LineSuggestion(routeId,
+                    createRouteFromCursorRow(cursor)))
         }
+
         return routes.sortedBy { it.name }
     }
 
-    fun getHeadlinesForStop(stops: Set<AgencyAndId>): Map<AgencyAndId, Pair<String, Set<String>>> { //fixme adds one (not-)random shed
-        val trips = HashMap<String, HashSet<String>>()
-        val routes = HashMap<String, Pair<String, String>>()
+    fun getHeadlinesForStop(stops: Set<AgencyAndId>): Map<AgencyAndId, Pair<String, Set<String>>> {
         val headsigns = HashMap<AgencyAndId, Pair<String, HashSet<String>>>()
 
-        val stopIds = stops.map { it.id }
+        val stopsIndex = HashMap<Int, String>()
+        val where = stops.joinToString(" or ", "where ") { "stop_id = ?" }
+        var cursor = db.rawQuery("select stop_id, stop_code from stops $where", stops.map { it.toString() }.toTypedArray())
 
-        parseStopTimesWithStopIndex(stopIds) {
-            val stopId = it[3]
-//            println("Parsing line ${parser.context.currentLine()}; stopId: $stopId")
-            if (it[6] != "1") {
-                if (trips[stopId] == null)
-                    trips[stopId] = HashSet()
-                trips[stopId]!!.add(it[0])
-            }
+        while (cursor.moveToNext()) {
+            stopsIndex[cursor.getInt(0)] = cursor.getString(1)
         }
 
-        if (tripsCache.isEmpty())
-            createTripCache()
-        tripsCache.forEach {
-            routes[it.key] = Pair(it.value[0], it.value[3])
-        }
+        cursor.close()
 
+        cursor = db.rawQuery("select stop_id, route_id, trip_headsign " +
+                "from stop_times natural join trips " +
+                where, stops.map { it.toString() }.toTypedArray())
 
-        trips.forEach {
-            val headsign = HashSet<String>()
-            it.value.forEach {
-                headsign.add("${routes[it]!!.first} → ${routes[it]!!.second}")
-            }
-            headsigns[AgencyAndId(it.key)] = Pair(getStopCode(AgencyAndId(it.key)), headsign)
+        while (cursor.moveToNext()) {
+            val stop = cursor.getInt(0)
+            val stopId = AgencyAndId(stop.toString())
+            val route = cursor.getString(1)
+            val headsign = cursor.getString(2)
+            if (stopId !in headsigns)
+                headsigns[stopId] = Pair(stopsIndex[stop]!!, HashSet())
+            headsigns[stopId]!!.second.add("$route → $headsign")
         }
 
+        cursor.close()
+
         return headsigns
+
         /*
         1435 -> (AWF03, {232 → Os. Rusa})
         1436 -> (AWF04, {232 → Rondo Kaponiera})
@@ -230,37 +200,23 @@         }
     }
 
     fun getStopName(stopId: AgencyAndId): String {
-        val file = File(filesDir, "gtfs_files/stops.txt")
-        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
-        val header = mapReader.getHeader(true)
+        val cursor = db.rawQuery("select stop_name from stops where stop_id = ?",
+                arrayOf(stopId.id))
+        cursor.moveToNext()
+        val name = cursor.getString(0)
+        cursor.close()
 
-        var row: Map<String, Any>? = null
-        val processors = Array<CellProcessor?>(header.size, { null })
-        while ({ row = mapReader.read(header, processors); row }() != null) {
-            if ((row!!["stop_id"] as String) == stopId.id) {
-                mapReader.close()
-                return row!!["stop_name"] as String
-            }
-        }
-        mapReader.close()
-        throw IllegalArgumentException("Stop $stopId not in store")
+        return name
     }
 
     fun getStopCode(stopId: AgencyAndId): String {
-        val file = File(filesDir, "gtfs_files/stops.txt")
-        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
-        val header = mapReader.getHeader(true)
+        val cursor = db.rawQuery("select stop_code from stops where stop_id = ?",
+                arrayOf(stopId.id))
+        cursor.moveToNext()
+        val code = cursor.getString(0)
+        cursor.close()
 
-        var row: Map<String, Any>? = null
-        val processors = Array<CellProcessor?>(header.size, { null })
-        while ({ row = mapReader.read(header, processors); row }() != null) {
-            if ((row!!["stop_id"] as String) == stopId.id) {
-                mapReader.close()
-                return row!!["stop_code"] as String
-            }
-        }
-        mapReader.close()
-        throw IllegalArgumentException("Stop $stopId not in store")
+        return code
     }
 
     fun getLineNumber(lineId: AgencyAndId): String {
@@ -281,22 +237,65 @@         throw IllegalArgumentException("Route $lineId not in store")
     }
 
     fun getStopDepartures(stopId: AgencyAndId): Map<AgencyAndId, List<Departure>> {
-        println("getStopDepartures: ${JCalendar.getInstance().timeInMillis}")
-//        val trips = getTripsForStop(stopId)
-        val trips = HashMap<String, Trip>()
-        tripsCache.forEach {
-            trips[it.key] = tripFromCache(it.key)
+        val map = HashMap<AgencyAndId, ArrayList<Departure>>()
+        val measure = measureTimeMillis {
+
+            val cursor = db.rawQuery("select route_id, service_id, departure_time, " +
+                    "wheelchair_accessible, stop_sequence, trip_id, trip_headsign, route_desc " +
+                    "from stop_times natural join trips natural join routes where stop_id = ?",
+                    arrayOf(stopId.id))
+
+            while (cursor.moveToNext()) {
+                val line = AgencyAndId(cursor.getString(0))
+                val service = AgencyAndId(cursor.getInt(1).toString())
+                val mode = calendarToMode(service)
+                val time = parseTime(cursor.getString(2))
+                val lowFloor = cursor.getInt(3) == 1
+                val stopSequence = cursor.getInt(4)
+                val tripId = createTripId(cursor.getString(5))
+                val headsign = cursor.getString(6)
+                val desc = cursor.getString(7)
+
+                val modifications = Route.createModifications(desc)
+
+                val modification = explainModification(tripId, stopSequence, modifications)
+                val departure = Departure(line, mode, time, lowFloor, modification, headsign)
+                if (map[service] == null)
+                    map[service] = ArrayList()
+                map[service]!!.add(departure)
+            }
+
+            cursor.close()
+            map.forEach { it.value.sortBy { it.time } }
         }
-        val segment = StopSegment(stopId, null)
-        segment.fillPlates()
-        return getStopDeparturesBySegment(segment, trips)
+
+        //println(measure)
+
+        return map
+    }
+
+    fun getTrip(id: String): Trip {
+        val cursor = db.rawQuery("select * from trips where trip_id = ?", arrayOf(id))
+
+        val trip = Trip(
+                AgencyAndId(cursor.getString(0)),
+                AgencyAndId(cursor.getInt(1).toString()),
+                createTripId(cursor.getString(2)),
+                cursor.getString(3),
+                cursor.getInt(4),
+                AgencyAndId(cursor.getInt(5).toString()),
+                cursor.getInt(6) == 1
+        )
+
+        cursor.close()
+        return trip
     }
 
     fun getStopDeparturesBySegment(segment: StopSegment) = getStopDeparturesBySegment(segment, getTripsForStop(segment.stop))
 
     private fun getStopDeparturesBySegment(segment: StopSegment, trips: Map<String, Trip>): HashMap<AgencyAndId, List<Departure>> {
         println("getStopDeparturesBySegment: ${JCalendar.getInstance().timeInMillis}")
-        val departures = HashMap<AgencyAndId, ArrayList<Departure>>()
+        /*val departures = HashMap<AgencyAndId, ArrayList<Departure>>()
 
         val tripsInStop = HashMap<String, Pair<Int, Int>>()
 
@@ -329,7 +328,8 @@             sortedDepartures[it] = departures[it]!!.sortedBy { it.time }
         }
         println("</>: ${JCalendar.getInstance().timeInMillis}")
         println("</>: ${JCalendar.getInstance().timeInMillis}")
-        return sortedDepartures
+        return sortedDepartures*/
+        TODO("FIXME")
     }
 
     private fun parseTime(time: String): Int {
@@ -342,45 +342,27 @@         return cal.secondsAfterMidnight()
     }
 
     fun calendarToMode(serviceId: AgencyAndId): List<Int> {
-        val file = File(filesDir, "gtfs_files/calendar.txt")
-        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
-        val header = mapReader.getHeader(true)
+        val days = ArrayList<Int>()
+        val cursor = db.rawQuery("select * from calendar where service_id = ?",
+                arrayOf(serviceId.id))
 
-        var row: Map<String, Any>? = null
-        val processors = Array<CellProcessor?>(header.size, { null })
-        while ({ row = mapReader.read(header, processors); row }() != null) {
-            if ((row!!["service_id"] as String) == serviceId.id) {
-                mapReader.close()
-                val calendar = Calendar(row!!["monday"] as String == "1", row!!["tuesday"] as String == "1",
-                        row!!["wednesday"] as String == "1", row!!["thursday"] as String == "1", row!!["friday"] as String == "1",
-                        row!!["saturday"] as String == "1", row!!["sunday"] as String == "1")
-                val days = ArrayList<Int>()
-                if (calendar.monday) days.add(0)
-                if (calendar.tuesday) days.add(1)
-                if (calendar.wednesday) days.add(2)
-                if (calendar.thursday) days.add(3)
-                if (calendar.friday) days.add(4)
-                if (calendar.saturday) days.add(5)
-                if (calendar.sunday) days.add(6)
+        cursor.moveToNext()
+        (1 until 7).forEach {
+            if (cursor.getInt(it) == 1) days.add(it - 1)
+        }
 
-                return days
-            }
-        }
-        mapReader.close()
-        throw IllegalArgumentException("Service $serviceId not in store")
+        cursor.close()
+        return days
     }
 
-    private fun explainModification(trip: Trip, stopSequence: Int): List<String> { //todo<p:1> "kurs obsługiwany taborem niskopodłogowym" -> ignore
-        val route = getRouteForTrip(trip)
-        val definitions = route.modifications
-
+    private fun explainModification(tripId: Trip.ID, stopSequence: Int, routeModifications: Map<String, String>): List<String> { //todo<p:1> "kurs obsługiwany taborem niskopodłogowym" -> ignore
         val explanations = ArrayList<String>()
-        trip.id.modification.forEach {
+        tripId.modification.forEach {
             if (it.stopRange != null) {
                 if (stopSequence in it.stopRange)
-                    explanations.add(definitions[it.id.id]!!)
+                    explanations.add(routeModifications[it.id.id]!!)
             } else {
-                explanations.add(definitions[it.id.id]!!)
+                explanations.add(routeModifications[it.id.id]!!)
             }
         }
 
@@ -388,59 +370,26 @@         return explanations
     }
 
     private fun getRouteForTrip(trip: Trip): Route {
-        if (tripsCache.isEmpty())
-            createTripCache()
-        val routeId = tripsCache[trip.id.rawId]!![0]
-
-        val tripsFile = File(filesDir, "gtfs_files/routes.txt")
-        val mapReader = CsvMapReader(FileReader(tripsFile), CsvPreference.STANDARD_PREFERENCE)
-        val header = mapReader.getHeader(true)
+        val cursor = db.rawQuery("select * from routes natural join trips where trip_id = ?",
+                arrayOf(trip.id.rawId))
 
-        var routeRow: Map<String, Any>? = null
-        val processors = Array<CellProcessor?>(header.size, { null })
-        while ({ routeRow = mapReader.read(header, processors); routeRow }() != null) {
-            if ((routeRow!!["route_id"] as String) == routeId) {
-                mapReader.close()
-                val id = routeRow!!["route_id"] as String
-                val agency = routeRow!!["agency_id"] as String
-                val shortName = routeRow!!["route_short_name"] as String
-                val longName = routeRow!!["route_long_name"] as String
-                val desc = routeRow!!["route_desc"] as String
-                val type = Integer.parseInt(routeRow!!["route_type"] as String)
-                val colour = Integer.parseInt(routeRow!!["route_color"] as String, 16)
-                val textColour = Integer.parseInt(routeRow!!["route_text_color"] as String, 16)
-                return createRoute(id, agency, shortName, longName, desc, type, colour, textColour)
-            }
-        }
-        mapReader.close()
-        throw IllegalArgumentException("Trip ${trip.id.rawId} not in store")
+        cursor.moveToNext()
+        val route = createRouteFromCursorRow(cursor)
+        cursor.close()
+        return route
     }
 
-    private fun createRoute(id: String, agency: String, shortName: String, longName: String,
-                            desc: String, type: Int, colour: Int, textColour: Int): Route {
-        if (desc.contains("|")) {
-            val (to, from) = desc.split("|")
-            val fromSplit = from.split("^")
-            val toSplit = to.split("^")
-            val description = "${toSplit[0]}|${fromSplit[0]}"
-            val modifications = HashMap<String, String>()
-            toSplit.slice(1 until toSplit.size).forEach {
-                val (k, v) = it.split(" - ")
-                modifications[k] = v
-            }
-            return Route(AgencyAndId(id), AgencyAndId(agency), shortName, longName, description,
-                    type, colour, textColour, modifications)
-        } else {
-            val toSplit = desc.split("^")
-            val description = toSplit[0]
-            val modifications = HashMap<String, String>()
-            toSplit.slice(1 until toSplit.size).forEach {
-                val (k, v) = it.split(" - ")
-                modifications[k] = v
-            }
-            return Route(AgencyAndId(id), AgencyAndId(agency), shortName, longName, description,
-                    type, colour, textColour, modifications)
-        }
+    private fun createRouteFromCursorRow(cursor: Cursor): Route {
+        val routeId = cursor.getString(0)
+        val agencyId = cursor.getInt(1).toString()
+        val shortName = cursor.getString(2)
+        val longName = cursor.getString(3)
+        val desc = cursor.getString(4)
+        val type = cursor.getInt(5)
+        val colour = cursor.getString(6).toInt(16)
+        val textColour = cursor.getString(7).toInt(16)
+
+        return Route.create(routeId, agencyId, shortName, longName, desc, type, colour, textColour)
     }
 
     fun getTripsForStop(stopId: AgencyAndId): HashMap<String, Trip> {
@@ -491,36 +440,33 @@             return Trip.ID(rawId, AgencyAndId(rawId), HashSet(), false)
     }
 
     fun isEmpty(): Boolean {
-        try {
-            val file = File(filesDir, "gtfs_files/feed_info.txt").readText()
-            if (file == "")
-                return true
-            return false
+        return try {
+            File(filesDir, "timetable.db")
+            //todo check if not empty
+            false
         } catch (e: Exception) {
-            return true
+            true
         }
     }
 
     fun getValidSince(): String {
-        val file = File(filesDir, "gtfs_files/feed_info.txt")
-        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
-        val header = mapReader.getHeader(true)
+        val cursor = db.rawQuery("select feed_start_date from feed_info", null)
 
-        val processors = Array<CellProcessor?>(header.size, { null })
-        val row = mapReader.read(header, processors)
-        mapReader.close()
-        return row["feed_start_date"] as String
+        cursor.moveToNext()
+        val validTill = cursor.getString(0)
+
+        cursor.close()
+        return validTill
     }
 
     fun getValidTill(): String {
-        val file = File(filesDir, "gtfs_files/feed_info.txt")
-        val mapReader = CsvMapReader(FileReader(file), CsvPreference.STANDARD_PREFERENCE)
-        val header = mapReader.getHeader(true)
+        val cursor = db.rawQuery("select feed_end_date from feed_info", null)
 
-        val processors = Array<CellProcessor?>(header.size, { null })
-        val row = mapReader.read(header, processors)
-        mapReader.close()
-        return row["feed_end_date"] as String
+        cursor.moveToNext()
+        val validTill = cursor.getString(0)
+
+        cursor.close()
+        return validTill
     }
 
     fun getServiceForToday(): AgencyAndId {
@@ -536,22 +482,18 @@         return getServiceFor(tomorrowDoW)
     }
 
     fun getServiceFor(day: Int): AgencyAndId {
-        val dayColumn = ((day + 5) % 7) + 1
-        val file = File(filesDir, "gtfs_files/calendar.txt")
-
-        val settings = CsvParserSettings()
-        settings.format.quote = '"'
-        settings.format.setLineSeparator("\r\n")
-        settings.isHeaderExtractionEnabled = true
-        val parser = CsvParser(settings)
+        val dayColumn = arrayOf("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday")[((day + 5) % 7)]
+        val cursor = db.rawQuery("select service_id from calendar where $dayColumn = 1", null)
 
-        parser.parseAll(file).forEach {
-            if ((it[dayColumn] as String) == "1") {
-                return AgencyAndId(it[0] as String)
-            }
+        val service: Int
+        cursor.moveToNext()
+        try {
+            service = cursor.getInt(0)
+            cursor.close()
+            return AgencyAndId(service.toString())
+        } catch (e: CursorIndexOutOfBoundsException) {
+            throw IllegalArgumentException()
         }
-        throw IllegalArgumentException("Day $day not in calendar")
-
     }
 
     fun getLineForNumber(number: String): AgencyAndId {
@@ -572,18 +514,19 @@         throw IllegalArgumentException("Route $number not in store")
     }
 
     fun getPlatesForStop(stop: AgencyAndId): Set<Plate.ID> {
-        val tripIds = HashSet<String>()
+        val plates = HashSet<Plate.ID>()
+        val cursor = db.rawQuery("select route_id, trip_headsign " +
+                "from stop_times natural join trips where stop_id = ? " +
+                "group by route_id, trip_headsign", arrayOf(stop.id))
 
-        parseStopTimesWithStopIndex(listOf(stop.id)) {
-            tripIds.add(it[0])
+        while (cursor.moveToNext()) {
+            val routeId = AgencyAndId(cursor.getString(0))
+            val headsign = cursor.getString(1)
+            plates.add(Plate.ID(routeId, stop, headsign))
         }
 
-        val filteredPlates = HashSet<Plate.ID>()
-        tripIds.forEach {
-            filteredPlates.add(Plate.ID(AgencyAndId(tripsCache[it]!![0]), stop, tripsCache[it]!![3]))
-        }
-
-        return filteredPlates
+        cursor.close()
+        return plates
     }
 
     fun getTripGraphs(id: AgencyAndId): List<Map<Int, List<Int>>> {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/gtfs/Route.kt b/app/src/main/java/ml/adamsprogs/bimba/models/gtfs/Route.kt
index a93eeec0cd52a162376220db166b5ad6d7ba8785..ddba5daf28fd737a6c067c263d10c19ecc9d05d0 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/gtfs/Route.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/gtfs/Route.kt
@@ -6,7 +6,7 @@
 
 data class Route(val id: AgencyAndId, val agency: AgencyAndId, val shortName: String,
                  val longName: String, val description: String, val type: Int, val colour: Int,
-                 val textColour: Int, val modifications: HashMap<String, String>) : Parcelable {
+                 val textColour: Int, val modifications: Map<String, String>) : Parcelable {
     companion object CREATOR : Parcelable.Creator<Route> {
         const val TYPE_BUS = 3
         const val TYPE_TRAM = 0
@@ -18,6 +18,41 @@
         override fun newArray(size: Int): Array<Route?> {
             return arrayOfNulls(size)
         }
+
+        fun create(id: String, agency: String, shortName: String, longName: String,
+                                desc: String, type: Int, colour: Int, textColour: Int): Route {
+            return if (desc.contains("|")) {
+                val (to, from) = desc.split("|")
+                val fromSplit = from.split("^")
+                val toSplit = to.split("^")
+                val description = "${toSplit[0]}|${fromSplit[0]}"
+                val modifications = createModifications(desc)
+                Route(AgencyAndId(id), AgencyAndId(agency), shortName, longName, description,
+                        type, colour, textColour, modifications)
+            } else {
+                val toSplit = desc.split("^")
+                val description = toSplit[0]
+                val modifications = createModifications(desc)
+                Route(AgencyAndId(id), AgencyAndId(agency), shortName, longName, description,
+                        type, colour, textColour, modifications)
+            }
+        }
+
+        fun createModifications(desc: String): Map<String, String> {
+            val (to, from) = if(desc.contains('|')) desc.split("|") else listOf(desc, null)
+            val toSplit = to!!.split("^")
+            val fromSplit = from?.split("^")
+            val modifications = HashMap<String, String>()
+            toSplit.slice(1 until toSplit.size).forEach {
+                val (k, v) = it.split(" - ")
+                modifications[k] = v
+            }
+            fromSplit?.slice(1 until fromSplit.size)?.forEach {
+                val (k,v) = it.split(" - ")
+                modifications[k] = v
+            }
+            return modifications
+        }
     }
 
     @Suppress("UNCHECKED_CAST")
@@ -41,7 +76,7 @@         parcel.writeString(description)
         parcel.writeInt(type)
         parcel.writeInt(colour)
         parcel.writeInt(textColour)
-        parcel.writeSerializable(modifications)
+        parcel.writeSerializable(modifications as HashMap)
     }
 
     override fun describeContents(): Int {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/GtfsSuggestion.kt b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/GtfsSuggestion.kt
index edabd07aa75d649c2fd94f1d63497f7e3315d50f..1fab1c029c41a7e5dc26178ae5f5e86b33ab375a 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/GtfsSuggestion.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/GtfsSuggestion.kt
@@ -4,4 +4,8 @@ import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
 
 abstract class GtfsSuggestion(val name: String) : SearchSuggestion, Comparable<GtfsSuggestion> {
     abstract fun getIcon(): Int
+
+    abstract fun getColour(): Int
+
+    abstract fun getBgColour(): Int
 }
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/LineSuggestion.kt b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/LineSuggestion.kt
index 5ad33995325f29d99cec71f072f9d8e1331c3747..0b71ad0cce6bf32496a79b791082caa7a5ebf5c6 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/LineSuggestion.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/LineSuggestion.kt
@@ -31,9 +31,17 @@     override fun getBody(): String {
         return name
     }
 
+    override fun getColour(): Int {
+        return route.colour
+    }
+
+    override fun getBgColour(): Int {
+        return route.textColour
+    }
+
     override fun compareTo(other: GtfsSuggestion): Int {
         return if (other is LineSuggestion)
-            name.toInt().compareTo(other.name.toInt())
+            name.padStart(3, '0').compareTo(other.name.padStart(3, '0'))
         else
             name.compareTo(other.name)
     }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/StopSuggestion.kt b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/StopSuggestion.kt
index d0fbe0272348a38a004b1211de5aeed2c8ee5202..934a6e0e2ef25631fe6fde8162becd1260b8a8d8 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/StopSuggestion.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/StopSuggestion.kt
@@ -21,11 +21,19 @@         dest?.writeString(zoneColour)
     }
 
     override fun getBody(): String {
-        return "$name <small><font color=\"$zoneColour\">$zone</font></small>"
+        return name
     }
 
     override fun getIcon(): Int {
         return R.drawable.ic_stop
+    }
+
+    override fun getColour(): Int {
+        return zoneColour.filter { it in "0123456789abcdef" }.toInt(16)
+    }
+
+    override fun getBgColour(): Int {
+        return "ffffff".toInt(16)
     }
 
     override fun compareTo(other: GtfsSuggestion): Int {




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 958e22c9bfc3f10cc768bfac26ac8bc625eaac30..48f82d3da3b2f187fe4b897dc73c85a31c89ee6a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -79,5 +79,6 @@     LineSpecifyActivity
     <string name="tab_text_line_to">To</string>
     <string name="tab_text_line_fro">Fro</string>
     <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_warning">Timetable validity ends today.</string>
+    <string name="timetable_validity_today">Timetable validity ends today.</string>
+    <string name="timetable_validity_tomorrow">Timetable validity ends tomorrow.</string>
 </resources>




diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index d74f1332adb4471a500c9216f764ff84302c2799..2b4277b945fb4c63257f0851ee51482a2a822b1c 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -64,6 +64,7 @@     Heute
     <string name="no_departures">Keine Abfahrten</string>
     <string name="tab_text_line_to">Hin</string>
     <string name="tab_text_line_fro">Her</string>
-    <string name="timetable_validity_warning">Fahrplan gilt nur bis heute.</string>
+    <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>
 </resources>




diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 6a7aa064786e68c9c4186348dfbee07e5e2eaa39..689856d152ff3c9a9845abb8d6e379666da1e02e 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -62,6 +62,7 @@     Oggi
     <string name="no_departures">Nessune partenze</string>
     <string name="tab_text_line_to">Avanti</string>
     <string name="tab_text_line_fro">Indietro</string>
-    <string name="timetable_validity_warning">L’orario è valido solo fino ad oggi.</string>
+    <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>
 </resources>




diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 83866dcc428e2d63c4de098aaa63e252eb90cd6c..b01fe4826ede75950ac49d588fd6b78eda161c82 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -64,6 +64,7 @@     Dzisiaj
     <string name="no_departures">Brak odjazdów</string>
     <string name="tab_text_line_to">Tam</string>
     <string name="tab_text_line_fro">Z powrotem</string>
-    <string name="timetable_validity_warning">Rozkład obowiązuje tylko do dzisiaj.</string>
+    <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>
 </resources>
\ No newline at end of file