Bimba.git

commit bfcea682698889e5b77ef1d0015b9f8d9b0ac637

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

Downloading timetable on first run

 app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt | 12 
 app/src/main/java/ml/adamsprogs/bimba/NetworkStateReceiver.kt | 44 +++
 app/src/main/java/ml/adamsprogs/bimba/NoDbActivity.kt | 50 +++
 app/src/main/java/ml/adamsprogs/bimba/SplashActivity.kt | 8 
 app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt | 43 ++
 app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt | 45 ++-
 app/src/main/res/drawable/ic_download.xml | 9 
 app/src/main/res/drawable/nodb.xml | 70 +---
 app/src/main/res/layout/activity_nodb.xml | 45 ++
 app/src/main/res/values/strings.xml | 4 


diff --git a/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt
index 8d14945fb0803008a88b8e924928ddb075b1423a..4040505398c178bba2b0bde1bfc4ecbc381431dc 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/MainActivity.kt
@@ -18,8 +18,8 @@ import android.view.inputmethod.InputMethodManager
 
 
 class MainActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener {
-    lateinit var listener: MessageReceiver.OnTimetableDownloadListener
-    lateinit var receiver: MessageReceiver
+    val listener = this
+    val receiver  = MessageReceiver()
     lateinit var layout: View
     lateinit var timetable: Timetable
     var stops: ArrayList<StopSuggestion>? = null
@@ -31,11 +31,9 @@         AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO)
 
         layout = findViewById(R.id.main_layout)
         val context = this
-        listener = this
 
         val filter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded")
         filter.addCategory(Intent.CATEGORY_DEFAULT)
-        receiver = MessageReceiver()
         registerReceiver(receiver, filter)
         receiver.addOnTimetableDownloadListener(listener)
         startService(Intent(context, TimetableDownloader::class.java))
@@ -43,12 +41,6 @@
         timetable = Timetable(this)
         stops = timetable.getStops()
         val searchView = findViewById(R.id.search_view) as FloatingSearchView
-
-        if (stops == null) {
-            //todo something more direct and create pull-to-refresh
-            Snackbar.make(layout, getString(R.string.no_timetable), Snackbar.LENGTH_LONG).show()
-            return
-        }
 
         searchView.setOnQueryChangeListener({ _, newQuery ->
             thread {




diff --git a/app/src/main/java/ml/adamsprogs/bimba/NetworkStateReceiver.kt b/app/src/main/java/ml/adamsprogs/bimba/NetworkStateReceiver.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cd17f1cdc5ed38f6452844e51000d72fdc21e380
--- /dev/null
+++ b/app/src/main/java/ml/adamsprogs/bimba/NetworkStateReceiver.kt
@@ -0,0 +1,44 @@
+package ml.adamsprogs.bimba
+
+import android.net.ConnectivityManager
+import android.content.Intent
+import android.content.BroadcastReceiver
+import android.content.Context
+
+fun isNetworkAvailable(context: Context): Boolean {
+    val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+    val activeNetworkInfo = connectivityManager.activeNetworkInfo
+    return activeNetworkInfo != null && activeNetworkInfo.isConnected
+}
+
+class NetworkStateReceiver : BroadcastReceiver() {
+
+    val onConnectivityChangeListeners = HashSet<OnConnectivityChangeListener>()
+
+    override fun onReceive(context: Context, intent: Intent) {
+        if (intent.extras != null) {
+            val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+            val ni = connectivityManager.activeNetworkInfo
+
+            if (ni != null && ni.isConnectedOrConnecting) {
+                for (listener in onConnectivityChangeListeners)
+                    listener.onConnectivityChange(true)
+            } else if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, java.lang.Boolean.FALSE)) {
+                for (listener in onConnectivityChangeListeners)
+                    listener.onConnectivityChange(false)
+            }
+        }
+    }
+
+    fun addOnConnectivityChangeListener(listener: OnConnectivityChangeListener) {
+        onConnectivityChangeListeners.add(listener)
+    }
+
+    fun removeOnConnectivityChangeListener(listener: OnConnectivityChangeListener) {
+        onConnectivityChangeListeners.remove(listener)
+    }
+
+    interface OnConnectivityChangeListener {
+        fun onConnectivityChange(connected: Boolean)
+    }
+}
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/NoDbActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/NoDbActivity.kt
index 1fa7b24531f948df29521998f3d51067d29018c1..177140d77dbe6c1a5c8a66e159318a03b6cd7ae1 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/NoDbActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/NoDbActivity.kt
@@ -1,12 +1,60 @@
 package ml.adamsprogs.bimba
 
+import android.content.Intent
 import android.support.v7.app.AppCompatActivity
 import android.os.Bundle
+import android.content.IntentFilter
+import android.widget.TextView
+
 
-class NoDbActivity : AppCompatActivity() {
+class NoDbActivity : AppCompatActivity(), NetworkStateReceiver.OnConnectivityChangeListener, MessageReceiver.OnTimetableDownloadListener {
+    val networkStateReceiver = NetworkStateReceiver()
+    val timetableDownloadReceiver = MessageReceiver()
+    var serviceRunning = false
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.activity_nodb)
+        var filter: IntentFilter
+        filter = IntentFilter("ml.adamsprogs.bimba.timetableDownloaded")
+        filter.addCategory(Intent.CATEGORY_DEFAULT)
+        registerReceiver(timetableDownloadReceiver, filter)
+        timetableDownloadReceiver.addOnTimetableDownloadListener(this)
+
+        if (!isNetworkAvailable(this)) {
+            (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.no_db_connect)
+            filter = IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")
+            registerReceiver(networkStateReceiver, filter)
+            networkStateReceiver.addOnConnectivityChangeListener(this)
+        } else
+            downloadTimetable()
+    }
+
+    fun downloadTimetable() {
+        (findViewById(R.id.noDbCaption) as TextView).text = getString(R.string.no_db_downloading)
+        serviceRunning = true
+        intent = Intent(this, TimetableDownloader::class.java)
+        intent.putExtra("force", true)
+        startService(intent)
+    }
+
+    override fun onConnectivityChange(connected: Boolean) {
+        if (connected && !serviceRunning)
+            downloadTimetable()
+        /*if (!connected)
+            serviceRunning = false*/
+    }
+
+    override fun onTimetableDownload() {
+        timetableDownloadReceiver.removeOnTimetableDownloadListener(this)
+        networkStateReceiver.removeOnConnectivityChangeListener(this)
+        startActivity(Intent(this, MainActivity::class.java))
+        finish()
+    }
+
+    override fun onPause() {
+        super.onPause()
+        unregisterReceiver(timetableDownloadReceiver)
+        unregisterReceiver(networkStateReceiver)
     }
 }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/SplashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/SplashActivity.kt
index 168ec598511fdd5f348ca221fd61338d388dfe61..19f61cc3f8a4243ae8923ddc580b6b1085c5e2b0 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/SplashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/SplashActivity.kt
@@ -3,15 +3,17 @@
 import android.support.v7.app.AppCompatActivity
 import android.os.Bundle
 import android.content.Intent
+import ml.adamsprogs.bimba.models.Timetable
 
 
 class SplashActivity : AppCompatActivity() {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        //todo if no db
-        //startActivity(Intent(this, NoDbActivity::class.java))
-        startActivity(Intent(this, MainActivity::class.java))
+        if(Timetable(this).isDatabaseHealthy())
+            startActivity(Intent(this, MainActivity::class.java))
+        else
+            startActivity(Intent(this, NoDbActivity::class.java))
         finish()
     }
 }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt b/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt
index e06ca9c86d60fa702b7bebd9514f4610b880295a..a39ebacecf238e3ff98cc59a3f0acfb6fb2ce697 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/TimetableDownloader.kt
@@ -3,20 +3,27 @@
 import android.app.IntentService
 import android.content.Context
 import android.content.Intent
+import android.support.v4.app.NotificationCompat
 import java.net.HttpURLConnection
 import java.net.URL
 import org.tukaani.xz.XZInputStream
 import java.io.*
 import java.security.MessageDigest
 import kotlin.experimental.and
-import android.net.ConnectivityManager
 import android.util.Log
+import android.app.NotificationManager
+
 
 class TimetableDownloader : IntentService("TimetableDownloader") {
+    lateinit var notificationManager: NotificationManager
+    var size: Int = 0
+
     override fun onHandleIntent(intent: Intent?) {
+
         if (intent != null) {
+            notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
             val prefs = this.getSharedPreferences("ml.adamsprogs.bimba.prefs", Context.MODE_PRIVATE)!!
-            if (!isNetworkAvailable())
+            if (!isNetworkAvailable(this))
                 return
             val metadataUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/timetable.db.meta")
             var httpCon = metadataUrl.openConnection() as HttpURLConnection
@@ -26,10 +33,13 @@             Log.i("Downloader", "Got metadata")
             val reader = BufferedReader(InputStreamReader(httpCon.inputStream))
             val lastModified = reader.readLine()
             val checksum = reader.readLine()
+            size = Integer.parseInt(reader.readLine()) / 1024
             val currentLastModified = prefs.getString("timetableLastModified", "1979-10-12T00:00")
-            if (lastModified <= currentLastModified)
+            if (lastModified <= currentLastModified && !intent.getBooleanExtra("force", false))
                 return
             Log.i("Downloader", "timetable is newer ($lastModified > $currentLastModified)")
+
+            notify(0)
 
             val xzDbUrl = URL("https://adamsprogs.ml/w/_media/programmes/bimba/timetable.db.xz")
             httpCon = xzDbUrl.openConnection() as HttpURLConnection
@@ -53,15 +63,33 @@                 sendBroadcast(broadcastIntent)
             } else {
                 Log.i("Downloader", "downloaded but is wrong")
             }
+
+            cancelNotification()
         }
     }
 
+    private fun notify(progress: Int) {
+        val builder = NotificationCompat.Builder(this)
+                .setSmallIcon(R.drawable.ic_download)
+                .setContentTitle(getString(R.string.timetable_downloading))
+                .setContentText("$progress KiB/$size KiB")
+                .setCategory(NotificationCompat.CATEGORY_PROGRESS)
+                .setOngoing(true)
+                .setProgress(size, progress, false)
+
+        notificationManager.notify(42, builder.build())
+    }
+
+    private fun cancelNotification() {
+        notificationManager.cancel(42)
+    }
+
     private fun copyInputStreamToFile(ins: InputStream, file: File, checksum: String): Boolean {
         val md = MessageDigest.getInstance("SHA-512")
         var hex = ""
         try {
             val out = FileOutputStream(file)
-            val buf = ByteArray(1024)
+            val buf = ByteArray(1024) //todo bigger?
             var lenSum = 0.0f
             var len = 42
             while (len > 0) {
@@ -71,6 +99,7 @@                     break
                 md.update(buf, 0, len)
                 out.write(buf, 0, len)
                 lenSum += len.toFloat() / 1024.0f
+                notify(lenSum.toInt())
                 Log.i("Downloader", "downloading $len B: $lenSum KiB")
             }
             out.close()
@@ -85,11 +114,5 @@             }
             Log.i("Downloader", "checksum is $checksum, and hex is $hex")
             return checksum == hex
         }
-    }
-
-    private fun isNetworkAvailable(): Boolean {
-        val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
-        val activeNetworkInfo = connectivityManager.activeNetworkInfo
-        return activeNetworkInfo != null && activeNetworkInfo.isConnected
     }
 }




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 9ef9a47d5def1edfd8576f36eef8f7473cac37ae..9e08ecd35ab476075222b2f4fc731c6e8949fdba 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/Timetable.kt
@@ -1,14 +1,24 @@
 package ml.adamsprogs.bimba.models
 
 import android.content.Context
+import android.database.Cursor
 import android.database.sqlite.SQLiteCantOpenDatabaseException
 import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteDatabaseCorruptException
 import android.util.Log
 import java.io.File
 
 class Timetable(var context: Context) {
     var db: SQLiteDatabase? = null
 
+    init {
+        readDbFile()
+    }
+
+    fun isDatabaseHealthy(): Boolean {
+        return db != null
+    }
+
     fun refresh() {
         readDbFile()
     }
@@ -18,7 +28,10 @@         try {
             db = SQLiteDatabase.openDatabase(File(context.filesDir, "new_timetable.db").path,
                     null, SQLiteDatabase.OPEN_READONLY)
         } catch(e: SQLiteCantOpenDatabaseException) {
-            Log.e("Timetable", "Cannot open db")
+            Log.e("Timetable", "Cannot open database")
+            db = null
+        } catch(e: SQLiteDatabaseCorruptException) {
+            Log.e("Timetable", "Database is corrupted")
             db = null
         }
     }
@@ -26,12 +39,19 @@
     fun getStops(): ArrayList<StopSuggestion>? {
         if (db == null)
             return null
-        val cursor = db!!.rawQuery("select name ||char(10)|| headsigns as suggestion, id from stops" +
-                " join nodes on(stops.symbol = nodes.symbol) order by name, id;", null)
         val stops = ArrayList<StopSuggestion>()
-        while (cursor.moveToNext())
-            stops.add(StopSuggestion(cursor.getString(0), cursor.getString(1)))
-        cursor.close()
+        var cursor : Cursor? = null
+        try {
+            cursor = db!!.rawQuery("select name ||char(10)|| headsigns as suggestion, id from stops" +
+                    " join nodes on(stops.symbol = nodes.symbol) order by name, id;", null)
+            while (cursor.moveToNext())
+                stops.add(StopSuggestion(cursor.getString(0), cursor.getString(1)))
+        }catch (e: SQLiteDatabaseCorruptException) {
+            cursor?.close()
+            return null
+        } finally {
+            cursor?.close()
+        }
         return stops
     }
 
@@ -42,7 +62,7 @@         val cursor = db!!.rawQuery("select name from nodes join stops on(stops.symbol = nodes.symbol) where id = ?;",
                 listOf(stopId).toTypedArray())
         val name: String
         cursor.moveToNext()
-            name = cursor.getString(0)
+        name = cursor.getString(0)
         cursor.close()
         return name
     }
@@ -51,9 +71,9 @@     fun getStopDepartures(stopId: String): HashMap>? {
         if (db == null)
             return null
         val cursor = db!!.rawQuery("select lines.number, mode, substr('0'||hour, -2) || ':' || " +
-            "substr('0'||minute, -2) as time, lowFloor, modification, headsign from departures join "+
-            "timetables on(timetable_id = timetables.id) join lines on(line_id = lines.id) where "+
-            "stop_id = ? order by mode, time;", listOf(stopId).toTypedArray())
+                "substr('0'||minute, -2) as time, lowFloor, modification, headsign from departures join " +
+                "timetables on(timetable_id = timetables.id) join lines on(line_id = lines.id) where " +
+                "stop_id = ? order by mode, time;", listOf(stopId).toTypedArray())
         val departures = HashMap<String, ArrayList<Departure>>()
         departures.put("workdays", ArrayList())
         departures.put("saturdays", ArrayList())
@@ -66,9 +86,4 @@         }
         cursor.close()
         return departures
     }
-
-    init {
-        readDbFile()
-    }
-
 }




diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a2b16c14e56813b9d6c53f6dee2a9d82729164c0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_download.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"
+        android:fillColor="#000000"/>
+</vector>




diff --git a/app/src/main/res/drawable/nodb.xml b/app/src/main/res/drawable/nodb.xml
index 4255d8d033055c251983dfa0c42184f1fb987d6d..1fee6a8f9864a21b9ed0400fa435fe5adb58e7d4 100644
--- a/app/src/main/res/drawable/nodb.xml
+++ b/app/src/main/res/drawable/nodb.xml
@@ -1,67 +1,33 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="285dp"
-        android:height="508dp"
+        android:width="1080dp"
+        android:height="1920dp"
         android:viewportWidth="285.75"
         android:viewportHeight="508.0">
     <path
-        android:pathData="M0,0V508H285.75V0Z"
+        android:pathData="M0,0h285.75v508h-285.75z"
         android:fillAlpha="1"
         android:strokeColor="#00000000"
-        android:fillColor="#f5f5f5"
-        android:strokeWidth="1.09682584"/>
+        android:fillColor="#e0e0e0"
+        android:strokeWidth="2.96499991"
+        android:strokeAlpha="1"/>
     <path
-        android:pathData="M118.39,10.65h11.8v45.79h-11.8z"
+        android:pathData="M136.13,341.19h13.49v166.81h-13.49z"
         android:fillAlpha="1"
-        android:fillType="evenOdd"
-        android:strokeColor="#00000000"
-        android:fillColor="#ffffff"
-        android:strokeWidth="1.09682584"/>
-    <path
-        android:pathData="M118.39,95.69h11.8v45.79h-11.8z"
-        android:fillAlpha="1"
-        android:fillType="evenOdd"
-        android:strokeColor="#00000000"
-        android:fillColor="#ffffff"
-        android:strokeWidth="1.09682584"/>
-    <path
-        android:pathData="M118.39,180.74h11.8v45.79h-11.8z"
-        android:fillAlpha="1"
-        android:fillType="evenOdd"
-        android:strokeColor="#00000000"
-        android:fillColor="#ffffff"
-        android:strokeWidth="1.09682584"/>
-    <path
-        android:pathData="M118.39,265.78h11.8v45.79h-11.8z"
-        android:fillAlpha="1"
-        android:fillType="evenOdd"
         android:strokeColor="#00000000"
-        android:fillColor="#ffffff"
-        android:strokeWidth="1.09682584"/>
+        android:fillColor="#aaaaaa"
+        android:strokeWidth="5.5416379"
+        android:strokeAlpha="1"/>
     <path
-        android:pathData="M157.97,342.48l6.86,-9.59l37.36,26.6l-6.86,9.59z"
-        android:fillType="evenOdd"
+        android:pathData="M142.88,301.08m-42.33,0a42.33,42.33 69.77,1 1,84.67 0a42.33,42.33 69.77,1 1,-84.67 0"
         android:strokeColor="#00000000"
-        android:fillAlpha="1"
-        android:fillColor="#ffffff"
-        android:strokeWidth="1.06666672"/>
+        android:fillColor="#0e518d"/>
     <path
-        android:pathData="M227.37,391.87l6.86,-9.59l37.36,26.6l-6.86,9.59z"
-        android:fillType="evenOdd"
+        android:pathData="m131.65,282.69 l-10.98,7.52 9.06,6.2L113.25,296.41l0.3,1.93L111.83,304.94l0,8.44l5.23,0l0.45,-2.03l17.69,0l0.45,2.03l14.43,0l0.45,-2.03l17.69,0l0.45,2.03L173.92,313.38L173.92,304.94l-1.73,-6.61 0.31,-1.93L133.57,296.41l9.05,-6.2zM131.65,284.42 L140.09,290.2l0,0.02l-8.44,5.79 -8.44,-5.79l0,-0.02zM114.62,298.33L119.3,298.33L119.3,304.94L112.9,304.94ZM120.52,298.33L128.45,298.33L128.45,304.94L120.52,304.94ZM129.67,298.33l7.93,0l0,6.61l-7.93,0zM138.81,298.33l8.13,0L146.94,304.94L138.81,304.94ZM148.16,298.33l7.93,0l0,6.61l-7.93,0zM157.31,298.33l7.93,0l0,6.61l-7.93,0zM166.45,298.33l4.67,0l1.72,6.61L166.45,304.94ZM119.14,312.66l0,0.69c0,1.51 1.22,2.74 2.74,2.74 1.21,0 2.25,-0.79 2.6,-1.89l3.82,0c0.36,1.1 1.39,1.89 2.6,1.89 1.51,0 2.74,-1.22 2.74,-2.74l-0.01,-0.69L119.14,312.66ZM152.12,312.66 L152.11,313.36c0,1.51 1.22,2.74 2.74,2.74 1.21,0 2.25,-0.79 2.6,-1.89l3.82,0c0.36,1.1 1.39,1.89 2.6,1.89 1.51,0 2.74,-1.22 2.74,-2.74l0,-0.69l-14.49,0z"
         android:strokeColor="#00000000"
-        android:fillAlpha="1"
-        android:fillColor="#ffffff"
-        android:strokeWidth="1.06666672"/>
+        android:fillColor="#ffffff"/>
     <path
-        android:pathData="m242.98,155.61v32.85h27.21v-33.83h4.92L271.5,22.16L156.09,22.16l-3.61,132.47h4.92v33.83h27.21v-32.85zM260.68,35.24 L262.65,85.61h-99.01l1.97,-50.37z"
-        android:fillAlpha="1"
-        android:fillType="evenOdd"
-        android:strokeColor="#00000000"
-        android:fillColor="#ffffff"
-        android:strokeWidth="1.09682584"/>
-    <path
-        android:pathData="m151.43,218.53h17.63c3.49,0 6.09,0.16 7.81,0.49 1.71,0.33 3.25,1.01 4.6,2.06 1.35,1.04 2.48,2.43 3.38,4.16 0.9,1.73 1.35,3.68 1.35,5.83 -0,2.33 -0.56,4.47 -1.67,6.42 -1.11,1.95 -2.62,3.41 -4.53,4.38 2.69,0.88 4.75,2.39 6.2,4.52 1.44,2.13 2.17,4.63 2.17,7.51 -0,2.27 -0.47,4.47 -1.4,6.61 -0.93,2.14 -2.21,3.85 -3.82,5.13 -1.61,1.28 -3.61,2.07 -5.97,2.36 -1.48,0.18 -5.06,0.29 -10.74,0.34h-15.01zM160.34,226.82v11.52h5.84c3.47,0 5.63,-0.06 6.47,-0.17 1.52,-0.2 2.72,-0.8 3.6,-1.78 0.87,-0.99 1.31,-2.28 1.31,-3.89 -0,-1.54 -0.38,-2.79 -1.13,-3.76 -0.75,-0.96 -1.87,-1.55 -3.35,-1.75 -0.88,-0.11 -3.42,-0.17 -7.61,-0.17zM160.34,246.64v13.32h8.24c3.21,0 5.24,-0.1 6.11,-0.31 1.32,-0.27 2.4,-0.93 3.23,-1.99 0.83,-1.05 1.25,-2.46 1.25,-4.23 -0,-1.5 -0.32,-2.76 -0.96,-3.81 -0.64,-1.04 -1.57,-1.8 -2.78,-2.28 -1.21,-0.48 -3.85,-0.71 -7.9,-0.71zM195.87,218.53h8.91v26.99c0,4.28 0.11,7.06 0.33,8.33 0.38,2.04 1.29,3.68 2.72,4.91 1.43,1.23 3.39,1.85 5.88,1.85 2.53,0 4.43,-0.58 5.72,-1.75 1.28,-1.17 2.06,-2.6 2.32,-4.3 0.26,-1.7 0.39,-4.52 0.39,-8.46v-27.56h8.91v26.17c-0,5.98 -0.24,10.21 -0.72,12.68 -0.48,2.47 -1.37,4.55 -2.66,6.25 -1.29,1.7 -3.02,3.05 -5.19,4.06 -2.17,1.01 -4.99,1.51 -8.48,1.51 -4.21,0 -7.41,-0.55 -9.58,-1.65 -2.18,-1.1 -3.9,-2.53 -5.16,-4.28 -1.26,-1.76 -2.1,-3.6 -2.5,-5.52 -0.58,-2.85 -0.87,-7.07 -0.87,-12.64zM238.2,252.14 L246.86,251.19c0.52,3.29 1.58,5.7 3.17,7.24 1.59,1.54 3.75,2.31 6.45,2.31 2.87,0 5.03,-0.69 6.48,-2.06 1.45,-1.37 2.18,-2.97 2.18,-4.81 0,-1.18 -0.31,-2.18 -0.92,-3.01 -0.61,-0.83 -1.68,-1.55 -3.2,-2.16 -1.04,-0.41 -3.42,-1.13 -7.13,-2.18 -4.77,-1.34 -8.12,-2.98 -10.05,-4.93 -2.71,-2.74 -4.06,-6.08 -4.06,-10.03 0,-2.54 0.64,-4.91 1.91,-7.12 1.27,-2.21 3.11,-3.89 5.51,-5.05 2.4,-1.16 5.29,-1.73 8.68,-1.73 5.54,0 9.7,1.37 12.5,4.11 2.8,2.74 4.27,6.4 4.41,10.98l-8.91,0.44c-0.38,-2.56 -1.2,-4.4 -2.45,-5.52 -1.25,-1.12 -3.13,-1.68 -5.64,-1.68 -2.59,0 -4.61,0.6 -6.08,1.8 -0.94,0.77 -1.41,1.8 -1.41,3.09 -0,1.18 0.44,2.19 1.32,3.02 1.12,1.06 3.85,2.18 8.18,3.33 4.33,1.16 7.54,2.35 9.61,3.59 2.08,1.23 3.7,2.92 4.87,5.06 1.17,2.14 1.76,4.79 1.76,7.94 -0,2.85 -0.7,5.53 -2.11,8.02 -1.4,2.49 -3.39,4.34 -5.96,5.56 -2.57,1.21 -5.77,1.82 -9.6,1.82 -5.58,0 -9.86,-1.46 -12.85,-4.37 -2.99,-2.91 -4.77,-7.15 -5.36,-12.73z"
-        android:fillAlpha="1"
-        android:strokeColor="#00000000"
-        android:fillColor="#ffffff"
-        android:strokeWidth="1.09682584"/>
+        android:pathData="M142.88,301.08m-40.06,0a40.06,40.06 59.4,1 1,80.12 0a40.06,40.06 82.72,1 1,-80.12 0"
+        android:strokeColor="#f7fbf5"
+        android:fillColor="#00000000"
+        android:strokeWidth="6.73600006"/>
 </vector>




diff --git a/app/src/main/res/layout/activity_nodb.xml b/app/src/main/res/layout/activity_nodb.xml
index f0993ec7fbeada126ebe5beb61b07c5c98369970..287aaa941d98a4342927ca898de4e9840ee48e38 100644
--- a/app/src/main/res/layout/activity_nodb.xml
+++ b/app/src/main/res/layout/activity_nodb.xml
@@ -1,14 +1,37 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical" android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@drawable/nodb">
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:contentDescription="@string/no_database_background"
+        android:scaleType="centerCrop"
+        app:srcCompat="@drawable/nodb" />
 
-    <android.support.v7.widget.Toolbar
-        android:id="@+id/toolbar2"
+    <android.support.constraint.ConstraintLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="?attr/colorPrimary"
-        android:minHeight="?attr/actionBarSize"
-        android:theme="?attr/actionBarTheme" />
-</LinearLayout>
\ No newline at end of file
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/noDbCaption"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            android:text=""
+            android:textAlignment="center"
+            android:textAppearance="@style/TextAppearance.AppCompat.Headline"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintVertical_bias="0.4" />
+    </android.support.constraint.ConstraintLayout>
+</FrameLayout>




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bb01817c79fd103271ac9c942e44522b7e89ef1d..a56f3fca135b861588ef4cdbc093a4017ccc9e19 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -11,4 +11,8 @@     departure type (timetable, VM)
     <string name="departure_in">In %1$s minutes</string>
     <string name="departure_to">→ %1$s</string>
     <string name="departure_at">At %1$s</string>
+    <string name="no_database_background">no database background</string>
+    <string name="no_db_connect">Connect to the Internet to download the timetable</string>
+    <string name="no_db_downloading">Timetable is being downloaded</string>
+    <string name="timetable_downloading">Downloading timetable</string>
 </resources>