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