From 7bdec40543a3358bd033d34a8d25c94078888749 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Thu, 19 Apr 2018 19:50:04 +0200
Subject: [PATCH] Implement SSL whitelist UI

---
 app/src/main/AndroidManifest.xml              | 20 ++--
 .../quasseldroid/dagger/ActivityModule.kt     | 37 ++++---
 .../quasseldroid/service/QuasselService.kt    |  6 +-
 .../quasseldroid/ui/chat/ChatActivity.kt      |  6 +-
 ...utSettingsActivity.kt => AboutActivity.kt} |  4 +-
 ...utSettingsFragment.kt => AboutFragment.kt} |  4 +-
 ...ntProvider.kt => AboutFragmentProvider.kt} |  4 +-
 .../ui/clientsettings/about/LibraryAdapter.kt |  8 +-
 .../app/AppSettingsFragmentProvider.kt        | 10 --
 .../client/ClientSettingsActivity.kt          | 12 +++
 .../ClientSettingsFragment.kt}                | 25 +++--
 .../client/ClientSettingsFragmentProvider.kt  | 10 ++
 ...shSettingsActivity.kt => CrashActivity.kt} |  4 +-
 ...shSettingsFragment.kt => CrashFragment.kt} |  6 +-
 ...ntProvider.kt => CrashFragmentProvider.kt} |  4 +-
 ...SettingsActivity.kt => LicenseActivity.kt} |  4 +-
 ...SettingsFragment.kt => LicenseFragment.kt} |  4 +-
 ...Provider.kt => LicenseFragmentProvider.kt} |  4 +-
 .../ui/clientsettings/whitelist/Whitelist.kt  |  8 ++
 .../WhitelistActivity.kt}                     |  6 +-
 .../whitelist/WhitelistCertificateAdapter.kt  | 97 ++++++++++++++++++
 .../whitelist/WhitelistFragment.kt            | 99 +++++++++++++++++++
 .../whitelist/WhitelistFragmentProvider.kt    | 10 ++
 .../whitelist/WhitelistHostnameAdapter.kt     | 90 +++++++++++++++++
 .../quasseldroid/ui/setup/SetupActivity.kt    | 17 ++--
 .../res/layout-sw720dp/activity_settings.xml  |  2 +-
 app/src/main/res/layout/layout_editor.xml     |  5 +-
 ...agment_about.xml => preferences_about.xml} |  4 +-
 ...eader.xml => preferences_about_header.xml} |  0
 ...agment_crash.xml => preferences_crash.xml} |  0
 ...nt_license.xml => preferences_license.xml} |  0
 .../main/res/layout/preferences_whitelist.xml | 41 ++++++++
 ...preferences_whitelist_certificate_item.xml | 55 +++++++++++
 .../preferences_whitelist_hostname_item.xml   | 55 +++++++++++
 .../main/res/layout/widget_contributor.xml    |  2 +-
 app/src/main/res/layout/widget_library.xml    |  2 +-
 app/src/main/res/menu/activity_settings.xml   |  3 +
 app/src/main/res/menu/activity_setup.xml      |  3 +
 app/src/main/res/values-de/strings.xml        |  4 +
 app/src/main/res/values-de/strings_error.xml  | 12 ++-
 .../main/res/values-v17/styles_widgets.xml    | 14 +++
 app/src/main/res/values/strings.xml           |  4 +
 app/src/main/res/values/strings_error.xml     | 11 ++-
 app/src/main/res/values/styles_widgets.xml    | 12 +++
 .../libquassel/session/SessionManager.kt      | 13 ++-
 .../persistence/QuasselDatabase.kt            | 12 +++
 .../viewmodel/QuasselViewModel.kt             |  6 +-
 47 files changed, 664 insertions(+), 95 deletions(-)
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/{AboutSettingsActivity.kt => AboutActivity.kt} (63%)
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/{AboutSettingsFragment.kt => AboutFragment.kt} (98%)
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/{AboutSettingsFragmentProvider.kt => AboutFragmentProvider.kt} (59%)
 delete mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragmentProvider.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsActivity.kt
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/{app/AppSettingsFragment.kt => client/ClientSettingsFragment.kt} (74%)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragmentProvider.kt
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/{CrashSettingsActivity.kt => CrashActivity.kt} (63%)
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/{CrashSettingsFragment.kt => CrashFragment.kt} (94%)
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/{CrashSettingsFragmentProvider.kt => CrashFragmentProvider.kt} (60%)
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/{LicenseSettingsActivity.kt => LicenseActivity.kt} (80%)
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/{LicenseSettingsFragment.kt => LicenseFragment.kt} (85%)
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/{LicenseSettingsFragmentProvider.kt => LicenseFragmentProvider.kt} (58%)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/Whitelist.kt
 rename app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/{app/AppSettingsActivity.kt => whitelist/WhitelistActivity.kt} (51%)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistCertificateAdapter.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistFragment.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistFragmentProvider.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistHostnameAdapter.kt
 rename app/src/main/res/layout/{fragment_about.xml => preferences_about.xml} (96%)
 rename app/src/main/res/layout/{layout_about_header.xml => preferences_about_header.xml} (100%)
 rename app/src/main/res/layout/{fragment_crash.xml => preferences_crash.xml} (100%)
 rename app/src/main/res/layout/{fragment_license.xml => preferences_license.xml} (100%)
 create mode 100644 app/src/main/res/layout/preferences_whitelist.xml
 create mode 100644 app/src/main/res/layout/preferences_whitelist_certificate_item.xml
 create mode 100644 app/src/main/res/layout/preferences_whitelist_hostname_item.xml

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 49809b1aa..ace3e96d6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -160,28 +160,34 @@
 
     <!-- Client Settings -->
     <activity
-      android:name=".ui.clientsettings.app.AppSettingsActivity"
+      android:name=".ui.clientsettings.client.ClientSettingsActivity"
       android:exported="false"
       android:label="@string/label_settings_client"
       android:parentActivityName=".ui.chat.ChatActivity"
       android:windowSoftInputMode="adjustResize" />
     <activity
-      android:name=".ui.clientsettings.crash.CrashSettingsActivity"
+      android:name=".ui.clientsettings.crash.CrashActivity"
       android:exported="false"
       android:label="@string/label_crashes"
-      android:parentActivityName=".ui.clientsettings.app.AppSettingsActivity"
+      android:parentActivityName=".ui.clientsettings.client.ClientSettingsActivity"
       android:windowSoftInputMode="adjustResize" />
     <activity
-      android:name=".ui.clientsettings.about.AboutSettingsActivity"
+      android:name=".ui.clientsettings.whitelist.WhitelistActivity"
+      android:exported="false"
+      android:label="@string/label_certificates"
+      android:parentActivityName=".ui.clientsettings.client.ClientSettingsActivity"
+      android:windowSoftInputMode="adjustResize" />
+    <activity
+      android:name=".ui.clientsettings.about.AboutActivity"
       android:exported="false"
       android:label="@string/label_about"
-      android:parentActivityName=".ui.clientsettings.app.AppSettingsActivity"
+      android:parentActivityName=".ui.clientsettings.client.ClientSettingsActivity"
       android:windowSoftInputMode="adjustResize" />
     <activity
-      android:name=".ui.clientsettings.license.LicenseSettingsActivity"
+      android:name=".ui.clientsettings.license.LicenseActivity"
       android:exported="false"
       android:label="@string/label_license"
-      android:parentActivityName=".ui.clientsettings.about.AboutSettingsActivity"
+      android:parentActivityName=".ui.clientsettings.about.AboutActivity"
       android:windowSoftInputMode="adjustResize" />
 
     <!-- Client Setup Flow -->
diff --git a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt
index 62aa64020..9a281ef66 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt
@@ -12,14 +12,16 @@ import de.kuschku.quasseldroid.ui.chat.info.user.UserInfoActivity
 import de.kuschku.quasseldroid.ui.chat.info.user.UserInfoFragmentProvider
 import de.kuschku.quasseldroid.ui.chat.topic.TopicActivity
 import de.kuschku.quasseldroid.ui.chat.topic.TopicFragmentProvider
-import de.kuschku.quasseldroid.ui.clientsettings.about.AboutSettingsActivity
-import de.kuschku.quasseldroid.ui.clientsettings.about.AboutSettingsFragmentProvider
-import de.kuschku.quasseldroid.ui.clientsettings.app.AppSettingsActivity
-import de.kuschku.quasseldroid.ui.clientsettings.app.AppSettingsFragmentProvider
-import de.kuschku.quasseldroid.ui.clientsettings.crash.CrashSettingsActivity
-import de.kuschku.quasseldroid.ui.clientsettings.crash.CrashSettingsFragmentProvider
-import de.kuschku.quasseldroid.ui.clientsettings.license.LicenseSettingsActivity
-import de.kuschku.quasseldroid.ui.clientsettings.license.LicenseSettingsFragmentProvider
+import de.kuschku.quasseldroid.ui.clientsettings.about.AboutActivity
+import de.kuschku.quasseldroid.ui.clientsettings.about.AboutFragmentProvider
+import de.kuschku.quasseldroid.ui.clientsettings.client.ClientSettingsActivity
+import de.kuschku.quasseldroid.ui.clientsettings.client.ClientSettingsFragmentProvider
+import de.kuschku.quasseldroid.ui.clientsettings.crash.CrashActivity
+import de.kuschku.quasseldroid.ui.clientsettings.crash.CrashFragmentProvider
+import de.kuschku.quasseldroid.ui.clientsettings.license.LicenseActivity
+import de.kuschku.quasseldroid.ui.clientsettings.license.LicenseFragmentProvider
+import de.kuschku.quasseldroid.ui.clientsettings.whitelist.WhitelistActivity
+import de.kuschku.quasseldroid.ui.clientsettings.whitelist.WhitelistFragmentProvider
 import de.kuschku.quasseldroid.ui.coresettings.CoreSettingsActivity
 import de.kuschku.quasseldroid.ui.coresettings.CoreSettingsFragmentProvider
 import de.kuschku.quasseldroid.ui.coresettings.aliasitem.AliasItemActivity
@@ -70,17 +72,20 @@ abstract class ActivityModule {
   @ContributesAndroidInjector(modules = [TopicFragmentProvider::class])
   abstract fun bindTopicActivity(): TopicActivity
 
-  @ContributesAndroidInjector(modules = [AppSettingsFragmentProvider::class])
-  abstract fun bindAppSettingsActivity(): AppSettingsActivity
+  @ContributesAndroidInjector(modules = [ClientSettingsFragmentProvider::class])
+  abstract fun bindClientSettingsActivity(): ClientSettingsActivity
 
-  @ContributesAndroidInjector(modules = [CrashSettingsFragmentProvider::class])
-  abstract fun bindCrashSettingsActivity(): CrashSettingsActivity
+  @ContributesAndroidInjector(modules = [WhitelistFragmentProvider::class])
+  abstract fun bindWhitelistActivity(): WhitelistActivity
 
-  @ContributesAndroidInjector(modules = [AboutSettingsFragmentProvider::class])
-  abstract fun bindAboutSettingsActivity(): AboutSettingsActivity
+  @ContributesAndroidInjector(modules = [CrashFragmentProvider::class])
+  abstract fun bindCrashActivity(): CrashActivity
 
-  @ContributesAndroidInjector(modules = [LicenseSettingsFragmentProvider::class])
-  abstract fun bindLicenseSettingsActivity(): LicenseSettingsActivity
+  @ContributesAndroidInjector(modules = [AboutFragmentProvider::class])
+  abstract fun bindAboutActivity(): AboutActivity
+
+  @ContributesAndroidInjector(modules = [LicenseFragmentProvider::class])
+  abstract fun bindLicenseActivity(): LicenseActivity
 
   @ContributesAndroidInjector(modules = [CoreSettingsFragmentProvider::class])
   abstract fun bindCoreSettingsActivity(): CoreSettingsActivity
diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
index 9908f5366..4a41bfaef 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
@@ -276,7 +276,8 @@ class QuasselService : DaggerLifecycleService(),
       .throttleFirst(1, TimeUnit.SECONDS)
       .toLiveData()
       .observe(this, Observer {
-        if (wasEverConnected) sessionManager.reconnect(true)
+        if (wasEverConnected && !sessionManager.hasErrored)
+          sessionManager.reconnect(true)
       })
 
     sessionManager.state
@@ -287,7 +288,8 @@ class QuasselService : DaggerLifecycleService(),
       .observe(
         this, Observer {
         if (it == ConnectionState.DISCONNECTED || it == ConnectionState.CLOSED) {
-          if (wasEverConnected) sessionManager.reconnect()
+          if (wasEverConnected && !sessionManager.hasErrored)
+            sessionManager.reconnect()
         } else {
           wasEverConnected = true
         }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
index 82b9bff21..883a9e4be 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
@@ -39,7 +39,7 @@ import de.kuschku.quasseldroid.settings.MessageSettings
 import de.kuschku.quasseldroid.settings.Settings
 import de.kuschku.quasseldroid.ui.chat.input.AutoCompleteAdapter
 import de.kuschku.quasseldroid.ui.chat.input.ChatlineFragment
-import de.kuschku.quasseldroid.ui.clientsettings.app.AppSettingsActivity
+import de.kuschku.quasseldroid.ui.clientsettings.client.ClientSettingsActivity
 import de.kuschku.quasseldroid.ui.coresettings.CoreSettingsActivity
 import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
@@ -162,7 +162,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
 
     viewModel.errors.toLiveData().observe(this, Observer { error ->
-      error?.orNull()?.let {
+      error?.let {
         when (it) {
           is Error.HandshakeError -> it.message.let {
             when (it) {
@@ -567,7 +567,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       true
     }
     R.id.action_client_settings -> {
-      AppSettingsActivity.launch(this)
+      ClientSettingsActivity.launch(this)
       true
     }
     R.id.action_disconnect      -> {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutActivity.kt
similarity index 63%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsActivity.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutActivity.kt
index c13a3762c..0ca4b84fd 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutActivity.kt
@@ -4,9 +4,9 @@ import android.content.Context
 import android.content.Intent
 import de.kuschku.quasseldroid.util.ui.SettingsActivity
 
-class AboutSettingsActivity : SettingsActivity(AboutSettingsFragment()) {
+class AboutActivity : SettingsActivity(AboutFragment()) {
   companion object {
     fun launch(context: Context) = context.startActivity(intent(context))
-    fun intent(context: Context) = Intent(context, AboutSettingsActivity::class.java)
+    fun intent(context: Context) = Intent(context, AboutActivity::class.java)
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
similarity index 98%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsFragment.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
index 204297e59..136dc02dc 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
@@ -18,7 +18,7 @@ import dagger.android.support.DaggerFragment
 import de.kuschku.quasseldroid.BuildConfig
 import de.kuschku.quasseldroid.R
 
-class AboutSettingsFragment : DaggerFragment() {
+class AboutFragment : DaggerFragment() {
 
   @BindView(R.id.version)
   lateinit var version: TextView
@@ -40,7 +40,7 @@ class AboutSettingsFragment : DaggerFragment() {
 
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {
-    val view = inflater.inflate(R.layout.fragment_about, container, false)
+    val view = inflater.inflate(R.layout.preferences_about, container, false)
     ButterKnife.bind(this, view)
 
     version.text = BuildConfig.VERSION_NAME
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragmentProvider.kt
similarity index 59%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsFragmentProvider.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragmentProvider.kt
index 87c2b85f9..1f05d0a60 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutSettingsFragmentProvider.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragmentProvider.kt
@@ -4,7 +4,7 @@ import dagger.Module
 import dagger.android.ContributesAndroidInjector
 
 @Module
-abstract class AboutSettingsFragmentProvider {
+abstract class AboutFragmentProvider {
   @ContributesAndroidInjector
-  abstract fun bindAboutSettingsFragment(): AboutSettingsFragment
+  abstract fun bindAboutFragment(): AboutFragment
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/LibraryAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/LibraryAdapter.kt
index 28c092fc8..c8a49d52c 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/LibraryAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/LibraryAdapter.kt
@@ -8,7 +8,7 @@ import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
 import de.kuschku.quasseldroid.R
-import de.kuschku.quasseldroid.ui.clientsettings.license.LicenseSettingsActivity
+import de.kuschku.quasseldroid.ui.clientsettings.license.LicenseActivity
 import de.kuschku.quasseldroid.util.helper.visibleIf
 
 class LibraryAdapter(private val libraries: List<Library>) :
@@ -39,9 +39,9 @@ class LibraryAdapter(private val libraries: List<Library>) :
       ButterKnife.bind(this, itemView)
       itemView.setOnClickListener {
         this.item?.run {
-          LicenseSettingsActivity.launch(itemView.context,
-                                         license_name = license.fullName,
-                                         license_text = license.text)
+          LicenseActivity.launch(itemView.context,
+                                 license_name = license.fullName,
+                                 license_text = license.text)
         }
       }
     }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragmentProvider.kt
deleted file mode 100644
index 237399cb3..000000000
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragmentProvider.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package de.kuschku.quasseldroid.ui.clientsettings.app
-
-import dagger.Module
-import dagger.android.ContributesAndroidInjector
-
-@Module
-abstract class AppSettingsFragmentProvider {
-  @ContributesAndroidInjector
-  abstract fun bindAppSettingsFragment(): AppSettingsFragment
-}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsActivity.kt
new file mode 100644
index 000000000..af04881f2
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsActivity.kt
@@ -0,0 +1,12 @@
+package de.kuschku.quasseldroid.ui.clientsettings.client
+
+import android.content.Context
+import android.content.Intent
+import de.kuschku.quasseldroid.util.ui.SettingsActivity
+
+class ClientSettingsActivity : SettingsActivity(ClientSettingsFragment()) {
+  companion object {
+    fun launch(context: Context) = context.startActivity(intent(context))
+    fun intent(context: Context) = Intent(context, ClientSettingsActivity::class.java)
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragment.kt
similarity index 74%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragment.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragment.kt
index db3ce7a5a..2894d1923 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragment.kt
@@ -1,4 +1,4 @@
-package de.kuschku.quasseldroid.ui.clientsettings.app
+package de.kuschku.quasseldroid.ui.clientsettings.client
 
 import android.content.SharedPreferences
 import android.os.Bundle
@@ -11,13 +11,14 @@ import android.view.MenuItem
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.Settings
-import de.kuschku.quasseldroid.ui.clientsettings.about.AboutSettingsActivity
-import de.kuschku.quasseldroid.ui.clientsettings.crash.CrashSettingsActivity
+import de.kuschku.quasseldroid.ui.clientsettings.about.AboutActivity
+import de.kuschku.quasseldroid.ui.clientsettings.crash.CrashActivity
+import de.kuschku.quasseldroid.ui.clientsettings.whitelist.WhitelistActivity
 import de.kuschku.quasseldroid.util.backport.DaggerPreferenceFragmentCompat
 import javax.inject.Inject
 
-class AppSettingsFragment : DaggerPreferenceFragmentCompat(),
-                            SharedPreferences.OnSharedPreferenceChangeListener {
+class ClientSettingsFragment : DaggerPreferenceFragmentCompat(),
+                               SharedPreferences.OnSharedPreferenceChangeListener {
   @Inject
   lateinit var appearanceSettings: AppearanceSettings
 
@@ -68,14 +69,18 @@ class AppSettingsFragment : DaggerPreferenceFragmentCompat(),
   }
 
   override fun onOptionsItemSelected(item: MenuItem?) = when (item?.itemId) {
-    R.id.action_about   -> {
-      AboutSettingsActivity.launch(requireContext())
+    R.id.action_certificates -> {
+      WhitelistActivity.launch(requireContext())
       true
     }
-    R.id.action_crashes -> {
-      CrashSettingsActivity.launch(requireContext())
+    R.id.action_crashes      -> {
+      CrashActivity.launch(requireContext())
       true
     }
-    else                -> super.onOptionsItemSelected(item)
+    R.id.action_about        -> {
+      AboutActivity.launch(requireContext())
+      true
+    }
+    else                     -> super.onOptionsItemSelected(item)
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragmentProvider.kt
new file mode 100644
index 000000000..f7a0672ce
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/client/ClientSettingsFragmentProvider.kt
@@ -0,0 +1,10 @@
+package de.kuschku.quasseldroid.ui.clientsettings.client
+
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+
+@Module
+abstract class ClientSettingsFragmentProvider {
+  @ContributesAndroidInjector
+  abstract fun bindClientSettingsFragment(): ClientSettingsFragment
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashActivity.kt
similarity index 63%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsActivity.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashActivity.kt
index 6a17c61cc..e8d9992cf 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashActivity.kt
@@ -4,9 +4,9 @@ import android.content.Context
 import android.content.Intent
 import de.kuschku.quasseldroid.util.ui.SettingsActivity
 
-class CrashSettingsActivity : SettingsActivity(CrashSettingsFragment()) {
+class CrashActivity : SettingsActivity(CrashFragment()) {
   companion object {
     fun launch(context: Context) = context.startActivity(intent(context))
-    fun intent(context: Context) = Intent(context, CrashSettingsActivity::class.java)
+    fun intent(context: Context) = Intent(context, CrashActivity::class.java)
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt
similarity index 94%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsFragment.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt
index 55f1cd5bf..2f00c2c5d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragment.kt
@@ -18,7 +18,7 @@ import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.util.helper.fromJson
 import java.io.File
 
-class CrashSettingsFragment : DaggerFragment() {
+class CrashFragment : DaggerFragment() {
   @BindView(R.id.list)
   lateinit var list: RecyclerView
 
@@ -31,7 +31,7 @@ class CrashSettingsFragment : DaggerFragment() {
 
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
-    handlerThread = HandlerThread("CrashSettings")
+    handlerThread = HandlerThread("Crash")
     handlerThread.start()
     handler = Handler(handlerThread.looper)
   }
@@ -43,7 +43,7 @@ class CrashSettingsFragment : DaggerFragment() {
 
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {
-    val view = inflater.inflate(R.layout.fragment_crash, container, false)
+    val view = inflater.inflate(R.layout.preferences_crash, container, false)
     ButterKnife.bind(this, view)
 
     setHasOptionsMenu(true)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragmentProvider.kt
similarity index 60%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsFragmentProvider.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragmentProvider.kt
index 8c54633c4..4e6b4c456 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashSettingsFragmentProvider.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/crash/CrashFragmentProvider.kt
@@ -4,7 +4,7 @@ import dagger.Module
 import dagger.android.ContributesAndroidInjector
 
 @Module
-abstract class CrashSettingsFragmentProvider {
+abstract class CrashFragmentProvider {
   @ContributesAndroidInjector
-  abstract fun bindAppSettingsFragment(): CrashSettingsFragment
+  abstract fun bindClientSettingsFragment(): CrashFragment
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseActivity.kt
similarity index 80%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsActivity.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseActivity.kt
index 8b4d4b604..d514d01d1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseActivity.kt
@@ -5,7 +5,7 @@ import android.content.Intent
 import android.support.annotation.StringRes
 import de.kuschku.quasseldroid.util.ui.SettingsActivity
 
-class LicenseSettingsActivity : SettingsActivity(LicenseSettingsFragment()) {
+class LicenseActivity : SettingsActivity(LicenseFragment()) {
   companion object {
     fun launch(
       context: Context,
@@ -17,7 +17,7 @@ class LicenseSettingsActivity : SettingsActivity(LicenseSettingsFragment()) {
       context: Context,
       license_name: String,
       @StringRes license_text: Int
-    ) = Intent(context, LicenseSettingsActivity::class.java).apply {
+    ) = Intent(context, LicenseActivity::class.java).apply {
       putExtra("license_name", license_name)
       putExtra("license_text", license_text)
     }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseFragment.kt
similarity index 85%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsFragment.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseFragment.kt
index a13da2823..c5fe5f15b 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseFragment.kt
@@ -11,13 +11,13 @@ import butterknife.ButterKnife
 import dagger.android.support.DaggerFragment
 import de.kuschku.quasseldroid.R
 
-class LicenseSettingsFragment : DaggerFragment() {
+class LicenseFragment : DaggerFragment() {
   @BindView(R.id.text)
   lateinit var text: TextView
 
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {
-    val view = inflater.inflate(R.layout.fragment_license, container, false)
+    val view = inflater.inflate(R.layout.preferences_license, container, false)
     ButterKnife.bind(this, view)
 
     val textResource = arguments?.getInt("license_text", 0) ?: 0
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseFragmentProvider.kt
similarity index 58%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsFragmentProvider.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseFragmentProvider.kt
index c1f290046..930478f73 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseSettingsFragmentProvider.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/license/LicenseFragmentProvider.kt
@@ -4,7 +4,7 @@ import dagger.Module
 import dagger.android.ContributesAndroidInjector
 
 @Module
-abstract class LicenseSettingsFragmentProvider {
+abstract class LicenseFragmentProvider {
   @ContributesAndroidInjector
-  abstract fun bindLicenseSettingsFragment(): LicenseSettingsFragment
+  abstract fun bindLicenseFragment(): LicenseFragment
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/Whitelist.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/Whitelist.kt
new file mode 100644
index 000000000..fdf38f2d8
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/Whitelist.kt
@@ -0,0 +1,8 @@
+package de.kuschku.quasseldroid.ui.clientsettings.whitelist
+
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+
+data class Whitelist(
+  val certificates: List<QuasselDatabase.SslValidityWhitelistEntry>,
+  val hostnames: List<QuasselDatabase.SslHostnameWhitelistEntry>
+)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistActivity.kt
similarity index 51%
rename from app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsActivity.kt
rename to app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistActivity.kt
index b42b38709..c420a4106 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/app/AppSettingsActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistActivity.kt
@@ -1,12 +1,12 @@
-package de.kuschku.quasseldroid.ui.clientsettings.app
+package de.kuschku.quasseldroid.ui.clientsettings.whitelist
 
 import android.content.Context
 import android.content.Intent
 import de.kuschku.quasseldroid.util.ui.SettingsActivity
 
-class AppSettingsActivity : SettingsActivity(AppSettingsFragment()) {
+class WhitelistActivity : SettingsActivity(WhitelistFragment()) {
   companion object {
     fun launch(context: Context) = context.startActivity(intent(context))
-    fun intent(context: Context) = Intent(context, AppSettingsActivity::class.java)
+    fun intent(context: Context) = Intent(context, WhitelistActivity::class.java)
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistCertificateAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistCertificateAdapter.kt
new file mode 100644
index 000000000..3c408718c
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistCertificateAdapter.kt
@@ -0,0 +1,97 @@
+package de.kuschku.quasseldroid.ui.clientsettings.whitelist
+
+import android.support.v7.widget.AppCompatImageButton
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import butterknife.BindView
+import butterknife.ButterKnife
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+import de.kuschku.quasseldroid.util.helper.visibleIf
+
+class WhitelistCertificateAdapter :
+  RecyclerView.Adapter<WhitelistCertificateAdapter.WhitelistItemViewHolder>() {
+  private var clickListener: ((QuasselDatabase.SslValidityWhitelistEntry) -> Unit)? = null
+
+  fun setOnClickListener(listener: ((QuasselDatabase.SslValidityWhitelistEntry) -> Unit)?) {
+    clickListener = listener
+  }
+
+  private val data = mutableListOf<QuasselDatabase.SslValidityWhitelistEntry>()
+  var list: List<QuasselDatabase.SslValidityWhitelistEntry>
+    get() = data
+    set(value) {
+      val length = data.size
+      data.clear()
+      notifyItemRangeRemoved(0, length)
+      data.addAll(value)
+      notifyItemRangeInserted(0, list.size)
+    }
+
+  fun add(item: QuasselDatabase.SslValidityWhitelistEntry) {
+    val index = data.size
+    data.add(item)
+    notifyItemInserted(index)
+  }
+
+  fun replace(index: Int, item: QuasselDatabase.SslValidityWhitelistEntry) {
+    data[index] = item
+    notifyItemChanged(index)
+  }
+
+  fun indexOf(item: QuasselDatabase.SslValidityWhitelistEntry) = data.indexOf(item)
+
+  fun remove(index: Int) {
+    data.removeAt(index)
+    notifyItemRemoved(index)
+  }
+
+  fun remove(item: QuasselDatabase.SslValidityWhitelistEntry) = remove(indexOf(item))
+
+  override fun getItemCount() = data.size
+
+  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = WhitelistItemViewHolder(
+    LayoutInflater.from(parent.context).inflate(R.layout.preferences_whitelist_certificate_item,
+                                                parent,
+                                                false),
+    ::remove
+  )
+
+  override fun onBindViewHolder(holder: WhitelistItemViewHolder, position: Int) {
+    holder.bind(data[position])
+  }
+
+  class WhitelistItemViewHolder(
+    itemView: View,
+    clickListener: ((QuasselDatabase.SslValidityWhitelistEntry) -> Unit)?
+  ) : RecyclerView.ViewHolder(itemView) {
+    @BindView(R.id.fingerprint)
+    lateinit var fingerprint: TextView
+
+    @BindView(R.id.ignore_date)
+    lateinit var ignoreDate: View
+
+    @BindView(R.id.action_delete)
+    lateinit var delete: AppCompatImageButton
+
+    private var item: QuasselDatabase.SslValidityWhitelistEntry? = null
+
+    init {
+      ButterKnife.bind(this, itemView)
+      delete.setOnClickListener {
+        item?.let {
+          clickListener?.invoke(it)
+        }
+      }
+    }
+
+    fun bind(item: QuasselDatabase.SslValidityWhitelistEntry) {
+      this.item = item
+      fingerprint.text = item.fingerprint
+      ignoreDate.visibleIf(item.ignoreDate)
+    }
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistFragment.kt
new file mode 100644
index 000000000..14a0315b0
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistFragment.kt
@@ -0,0 +1,99 @@
+package de.kuschku.quasseldroid.ui.clientsettings.whitelist
+
+import android.os.Bundle
+import android.os.Handler
+import android.os.HandlerThread
+import android.support.v4.view.ViewCompat
+import android.support.v7.widget.DividerItemDecoration
+import android.support.v7.widget.LinearLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import butterknife.BindView
+import butterknife.ButterKnife
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+import de.kuschku.quasseldroid.ui.coresettings.SettingsFragment
+import javax.inject.Inject
+
+class WhitelistFragment : SettingsFragment(), SettingsFragment.Changeable,
+                          SettingsFragment.Savable {
+  @BindView(R.id.certificate_whitelist)
+  lateinit var certificateList: RecyclerView
+
+  @BindView(R.id.hostname_whitelist)
+  lateinit var hostnameList: RecyclerView
+
+  @Inject
+  lateinit var database: QuasselDatabase
+
+  private var whitelist: Whitelist? = null
+
+  private lateinit var handlerThread: HandlerThread
+  private lateinit var handler: Handler
+
+  private var certificateAdapter = WhitelistCertificateAdapter()
+  private var hostnameAdapter = WhitelistHostnameAdapter()
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    handlerThread = HandlerThread("Crash")
+    handlerThread.start()
+    handler = Handler(handlerThread.looper)
+  }
+
+  override fun onDestroy() {
+    super.onDestroy()
+    handlerThread.quit()
+  }
+
+  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
+                            savedInstanceState: Bundle?): View? {
+    val view = inflater.inflate(R.layout.preferences_whitelist, container, false)
+    ButterKnife.bind(this, view)
+
+    setHasOptionsMenu(true)
+
+    certificateList.layoutManager = LinearLayoutManager(context)
+    certificateList.adapter = certificateAdapter
+    certificateList.addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
+    ViewCompat.setNestedScrollingEnabled(certificateList, false)
+
+    hostnameList.layoutManager = LinearLayoutManager(context)
+    hostnameList.adapter = hostnameAdapter
+    hostnameList.addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
+    ViewCompat.setNestedScrollingEnabled(hostnameList, false)
+
+    handler.post {
+      whitelist = Whitelist(database.validityWhitelist().all(), database.hostnameWhitelist().all())
+      whitelist?.let {
+        certificateAdapter.list = it.certificates
+        hostnameAdapter.list = it.hostnames
+      }
+    }
+    return view
+  }
+
+  fun applyChanges() = Whitelist(
+    certificateAdapter.list,
+    hostnameAdapter.list
+  )
+
+  override fun onSave() = whitelist?.let {
+    val data = applyChanges()
+    handler.post {
+      database.runInTransaction {
+        for (item in it.certificates - data.certificates) {
+          database.validityWhitelist().delete(item.fingerprint)
+        }
+        for (item in it.hostnames - data.hostnames) {
+          database.hostnameWhitelist().delete(item.fingerprint, item.hostname)
+        }
+      }
+    }
+    true
+  } ?: false
+
+  override fun hasChanged() = whitelist != applyChanges()
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistFragmentProvider.kt
new file mode 100644
index 000000000..90ae41197
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistFragmentProvider.kt
@@ -0,0 +1,10 @@
+package de.kuschku.quasseldroid.ui.clientsettings.whitelist
+
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+
+@Module
+abstract class WhitelistFragmentProvider {
+  @ContributesAndroidInjector
+  abstract fun bindWhitelistFragment(): WhitelistFragment
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistHostnameAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistHostnameAdapter.kt
new file mode 100644
index 000000000..569df654b
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/whitelist/WhitelistHostnameAdapter.kt
@@ -0,0 +1,90 @@
+package de.kuschku.quasseldroid.ui.clientsettings.whitelist
+
+import android.support.v7.widget.AppCompatImageButton
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import butterknife.BindView
+import butterknife.ButterKnife
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+
+class WhitelistHostnameAdapter :
+  RecyclerView.Adapter<WhitelistHostnameAdapter.WhitelistItemViewHolder>() {
+  private val data = mutableListOf<QuasselDatabase.SslHostnameWhitelistEntry>()
+  var list: List<QuasselDatabase.SslHostnameWhitelistEntry>
+    get() = data
+    set(value) {
+      val length = data.size
+      data.clear()
+      notifyItemRangeRemoved(0, length)
+      data.addAll(value)
+      notifyItemRangeInserted(0, list.size)
+    }
+
+  fun add(item: QuasselDatabase.SslHostnameWhitelistEntry) {
+    val index = data.size
+    data.add(item)
+    notifyItemInserted(index)
+  }
+
+  fun replace(index: Int, item: QuasselDatabase.SslHostnameWhitelistEntry) {
+    data[index] = item
+    notifyItemChanged(index)
+  }
+
+  fun indexOf(item: QuasselDatabase.SslHostnameWhitelistEntry) = data.indexOf(item)
+
+  fun remove(index: Int) {
+    data.removeAt(index)
+    notifyItemRemoved(index)
+  }
+
+  fun remove(item: QuasselDatabase.SslHostnameWhitelistEntry) = remove(indexOf(item))
+
+  override fun getItemCount() = data.size
+
+  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = WhitelistItemViewHolder(
+    LayoutInflater.from(parent.context).inflate(R.layout.preferences_whitelist_hostname_item,
+                                                parent,
+                                                false),
+    ::remove
+  )
+
+  override fun onBindViewHolder(holder: WhitelistItemViewHolder, position: Int) {
+    holder.bind(data[position])
+  }
+
+  class WhitelistItemViewHolder(
+    itemView: View,
+    clickListener: ((QuasselDatabase.SslHostnameWhitelistEntry) -> Unit)?
+  ) : RecyclerView.ViewHolder(itemView) {
+    @BindView(R.id.hostname)
+    lateinit var hostname: TextView
+
+    @BindView(R.id.fingerprint)
+    lateinit var fingerprint: TextView
+
+    @BindView(R.id.action_delete)
+    lateinit var delete: AppCompatImageButton
+
+    private var item: QuasselDatabase.SslHostnameWhitelistEntry? = null
+
+    init {
+      ButterKnife.bind(this, itemView)
+      delete.setOnClickListener {
+        item?.let {
+          clickListener?.invoke(it)
+        }
+      }
+    }
+
+    fun bind(item: QuasselDatabase.SslHostnameWhitelistEntry) {
+      this.item = item
+      hostname.text = item.hostname
+      fingerprint.text = item.fingerprint
+    }
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt
index 6eee73e76..370e27a00 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt
@@ -17,9 +17,10 @@ import butterknife.BindView
 import butterknife.ButterKnife
 import dagger.android.support.DaggerAppCompatActivity
 import de.kuschku.quasseldroid.R
-import de.kuschku.quasseldroid.ui.clientsettings.about.AboutSettingsActivity
-import de.kuschku.quasseldroid.ui.clientsettings.app.AppSettingsActivity
-import de.kuschku.quasseldroid.ui.clientsettings.crash.CrashSettingsActivity
+import de.kuschku.quasseldroid.ui.clientsettings.about.AboutActivity
+import de.kuschku.quasseldroid.ui.clientsettings.client.ClientSettingsActivity
+import de.kuschku.quasseldroid.ui.clientsettings.crash.CrashActivity
+import de.kuschku.quasseldroid.ui.clientsettings.whitelist.WhitelistActivity
 import de.kuschku.quasseldroid.util.helper.observeSticky
 import de.kuschku.quasseldroid.util.helper.or
 import de.kuschku.quasseldroid.util.helper.switchMap
@@ -92,15 +93,19 @@ abstract class SetupActivity : DaggerAppCompatActivity() {
     menuView.setOnMenuItemClickListener {
       when (it.itemId) {
         R.id.action_client_settings -> {
-          AppSettingsActivity.launch(this)
+          ClientSettingsActivity.launch(this)
+          true
+        }
+        R.id.action_certificates    -> {
+          WhitelistActivity.launch(this)
           true
         }
         R.id.action_crashes         -> {
-          CrashSettingsActivity.launch(this)
+          CrashActivity.launch(this)
           true
         }
         R.id.action_about           -> {
-          AboutSettingsActivity.launch(this)
+          AboutActivity.launch(this)
           true
         }
         else                        -> false
diff --git a/app/src/main/res/layout-sw720dp/activity_settings.xml b/app/src/main/res/layout-sw720dp/activity_settings.xml
index c1b350d69..43c3eeea2 100644
--- a/app/src/main/res/layout-sw720dp/activity_settings.xml
+++ b/app/src/main/res/layout-sw720dp/activity_settings.xml
@@ -41,7 +41,7 @@
         android:id="@+id/fragment_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        tools:layout="@layout/fragment_about" />
+        tools:layout="@layout/preferences_about" />
     </android.support.v7.widget.CardView>
 
   </LinearLayout>
diff --git a/app/src/main/res/layout/layout_editor.xml b/app/src/main/res/layout/layout_editor.xml
index 54c2793af..3bd7eb0ef 100644
--- a/app/src/main/res/layout/layout_editor.xml
+++ b/app/src/main/res/layout/layout_editor.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
+  xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
 
@@ -51,12 +52,14 @@
     android:layout_width="?attr/actionBarSize"
     android:layout_height="?attr/actionBarSize"
     android:layout_gravity="top"
+    android:autoMirrored="true"
     android:background="?attr/selectableItemBackgroundBorderless"
     android:padding="12dp"
     android:scaleType="fitXY"
     app:layout_constraintEnd_toEndOf="parent"
     app:srcCompat="@drawable/ic_send"
-    app:tint="?attr/colorAccent" />
+    app:tint="?attr/colorAccent"
+    tools:ignore="UnusedAttribute" />
 
   <de.kuschku.quasseldroid.util.ui.AutoCompleteRecyclerView
     android:id="@+id/autocomplete_list"
diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/preferences_about.xml
similarity index 96%
rename from app/src/main/res/layout/fragment_about.xml
rename to app/src/main/res/layout/preferences_about.xml
index 53c4d37b7..5e2048142 100644
--- a/app/src/main/res/layout/fragment_about.xml
+++ b/app/src/main/res/layout/preferences_about.xml
@@ -10,7 +10,7 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-    <include layout="@layout/layout_about_header" />
+    <include layout="@layout/preferences_about_header" />
 
     <LinearLayout
       android:layout_width="match_parent"
@@ -74,4 +74,4 @@
     </LinearLayout>
 
   </LinearLayout>
-</android.support.v4.widget.NestedScrollView>
\ No newline at end of file
+</android.support.v4.widget.NestedScrollView>
diff --git a/app/src/main/res/layout/layout_about_header.xml b/app/src/main/res/layout/preferences_about_header.xml
similarity index 100%
rename from app/src/main/res/layout/layout_about_header.xml
rename to app/src/main/res/layout/preferences_about_header.xml
diff --git a/app/src/main/res/layout/fragment_crash.xml b/app/src/main/res/layout/preferences_crash.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_crash.xml
rename to app/src/main/res/layout/preferences_crash.xml
diff --git a/app/src/main/res/layout/fragment_license.xml b/app/src/main/res/layout/preferences_license.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_license.xml
rename to app/src/main/res/layout/preferences_license.xml
diff --git a/app/src/main/res/layout/preferences_whitelist.xml b/app/src/main/res/layout/preferences_whitelist.xml
new file mode 100644
index 000000000..a2071bd68
--- /dev/null
+++ b/app/src/main/res/layout/preferences_whitelist.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:tools="http://schemas.android.com/tools"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:orientation="vertical">
+
+  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+      style="@style/Widget.Subhead"
+      android:text="@string/label_whitelist_certificates" />
+
+    <android.support.v7.widget.RecyclerView
+      android:id="@+id/certificate_whitelist"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingLeft="2dp"
+      android:paddingRight="2dp"
+      tools:itemCount="2"
+      tools:listitem="@layout/preferences_whitelist_certificate_item" />
+
+    <TextView
+      style="@style/Widget.Subhead"
+      android:text="@string/label_whitelist_hostnames" />
+
+    <android.support.v7.widget.RecyclerView
+      android:id="@+id/hostname_whitelist"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingLeft="2dp"
+      android:paddingRight="2dp"
+      tools:itemCount="3"
+      tools:listitem="@layout/preferences_whitelist_hostname_item" />
+
+  </LinearLayout>
+</android.support.v4.widget.NestedScrollView>
diff --git a/app/src/main/res/layout/preferences_whitelist_certificate_item.xml b/app/src/main/res/layout/preferences_whitelist_certificate_item.xml
new file mode 100644
index 000000000..d4ea0d821
--- /dev/null
+++ b/app/src/main/res/layout/preferences_whitelist_certificate_item.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout 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="wrap_content"
+  android:minHeight="?android:attr/listPreferredItemHeightSmall"
+  android:orientation="horizontal"
+  tools:showIn="@layout/preferences_whitelist">
+
+  <LinearLayout
+    android:layout_width="0dip"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:layout_weight="1"
+    android:orientation="vertical"
+    android:paddingBottom="16dp"
+    android:paddingEnd="0dip"
+    android:paddingLeft="?listPreferredItemPaddingLeft"
+    android:paddingRight="0dip"
+    android:paddingStart="?listPreferredItemPaddingLeft"
+    android:paddingTop="16dp">
+
+    <TextView
+      android:id="@+id/fingerprint"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:textAppearance="?android:textAppearanceMedium"
+      android:textColor="?colorTextPrimary"
+      android:textSize="16sp"
+      tools:text="65:F9:0D:04:8A:7B:4F:D8:C8:D8:75:8D:EC:48:8C:F2:96:86:00:44" />
+
+    <TextView
+      android:id="@+id/ignore_date"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/label_whitelist_ignore_date"
+      android:textAppearance="?textAppearanceListItemSecondary"
+      android:textColor="?colorTextSecondary" />
+
+  </LinearLayout>
+
+  <android.support.v7.widget.AppCompatImageButton
+    android:id="@+id/action_delete"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_vertical"
+    android:background="?selectableItemBackgroundBorderless"
+    android:paddingEnd="?listPreferredItemPaddingRight"
+    android:paddingLeft="32dp"
+    android:paddingRight="?listPreferredItemPaddingRight"
+    android:paddingStart="32dp"
+    app:srcCompat="@drawable/ic_delete"
+    app:tint="?colorTextSecondary" />
+</LinearLayout>
diff --git a/app/src/main/res/layout/preferences_whitelist_hostname_item.xml b/app/src/main/res/layout/preferences_whitelist_hostname_item.xml
new file mode 100644
index 000000000..f6ffa942d
--- /dev/null
+++ b/app/src/main/res/layout/preferences_whitelist_hostname_item.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout 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="wrap_content"
+  android:minHeight="?android:attr/listPreferredItemHeightSmall"
+  android:orientation="horizontal"
+  tools:showIn="@layout/preferences_whitelist">
+
+  <LinearLayout
+    android:layout_width="0dip"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_vertical"
+    android:layout_weight="1"
+    android:orientation="vertical"
+    android:paddingBottom="16dp"
+    android:paddingEnd="0dip"
+    android:paddingLeft="?listPreferredItemPaddingLeft"
+    android:paddingRight="0dip"
+    android:paddingStart="?listPreferredItemPaddingLeft"
+    android:paddingTop="16dp">
+
+    <TextView
+      android:id="@+id/hostname"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:textAppearance="?android:textAppearanceMedium"
+      android:textColor="?colorTextPrimary"
+      android:textSize="16sp"
+      tools:text="www.google.com" />
+
+    <TextView
+      android:id="@+id/fingerprint"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:textAppearance="?textAppearanceListItemSecondary"
+      android:textColor="?colorTextSecondary"
+      tools:text="65:F9:0D:04:8A:7B:4F:D8:C8:D8:75:8D:EC:48:8C:F2:96:86:00:44" />
+
+  </LinearLayout>
+
+  <android.support.v7.widget.AppCompatImageButton
+    android:id="@+id/action_delete"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_vertical"
+    android:background="?selectableItemBackgroundBorderless"
+    android:paddingEnd="?listPreferredItemPaddingRight"
+    android:paddingLeft="32dp"
+    android:paddingRight="?listPreferredItemPaddingRight"
+    android:paddingStart="32dp"
+    app:srcCompat="@drawable/ic_delete"
+    app:tint="?colorTextSecondary" />
+</LinearLayout>
diff --git a/app/src/main/res/layout/widget_contributor.xml b/app/src/main/res/layout/widget_contributor.xml
index 9df93a33b..de699de65 100644
--- a/app/src/main/res/layout/widget_contributor.xml
+++ b/app/src/main/res/layout/widget_contributor.xml
@@ -11,7 +11,7 @@
   android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
   android:paddingRight="?android:attr/listPreferredItemPaddingRight"
   android:paddingStart="?android:attr/listPreferredItemPaddingLeft"
-  tools:showIn="@layout/fragment_about">
+  tools:showIn="@layout/preferences_about">
 
   <LinearLayout
     android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/widget_library.xml b/app/src/main/res/layout/widget_library.xml
index 827a187eb..7ad732fc8 100644
--- a/app/src/main/res/layout/widget_library.xml
+++ b/app/src/main/res/layout/widget_library.xml
@@ -11,7 +11,7 @@
   android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
   android:paddingRight="?android:attr/listPreferredItemPaddingRight"
   android:paddingStart="?android:attr/listPreferredItemPaddingLeft"
-  tools:showIn="@layout/fragment_about">
+  tools:showIn="@layout/preferences_about">
 
   <LinearLayout
     android:layout_width="match_parent"
diff --git a/app/src/main/res/menu/activity_settings.xml b/app/src/main/res/menu/activity_settings.xml
index 391c758fa..85f5addf8 100644
--- a/app/src/main/res/menu/activity_settings.xml
+++ b/app/src/main/res/menu/activity_settings.xml
@@ -1,5 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
+  <item
+    android:id="@+id/action_certificates"
+    android:title="@string/label_certificates" />
   <item
     android:id="@+id/action_crashes"
     android:title="@string/label_crashes" />
diff --git a/app/src/main/res/menu/activity_setup.xml b/app/src/main/res/menu/activity_setup.xml
index 8e26096af..90461b687 100644
--- a/app/src/main/res/menu/activity_setup.xml
+++ b/app/src/main/res/menu/activity_setup.xml
@@ -3,6 +3,9 @@
   <item
     android:id="@+id/action_client_settings"
     android:title="@string/label_settings_client" />
+  <item
+    android:id="@+id/action_certificates"
+    android:title="@string/label_certificates" />
   <item
     android:id="@+id/action_crashes"
     android:title="@string/label_crashes" />
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 651de5611..c49cff8a1 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -13,6 +13,7 @@
   <string name="label_buffer_name">Chatname</string>
   <string name="label_cancel">Abbrechen</string>
   <string name="label_update_user_password">Benutzernamen/Passwort ändern</string>
+  <string name="label_certificates">Zertifikate</string>
   <string name="label_close">Schließen</string>
   <string name="label_colors_custom">Anpassen</string>
   <string name="label_colors_mirc">mIRC</string>
@@ -74,6 +75,9 @@
   <string name="label_unhide">Nicht mehr ausblenden</string>
   <string name="label_website">Webseite</string>
   <string name="label_whitelist">Ignorieren</string>
+  <string name="label_whitelist_ignore_date">Verfallsdatum wird ignoriert für dieses Zertifikat</string>
+  <string name="label_whitelist_certificates">Zertifikate</string>
+  <string name="label_whitelist_hostnames">Hosts</string>
   <string name="label_who">Who</string>
   <string name="label_who_long">Informationen aller Nutzer aktualisieren</string>
   <string name="label_whois">Whois</string>
diff --git a/app/src/main/res/values-de/strings_error.xml b/app/src/main/res/values-de/strings_error.xml
index b18e43f92..0af607f1e 100644
--- a/app/src/main/res/values-de/strings_error.xml
+++ b/app/src/main/res/values-de/strings_error.xml
@@ -7,12 +7,18 @@
   <string name="label_error_certificate_no_certificate">Kein Zertifikat verfügbar</string>
   <string name="label_error_certificate_no_hostname">Kein Rechnername verfügbar</string>
   <string name="label_error_certificate_invalid"><![CDATA[
-    <p>Das Zertifikat mit Fingerabdruck <code>%1$s</code> ist nur gültig im Zeitraum von %2$s bis %3$s</p>
+    <p>Das Zertifikat mit Fingerabdruck<br/>
+    <code>%1$s</code><br/>
+    ist nur gültig im Zeitraum von %2$s bis %3$s</p>
   ]]></string>
   <string name="label_error_certificate_untrusted"><![CDATA[
-    <p>Das Zertifikat mit Fingerabdruck <code>%1$s</code> ist nicht vertrauenswürdig.</p>
+    <p>Das Zertifikat mit Fingerabdruck<br/>
+    <code>%1$s</code><br/>
+    ist nicht vertrauenswürdig.</p>
   ]]></string>
   <string name="label_error_certificate_no_match"><![CDATA[
-    <p>Das Zertifikat mit Fingerabdruck <code>%1$s</code> ist nicht gültig für %2$s.</p>
+    <p>Das Zertifikat mit Fingerabdruck<br/>
+    <code>%1$s</code><br/>
+    ist nicht gültig für %2$s.</p>
   ]]></string>
 </resources>
diff --git a/app/src/main/res/values-v17/styles_widgets.xml b/app/src/main/res/values-v17/styles_widgets.xml
index b4027250e..eac3b62a0 100644
--- a/app/src/main/res/values-v17/styles_widgets.xml
+++ b/app/src/main/res/values-v17/styles_widgets.xml
@@ -88,4 +88,18 @@
     <item name="android:paddingStart">?android:attr/listPreferredItemPaddingLeft</item>
     <item name="android:paddingTop">16dp</item>
   </style>
+
+  <style name="Widget.Subhead" parent="">
+    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_height">wrap_content</item>
+    <item name="android:gravity">center_vertical</item>
+    <item name="android:minHeight">?listPreferredItemHeightSmall</item>
+    <item name="android:paddingEnd">?listPreferredItemPaddingRight</item>
+    <item name="android:paddingLeft">?listPreferredItemPaddingLeft</item>
+    <item name="android:paddingRight">?listPreferredItemPaddingRight</item>
+    <item name="android:paddingStart">?listPreferredItemPaddingLeft</item>
+    <item name="android:textColor">?colorAccent</item>
+    <item name="android:textSize">14sp</item>
+    <item name="android:textStyle">bold</item>
+  </style>
 </resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 97b10b2f9..fa55b8daa 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -13,6 +13,7 @@
   <string name="label_buffer_name">Buffer Name</string>
   <string name="label_cancel">Cancel</string>
   <string name="label_update_user_password">Update User/Password</string>
+  <string name="label_certificates">Certificates</string>
   <string name="label_close">Close</string>
   <string name="label_colors_custom">Custom</string>
   <string name="label_colors_mirc">mIRC</string>
@@ -74,6 +75,9 @@
   <string name="label_unhide">Make Visible</string>
   <string name="label_website">Website</string>
   <string name="label_whitelist">Ignore</string>
+  <string name="label_whitelist_ignore_date">Expiration date is ignored for this certificate</string>
+  <string name="label_whitelist_certificates">Certificates</string>
+  <string name="label_whitelist_hostnames">Hosts</string>
   <string name="label_who">Who</string>
   <string name="label_who_long">Update user information of all users</string>
   <string name="label_whois">Whois</string>
diff --git a/app/src/main/res/values/strings_error.xml b/app/src/main/res/values/strings_error.xml
index 4e9bdca3a..9d287180b 100644
--- a/app/src/main/res/values/strings_error.xml
+++ b/app/src/main/res/values/strings_error.xml
@@ -7,12 +7,17 @@
   <string name="label_error_certificate_no_certificate">No certificate available</string>
   <string name="label_error_certificate_no_hostname">No hostname available</string>
   <string name="label_error_certificate_invalid"><![CDATA[
-    <p>The certificate <code>%1$s</code> is only valid from %2$s to %3$s</p>
+    <p>The certificate with fingerprint<br/>
+    <code>%1$s</code> is only valid from %2$s to %3$s</p>
   ]]></string>
   <string name="label_error_certificate_untrusted"><![CDATA[
-    <p>The certificate <code>%1$s</code> is not trusted.</p>
+    <p>The certificate with fingerprint<br/>
+    <code>%1$s</code><br/>
+    is not trusted.</p>
   ]]></string>
   <string name="label_error_certificate_no_match"><![CDATA[
-    <p>The certificate <code>%1$s</code> is not valid for %2$s.</p>
+    <p>The certificate with fingerprint<br/>
+    <code>%1$s</code><br/>
+    is not valid for %2$s.</p>
   ]]></string>
 </resources>
diff --git a/app/src/main/res/values/styles_widgets.xml b/app/src/main/res/values/styles_widgets.xml
index 6d8a4743f..6c51192ce 100644
--- a/app/src/main/res/values/styles_widgets.xml
+++ b/app/src/main/res/values/styles_widgets.xml
@@ -235,6 +235,18 @@
     <item name="android:textColor">?colorTextSecondary</item>
   </style>
 
+  <style name="Widget.Subhead" parent="">
+    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_height">wrap_content</item>
+    <item name="android:gravity">center_vertical</item>
+    <item name="android:minHeight">?listPreferredItemHeightSmall</item>
+    <item name="android:paddingLeft">?listPreferredItemPaddingLeft</item>
+    <item name="android:paddingRight">?listPreferredItemPaddingRight</item>
+    <item name="android:textColor">?colorAccent</item>
+    <item name="android:textSize">14sp</item>
+    <item name="android:textStyle">bold</item>
+  </style>
+
   <!-- NavigationDrawerLayout -->
   <declare-styleable name="NavigationDrawerLayout">
     <attr name="insetBackground" />
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
index 9adb12b36..966985d46 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -10,6 +10,7 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
 import de.kuschku.libquassel.util.helpers.or
 import io.reactivex.BackpressureStrategy
+import io.reactivex.Flowable
 import io.reactivex.Observable
 import io.reactivex.functions.BiFunction
 import io.reactivex.subjects.BehaviorSubject
@@ -42,11 +43,14 @@ class SessionManager(
     else
       lastSession
   }
-  val error: Observable<Error>
+
+  var hasErrored: Boolean = false
+    private set
+
+  val error: Flowable<Error>
     get() = inProgressSession
       .toFlowable(BackpressureStrategy.LATEST)
       .switchMap(ISession::error)
-      .toObservable()
 
   val connectionProgress: Observable<Triple<ConnectionState, Int, Int>> = Observable.combineLatest(
     state, initStatus,
@@ -63,6 +67,10 @@ class SessionManager(
       }
     }
 
+    error.subscribe {
+      hasErrored = true
+    }
+
     // This should preload them
     Invokers
   }
@@ -90,6 +98,7 @@ class SessionManager(
     lastAddress = address
     lastUserData = userData
     lastShouldReconnect = shouldReconnect
+    hasErrored = false
     inProgressSession.onNext(
       Session(
         clientData,
diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
index d76b9060f..4761d606e 100644
--- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
+++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
@@ -161,6 +161,12 @@ abstract class QuasselDatabase : RoomDatabase() {
 
     @Query("SELECT * FROM ssl_validity_whitelist WHERE fingerprint = :fingerprint")
     fun find(fingerprint: String): SslValidityWhitelistEntry?
+
+    @Query("DELETE FROM ssl_validity_whitelist WHERE fingerprint = :fingerprint")
+    fun delete(fingerprint: String)
+
+    @Query("DELETE FROM ssl_validity_whitelist")
+    fun clear()
   }
 
   @Entity(tableName = "ssl_hostname_whitelist", primaryKeys = ["fingerprint", "hostname"])
@@ -179,6 +185,12 @@ abstract class QuasselDatabase : RoomDatabase() {
 
     @Query("SELECT * FROM ssl_hostname_whitelist WHERE fingerprint = :fingerprint AND hostname = :hostname")
     fun find(fingerprint: String, hostname: String): SslHostnameWhitelistEntry?
+
+    @Query("DELETE FROM ssl_hostname_whitelist WHERE fingerprint = :fingerprint AND hostname = :hostname")
+    fun delete(fingerprint: String, hostname: String)
+
+    @Query("DELETE FROM ssl_hostname_whitelist")
+    fun clear()
   }
 
   object Creator {
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
index eda7456ad..8a3e7d08e 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
@@ -18,6 +18,8 @@ import de.kuschku.libquassel.util.helpers.*
 import de.kuschku.libquassel.util.irc.IrcCaseMappers
 import de.kuschku.quasseldroid.util.helper.combineLatest
 import de.kuschku.quasseldroid.viewmodel.data.*
+import io.reactivex.BackpressureStrategy
+import io.reactivex.Flowable
 import io.reactivex.Observable
 import io.reactivex.subjects.BehaviorSubject
 import java.util.concurrent.TimeUnit
@@ -65,7 +67,9 @@ class QuasselViewModel : ViewModel() {
     }
   }
 
-  val errors = sessionManager.mapSwitchMap(SessionManager::error)
+  val errors = sessionManager.toFlowable(BackpressureStrategy.LATEST).switchMap {
+    it.orNull()?.error ?: Flowable.empty()
+  }
 
   val networkConfig = session.mapMapNullable(ISession::networkConfig)
 
-- 
GitLab