From fd29064a3a3c77c5196ec961da601ea646ed330e Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Sat, 6 Apr 2019 21:19:22 +0200
Subject: [PATCH] Implement Channel Creation UI and SASL upgrade UI

---
 app/build.gradle.kts                          |   1 +
 .../debug/res/values/strings_constants.xml    |   1 -
 .../chat/add/create/ChannelCreateFragment.kt  | 104 ++++++++-
 .../ui/chat/add/create/NetworkAdapter.kt      |  21 +-
 .../ui/chat/add/create/NetworkItem.kt         |   8 +
 .../chat/buffers/BufferViewConfigFragment.kt  | 121 +++++-----
 .../network/NetworkBaseFragment.kt            |  42 +++-
 .../util/ui/view/InlineSnackBar.kt            |  86 +++++++
 app/src/main/res/layout/add_create.xml        | 217 ++++++++++--------
 app/src/main/res/layout/chat_chatlist.xml     |   2 +-
 .../res/layout/preferences_about_header.xml   |  14 +-
 app/src/main/res/layout/settings_network.xml  |  20 +-
 .../res/layout/widget_inline_snackbar.xml     |  58 +++++
 app/src/main/res/values-fr-rCA/strings.xml    |   2 +-
 app/src/main/res/values-nl/strings.xml        |   2 +-
 app/src/main/res/values-nl/strings_info.xml   |   3 +-
 .../res/values-nl/strings_preferences.xml     |   3 +-
 app/src/main/res/values/strings_constants.xml |   1 -
 app/src/main/res/values/strings_settings.xml  |   2 +
 app/src/main/res/values/styles_widgets.xml    |   9 +-
 .../quassel/syncables/BufferSyncer.kt         |  32 +++
 .../libquassel/quassel/syncables/Network.kt   |   2 +-
 22 files changed, 557 insertions(+), 194 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/NetworkItem.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/view/InlineSnackBar.kt
 create mode 100644 app/src/main/res/layout/widget_inline_snackbar.xml

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 3a9348b0d..74cf763ff 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -154,6 +154,7 @@ dependencies {
   // UI
   implementation("com.leinardi.android", "speed-dial", "2.0.1")
   implementation("me.zhanghai.android.materialprogressbar", "library", "1.6.1")
+  implementation("com.google.android", "flexbox", "1.1.0")
   implementation(project(":ui_spinner"))
   withVersion("0.9.6.0") {
     implementation("com.afollestad.material-dialogs", "core", version)
diff --git a/app/src/debug/res/values/strings_constants.xml b/app/src/debug/res/values/strings_constants.xml
index 2c87f87db..fb13b0aa1 100644
--- a/app/src/debug/res/values/strings_constants.xml
+++ b/app/src/debug/res/values/strings_constants.xml
@@ -19,5 +19,4 @@
 
 <resources>
   <string name="package_name" translatable="false">com.iskrembilen.quasseldroid.debug</string>
-  <string name="speedial_behavior" translatable="false">com.leinardi.android.speeddial.SpeedDialView$ScrollingViewSnackbarBehavior</string>
 </resources>
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/ChannelCreateFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/ChannelCreateFragment.kt
index 3422f5edb..3ef7d379c 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/ChannelCreateFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/ChannelCreateFragment.kt
@@ -23,22 +23,28 @@ import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.widget.Button
 import android.widget.EditText
 import androidx.appcompat.widget.AppCompatSpinner
 import androidx.appcompat.widget.SwitchCompat
 import androidx.lifecycle.Observer
 import butterknife.BindView
 import butterknife.ButterKnife
+import de.kuschku.libquassel.protocol.Buffer_Type
+import de.kuschku.libquassel.protocol.NetworkId
+import de.kuschku.libquassel.quassel.syncables.IrcChannel
 import de.kuschku.libquassel.quassel.syncables.Network
-import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
+import de.kuschku.libquassel.util.helpers.nullIf
+import de.kuschku.libquassel.util.helpers.value
 import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.ui.chat.ChatActivity
 import de.kuschku.quasseldroid.util.helper.combineLatest
 import de.kuschku.quasseldroid.util.helper.setDependent
 import de.kuschku.quasseldroid.util.helper.toLiveData
-import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable
 import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment
+import io.reactivex.Observable
 
-class ChannelCreateFragment : ServiceBoundSettingsFragment(), Savable {
+class ChannelCreateFragment : ServiceBoundSettingsFragment() {
   @BindView(R.id.network)
   lateinit var network: AppCompatSpinner
 
@@ -60,6 +66,9 @@ class ChannelCreateFragment : ServiceBoundSettingsFragment(), Savable {
   @BindView(R.id.password)
   lateinit var password: EditText
 
+  @BindView(R.id.save)
+  lateinit var save: Button
+
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {
     val view = inflater.inflate(R.layout.add_create, container, false)
@@ -70,7 +79,12 @@ class ChannelCreateFragment : ServiceBoundSettingsFragment(), Savable {
 
     viewModel.networks.switchMap {
       combineLatest(it.values.map(Network::liveNetworkInfo)).map {
-        it.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, INetwork.NetworkInfo::networkName))
+        it.map {
+          NetworkItem(
+            it.networkId,
+            it.networkName
+          )
+        }.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, NetworkItem::name))
       }
     }.toLiveData().observe(this, Observer {
       if (it != null) {
@@ -80,11 +94,83 @@ class ChannelCreateFragment : ServiceBoundSettingsFragment(), Savable {
 
     passwordProtected.setDependent(passwordGroup)
 
-    return view
-  }
+    save.setOnClickListener {
+      save.setText(R.string.label_saving)
+      save.isEnabled = false
+
+      val networkId = NetworkId(network.selectedItemId.toInt())
+      val channelName = name.text.toString().trim()
+
+      val isInviteOnly = inviteOnly.isChecked
+      val isHidden = hidden.isChecked
+      val isPasswordProtected = passwordProtected.isChecked
+      val channelPassword = password.text.toString().trim()
+
+      viewModel.bufferSyncer.value?.orNull()?.let { bufferSyncer ->
+        val existingBuffer = bufferSyncer.find(
+          networkId = networkId,
+          type = Buffer_Type.of(Buffer_Type.ChannelBuffer),
+          bufferName = channelName
+        )
+        val existingChannel = viewModel.networks.value?.get(networkId)?.ircChannel(channelName)
+          .nullIf { it == IrcChannel.NULL }
+        if (existingBuffer != null) {
+          if (existingChannel == null) {
+            bufferSyncer.find(
+              networkId = networkId,
+              type = Buffer_Type.of(Buffer_Type.StatusBuffer)
+            )?.let { statusBuffer ->
+              viewModel.session.value?.orNull()?.rpcHandler?.apply {
+                sendInput(statusBuffer, "/join $channelName")
+              }
+            }
+          }
 
-  override fun onSave(): Boolean {
-    // TODO: Implement
-    return false
+          activity?.let {
+            it.finish()
+            ChatActivity.launch(it,
+              bufferId = existingBuffer.bufferId
+            )
+          }
+        } else {
+          bufferSyncer.find(
+            networkId = networkId,
+            type = Buffer_Type.of(Buffer_Type.StatusBuffer)
+          )?.let { statusBuffer ->
+            viewModel.session.value?.orNull()?.rpcHandler?.apply {
+              sendInput(statusBuffer, "/join $channelName")
+              viewModel.networks.switchMap {
+                it[networkId]?.liveIrcChannel(channelName)
+                  ?: Observable.empty()
+              }.subscribe {
+                if (it.ircUsers().size <= 1) {
+                  if (isInviteOnly) {
+                    sendInput(statusBuffer, "/mode $channelName +i")
+                  }
+
+                  if (isHidden) {
+                    sendInput(statusBuffer, "/mode $channelName +s")
+                  }
+
+                  if (isPasswordProtected) {
+                    sendInput(statusBuffer, "/mode $channelName +k $channelPassword")
+                  }
+                }
+
+                activity?.let {
+                  it.finish()
+                  ChatActivity.launch(it,
+                    networkId = networkId,
+                    channel = channelName
+                  )
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return view
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/NetworkAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/NetworkAdapter.kt
index f1f5976cb..512c396d4 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/NetworkAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/NetworkAdapter.kt
@@ -28,18 +28,19 @@ import androidx.recyclerview.widget.RecyclerView
 import butterknife.BindView
 import butterknife.ButterKnife
 import de.kuschku.libquassel.protocol.NetworkId
-import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.util.ui.ContextThemeWrapper
 import de.kuschku.quasseldroid.util.ui.RecyclerSpinnerAdapter
 
 class NetworkAdapter : RecyclerSpinnerAdapter<NetworkAdapter.NetworkViewHolder>(),
                        ThemedSpinnerAdapter {
-  var data = listOf<INetwork.NetworkInfo>()
+  var data = listOf<NetworkItem>()
 
-  fun submitList(list: List<INetwork.NetworkInfo>) {
-    data = list
-    notifyDataSetChanged()
+  fun submitList(list: List<NetworkItem>) {
+    if (data != list) {
+      data = list
+      notifyDataSetChanged()
+    }
   }
 
   override fun isEmpty() = data.isEmpty()
@@ -61,15 +62,15 @@ class NetworkAdapter : RecyclerSpinnerAdapter<NetworkAdapter.NetworkViewHolder>(
 
   fun indexOf(id: NetworkId): Int? {
     for ((key, item) in data.withIndex()) {
-      if (item.networkId == id) {
+      if (item.id == id) {
         return key
       }
     }
     return null
   }
 
-  override fun getItem(position: Int): INetwork.NetworkInfo = data[position]
-  override fun getItemId(position: Int) = getItem(position).networkId.id.toLong()
+  override fun getItem(position: Int): NetworkItem = data[position]
+  override fun getItemId(position: Int) = getItem(position).id.id.toLong()
   override fun hasStableIds() = true
   override fun getCount() = data.size
   class NetworkViewHolder(itemView: View) :
@@ -81,8 +82,8 @@ class NetworkAdapter : RecyclerSpinnerAdapter<NetworkAdapter.NetworkViewHolder>(
       ButterKnife.bind(this, itemView)
     }
 
-    fun bind(network: INetwork.NetworkInfo) {
-      text.text = network.networkName
+    fun bind(network: NetworkItem) {
+      text.text = network.name
     }
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/NetworkItem.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/NetworkItem.kt
new file mode 100644
index 000000000..12581d987
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/NetworkItem.kt
@@ -0,0 +1,8 @@
+package de.kuschku.quasseldroid.ui.chat.add.create
+
+import de.kuschku.libquassel.protocol.NetworkId
+
+data class NetworkItem(
+  val id: NetworkId,
+  val name: String
+)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
index de818a6de..6b917836d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
@@ -137,34 +137,34 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
             actionMode?.finish()
             true
           }
-          R.id.action_configure   -> {
+          R.id.action_configure -> {
             network?.let {
               NetworkEditActivity.launch(requireContext(), network = it.networkId())
             }
             actionMode?.finish()
             true
           }
-          R.id.action_connect     -> {
+          R.id.action_connect -> {
             network?.requestConnect()
             actionMode?.finish()
             true
           }
-          R.id.action_disconnect  -> {
+          R.id.action_disconnect -> {
             network?.requestDisconnect()
             actionMode?.finish()
             true
           }
-          R.id.action_join        -> {
+          R.id.action_join -> {
             session.rpcHandler.sendInput(info, "/join ${info.bufferName}")
             actionMode?.finish()
             true
           }
-          R.id.action_part        -> {
+          R.id.action_part -> {
             session.rpcHandler.sendInput(info, "/part ${info.bufferName}")
             actionMode?.finish()
             true
           }
-          R.id.action_delete      -> {
+          R.id.action_delete -> {
             MaterialDialog.Builder(activity!!)
               .content(R.string.buffer_delete_confirmation)
               .positiveText(R.string.label_yes)
@@ -184,7 +184,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
               .show()
             true
           }
-          R.id.action_rename      -> {
+          R.id.action_rename -> {
             MaterialDialog.Builder(activity!!)
               .input(
                 getString(R.string.label_buffer_name),
@@ -207,24 +207,24 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
               .show()
             true
           }
-          R.id.action_unhide      -> {
+          R.id.action_unhide -> {
             bufferSyncer?.let {
               bufferViewConfig?.orNull()?.insertBufferSorted(info, bufferSyncer)
             }
             actionMode?.finish()
             true
           }
-          R.id.action_hide_temp   -> {
+          R.id.action_hide_temp -> {
             bufferViewConfig?.orNull()?.requestRemoveBuffer(info.bufferId)
             actionMode?.finish()
             true
           }
-          R.id.action_hide_perm   -> {
+          R.id.action_hide_perm -> {
             bufferViewConfig?.orNull()?.requestRemoveBufferPermanently(info.bufferId)
             actionMode?.finish()
             true
           }
-          else                    -> false
+          else -> false
         }
       } else {
         false
@@ -270,7 +270,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     adapter.setOnUpdateFinishedListener {
       if (!hasSetBufferViewConfigId) {
         chatListSpinner.setSelection(adapter.indexOf(viewModel.bufferViewConfigId.value).nullIf { it == -1 }
-                                     ?: 0)
+          ?: 0)
         viewModel.bufferViewConfigId.onNext(chatListSpinner.selectedItemId.toInt())
         hasSetBufferViewConfigId = true
       }
@@ -326,10 +326,10 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     })
 
     combineLatest(viewModel.bufferList,
-                  viewModel.expandedNetworks,
-                  viewModel.selectedBuffer,
-                  database.filtered().listenRx(accountId).toObservable(),
-                  accountDatabase.accounts().listenDefaultFiltered(accountId, 0).toObservable()
+      viewModel.expandedNetworks,
+      viewModel.selectedBuffer,
+      database.filtered().listenRx(accountId).toObservable(),
+      accountDatabase.accounts().listenDefaultFiltered(accountId, 0).toObservable()
     ).toLiveData().observe(this, Observer { it ->
       it?.let { (info, expandedNetworks, selected, filteredList, defaultFiltered) ->
         runInBackground {
@@ -342,8 +342,8 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
             props.network.networkName
           }).map { props ->
             val activity = props.activity - (activities[props.info.bufferId]
-                                             ?: defaultFiltered?.toUInt()
-                                             ?: 0u)
+              ?: defaultFiltered?.toUInt()
+              ?: 0u)
             BufferListItem(
               props.copy(
                 activity = activity,
@@ -353,22 +353,22 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
                 ),
                 bufferActivity = Buffer_Activity.of(
                   when {
-                    props.highlights > 0                  -> Buffer_Activity.Highlight
+                    props.highlights > 0 -> Buffer_Activity.Highlight
                     activity.hasFlag(Message_Type.Plain) ||
-                    activity.hasFlag(Message_Type.Notice) ||
-                    activity.hasFlag(Message_Type.Action) -> Buffer_Activity.NewMessage
-                    activity.isNotEmpty()                 -> Buffer_Activity.OtherActivity
-                    else                                  -> Buffer_Activity.NoActivity
+                      activity.hasFlag(Message_Type.Notice) ||
+                      activity.hasFlag(Message_Type.Action) -> Buffer_Activity.NewMessage
+                    activity.isNotEmpty() -> Buffer_Activity.OtherActivity
+                    else -> Buffer_Activity.NoActivity
                   }
                 ),
                 fallbackDrawable = if (props.info.type.hasFlag(Buffer_Type.QueryBuffer)) {
                   props.ircUser?.let {
                     val nickName = it.nick()
                     val useSelfColor = when (messageSettings.colorizeNicknames) {
-                      MessageSettings.ColorizeNicknamesMode.ALL          -> false
+                      MessageSettings.ColorizeNicknamesMode.ALL -> false
                       MessageSettings.ColorizeNicknamesMode.ALL_BUT_MINE ->
                         props.ircUser?.network()?.isMyNick(nickName) == true
-                      MessageSettings.ColorizeNicknamesMode.NONE         -> true
+                      MessageSettings.ColorizeNicknamesMode.NONE -> true
                     }
 
                     colorContext.buildTextDrawable(it.nick(), useSelfColor)
@@ -385,14 +385,14 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
               ),
               BufferState(
                 networkExpanded = expandedNetworks[props.network.networkId]
-                                  ?: (props.networkConnectionState == INetwork.ConnectionState.Initialized),
+                  ?: (props.networkConnectionState == INetwork.ConnectionState.Initialized),
                 selected = selected.info?.bufferId == props.info.bufferId
               )
             )
           }.filter { (props, state) ->
             (props.info.type.hasFlag(BufferInfo.Type.StatusBuffer) || state.networkExpanded) &&
-            (minimumActivity.toInt() <= props.bufferActivity.toInt() ||
-             props.info.type.hasFlag(Buffer_Type.StatusBuffer))
+              (minimumActivity.toInt() <= props.bufferActivity.toInt() ||
+                props.info.type.hasFlag(Buffer_Type.StatusBuffer))
           }.toList()
 
           activity?.runOnUiThread {
@@ -424,7 +424,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
           )
 
           val visibilityActions = when (buffer.hiddenState) {
-            BufferHiddenState.VISIBLE          -> setOf(
+            BufferHiddenState.VISIBLE -> setOf(
               R.id.action_hide_temp,
               R.id.action_hide_perm
             )
@@ -439,15 +439,15 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
           }
 
           val availableActions = when (buffer.info?.type?.enabledValues()?.firstOrNull()) {
-            Buffer_Type.StatusBuffer  -> {
+            Buffer_Type.StatusBuffer -> {
               when (buffer.connectionState) {
                 INetwork.ConnectionState.Disconnected -> setOf(
                   R.id.action_configure, R.id.action_connect
                 )
-                INetwork.ConnectionState.Initialized  -> setOf(
+                INetwork.ConnectionState.Initialized -> setOf(
                   R.id.action_channellist, R.id.action_configure, R.id.action_disconnect
                 )
-                else                                  -> setOf(
+                else -> setOf(
                   R.id.action_configure, R.id.action_connect, R.id.action_disconnect
                 )
               }
@@ -459,10 +459,10 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
                 setOf(R.id.action_join, R.id.action_delete)
               } + visibilityActions
             }
-            Buffer_Type.QueryBuffer   -> {
+            Buffer_Type.QueryBuffer -> {
               setOf(R.id.action_delete, R.id.action_rename) + visibilityActions
             }
-            else                      -> visibilityActions
+            else -> visibilityActions
           }
 
           val unavailableActions = allActions - availableActions
@@ -483,7 +483,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     chatListToolbar.menu.findItem(R.id.action_search).isChecked = viewModel.bufferSearchTemporarilyVisible.value
     chatListToolbar.setOnMenuItemClickListener { item ->
       when (item.itemId) {
-        R.id.action_search      -> {
+        R.id.action_search -> {
           item.isChecked = !item.isChecked
           viewModel.bufferSearchTemporarilyVisible.onNext(item.isChecked)
           true
@@ -493,7 +493,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
           viewModel.showHidden.onNext(item.isChecked)
           true
         }
-        else                    -> false
+        else -> false
       }
     }
     chatList.layoutManager = object : LinearLayoutManager(context) {
@@ -512,7 +512,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
       .mapOrElse(false)
 
     combineLatest(viewModel.bufferSearchTemporarilyVisible.distinctUntilChanged(),
-                  bufferSearchPermanentlyVisible)
+      bufferSearchPermanentlyVisible)
       .toLiveData().observe(this, Observer { (temporarily, permanently) ->
         val visible = temporarily || permanently
 
@@ -569,41 +569,46 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
         .create()
     )
 
-    fab.addActionItem(
-      SpeedDialActionItem.Builder(R.id.fab_join, R.drawable.ic_channel)
-        .setFabBackgroundColor(fabBackground1)
-        .setFabImageTintColor(0xffffffffu.toInt())
-        .setLabel(R.string.label_join_long)
-        .setLabelBackgroundColor(colorLabelBackground)
-        .setLabelColor(colorLabel)
-        .create()
-    )
+    if (BuildConfig.DEBUG) {
+      fab.addActionItem(
+        SpeedDialActionItem.Builder(R.id.fab_join, R.drawable.ic_channel)
+          .setFabBackgroundColor(fabBackground1)
+          .setFabImageTintColor(0xffffffffu.toInt())
+          .setLabel(R.string.label_join_long)
+          .setLabelBackgroundColor(colorLabelBackground)
+          .setLabelColor(colorLabel)
+          .create()
+      )
 
-    fab.addActionItem(
-      SpeedDialActionItem.Builder(R.id.fab_query, R.drawable.ic_account)
-        .setFabBackgroundColor(fabBackground2)
-        .setFabImageTintColor(0xffffffffu.toInt())
-        .setLabel(R.string.label_query_medium)
-        .setLabelBackgroundColor(colorLabelBackground)
-        .setLabelColor(colorLabel)
-        .create()
-    )
+      fab.addActionItem(
+        SpeedDialActionItem.Builder(R.id.fab_query, R.drawable.ic_account)
+          .setFabBackgroundColor(fabBackground2)
+          .setFabImageTintColor(0xffffffffu.toInt())
+          .setLabel(R.string.label_query_medium)
+          .setLabelBackgroundColor(colorLabelBackground)
+          .setLabelColor(colorLabel)
+          .create()
+      )
+    }
 
     fab.setOnActionSelectedListener {
       when (it.id) {
-        R.id.fab_query  -> {
+        R.id.fab_query -> {
           context?.let(QueryCreateActivity.Companion::launch)
+          fab.close(false)
           true
         }
-        R.id.fab_join   -> {
+        R.id.fab_join -> {
           context?.let(ChannelJoinActivity.Companion::launch)
+          fab.close(false)
           true
         }
         R.id.fab_create -> {
           context?.let(ChannelCreateActivity.Companion::launch)
+          fab.close(false)
           true
         }
-        else            -> false
+        else -> false
       }
     }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkBaseFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkBaseFragment.kt
index c5394955a..87b20b9d0 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkBaseFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkBaseFragment.kt
@@ -44,15 +44,18 @@ import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
 import de.kuschku.libquassel.session.ISession
 import de.kuschku.libquassel.util.Optional
 import de.kuschku.libquassel.util.helpers.nullIf
+import de.kuschku.libquassel.util.helpers.value
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.defaults.Defaults
 import de.kuschku.quasseldroid.ui.coresettings.networkserver.NetworkServerActivity
 import de.kuschku.quasseldroid.util.helper.combineLatest
 import de.kuschku.quasseldroid.util.helper.setDependent
 import de.kuschku.quasseldroid.util.helper.toLiveData
+import de.kuschku.quasseldroid.util.helper.visibleIf
 import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable
 import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable
 import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment
+import de.kuschku.quasseldroid.util.ui.view.InlineSnackBar
 import kotlin.math.roundToInt
 
 abstract class NetworkBaseFragment(private val initDefault: Boolean) :
@@ -87,6 +90,9 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) :
   @BindView(R.id.autoidentify_group)
   lateinit var autoidentifyGroup: ViewGroup
 
+  @BindView(R.id.autoidentify_warning)
+  lateinit var autoidentifyWarning: InlineSnackBar
+
   @BindView(R.id.autoidentify_service)
   lateinit var autoidentifyService: EditText
 
@@ -191,11 +197,31 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) :
         .filter(Optional<Network>::isPresent)
         .map(Optional<Network>::get)
         .firstElement()
-        .toLiveData().observe(this, Observer {
+        .toLiveData()
+        .observe(this, Observer {
           it?.let {
             update(it, identityAdapter)
           }
         })
+      viewModel.networks.map { Optional.ofNullable(it[networkId]) }
+        .filter(Optional<Network>::isPresent)
+        .map(Optional<Network>::get)
+        .switchMap(Network::liveCaps)
+        .toLiveData()
+        .observe(this, Observer {
+          autoidentifyWarning.visibleIf(it.contains("sasl"))
+        })
+    }
+
+
+    autoidentifyWarning.setOnClickListener {
+      val identity = viewModel.identities.value?.get(IdentityId(identity.selectedItemId.toInt()))
+      if (identity != null) {
+        saslEnabled.isChecked = true
+        saslAccount.setText(identity.nicks().firstOrNull())
+        saslPassword.setText(autoidentifyPassword.text.toString())
+        autoidentifyEnabled.isChecked = false
+      }
     }
 
     saslEnabled.setDependent(saslGroup)
@@ -302,9 +328,9 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) :
 
     data.setUseAutoReconnect(autoreconnectEnabled.isChecked)
     data.setAutoReconnectInterval(autoreconnectInterval.text.toString().toUIntOrNull()
-                                  ?: data.autoReconnectInterval())
+      ?: data.autoReconnectInterval())
     data.setAutoReconnectRetries(autoreconnectRetries.text.toString().toUShortOrNull()
-                                 ?: data.autoReconnectRetries())
+      ?: data.autoReconnectRetries())
     data.setUnlimitedReconnectRetries(autoreconnectUnlimited.isChecked)
 
     data.setPerform(perform.text.lines())
@@ -312,13 +338,13 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) :
 
     data.setUseCustomMessageRate(customratelimitsEnabled.isChecked)
     data.setMessageRateBurstSize(customratelimitsBurstSize.text.toString().toUIntOrNull()
-                                 ?: data.messageRateBurstSize())
+      ?: data.messageRateBurstSize())
     data.setUnlimitedMessageRate(customratelimitsUnlimited.isChecked)
     data.setMessageRateDelay(customratelimitsDelay.toString().toFloatOrNull()
-                               ?.let { (it * 1000).roundToInt() }
-                               ?.nullIf { it < 0 }
-                               ?.toUInt()
-                             ?: data.messageRateDelay())
+      ?.let { (it * 1000).roundToInt() }
+      ?.nullIf { it < 0 }
+      ?.toUInt()
+      ?: data.messageRateDelay())
   }
 
   companion object {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/view/InlineSnackBar.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/view/InlineSnackBar.kt
new file mode 100644
index 000000000..1afe5e5b7
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/view/InlineSnackBar.kt
@@ -0,0 +1,86 @@
+/*
+ * Quasseldroid - Quassel client for Android
+ *
+ * Copyright (c) 2019 Janne Koschinski
+ * Copyright (c) 2019 The Quassel Project
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid.util.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.Button
+import android.widget.FrameLayout
+import android.widget.TextView
+import androidx.annotation.StringRes
+import butterknife.BindView
+import butterknife.ButterKnife
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.util.helper.use
+
+class InlineSnackBar : FrameLayout {
+  @BindView(R.id.text)
+  lateinit var text: TextView
+
+  @BindView(R.id.button)
+  lateinit var button: Button
+
+  constructor(context: Context) :
+    this(context, null)
+
+  constructor(context: Context, attrs: AttributeSet?) :
+    this(context, attrs, 0)
+
+  constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
+    super(context, attrs, defStyleAttr) {
+
+    LayoutInflater.from(context).inflate(R.layout.widget_inline_snackbar, this, true)
+    ButterKnife.bind(this)
+
+    context.theme.obtainStyledAttributes(attrs, R.styleable.InlineSnackBar, 0, 0).use {
+      if (it.hasValue(R.styleable.InlineSnackBar_text))
+        text.text = it.getString(R.styleable.InlineSnackBar_text)
+
+      if (it.hasValue(R.styleable.InlineSnackBar_buttonText))
+        button.text = it.getString(R.styleable.InlineSnackBar_buttonText)
+    }
+  }
+
+  fun setText(content: String) {
+    text.text = content
+  }
+
+  fun setText(@StringRes content: Int) {
+    text.setText(content)
+  }
+
+  fun setButtonText(content: String) {
+    button.text = content
+  }
+
+  fun setButtonText(@StringRes content: Int) {
+    button.setText(content)
+  }
+
+  fun setOnClickListener(listener: ((View) -> Unit)?) {
+    button.setOnClickListener(listener)
+  }
+
+  override fun setOnClickListener(listener: View.OnClickListener) {
+    button.setOnClickListener(listener)
+  }
+}
diff --git a/app/src/main/res/layout/add_create.xml b/app/src/main/res/layout/add_create.xml
index 94c0c9fd3..0d5cb2083 100644
--- a/app/src/main/res/layout/add_create.xml
+++ b/app/src/main/res/layout/add_create.xml
@@ -1,102 +1,137 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<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="match_parent">
+  android:layout_height="match_parent"
+  android:orientation="vertical">
 
-  <LinearLayout style="@style/Widget.CoreSettings.Wrapper">
+  <FrameLayout
+    android:layout_width="match_parent"
+    android:layout_height="0dip"
+    android:layout_weight="1">
 
-    <de.kuschku.ui.spinner.MaterialSpinnerLayout
-      style="@style/Widget.CustomSpinnerLayout"
+    <androidx.core.widget.NestedScrollView
       android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:hint="@string/label_network">
-
-      <androidx.appcompat.widget.AppCompatSpinner
-        android:id="@+id/network"
-        style="@style/Widget.MaterialSpinner"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        tools:listitem="@layout/widget_spinner_item_material" />
-    </de.kuschku.ui.spinner.MaterialSpinnerLayout>
-
-    <com.google.android.material.textfield.TextInputLayout
-      style="@style/Widget.CustomTextInput"
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:hint="@string/label_channel_name">
-
-      <com.google.android.material.textfield.TextInputEditText
-        android:id="@+id/name"
-        style="@style/Widget.CoreSettings.EditText"
-        tools:text="#trees" />
-    </com.google.android.material.textfield.TextInputLayout>
-
-    <LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:minHeight="48dp">
-
-      <androidx.appcompat.widget.AppCompatImageView
-        style="@style/Widget.CoreSettings.PrimaryItemIcon"
-        app:srcCompat="@drawable/ic_eye_off" />
-
-      <androidx.appcompat.widget.SwitchCompat
-        android:id="@+id/hidden"
-        style="@style/Widget.CoreSettings.PrimaryItemSwitch"
-        android:text="@string/addchat_channel_hidden" />
-    </LinearLayout>
-
-    <LinearLayout
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:minHeight="48dp">
-
-      <androidx.appcompat.widget.AppCompatImageView
-        style="@style/Widget.CoreSettings.PrimaryItemIcon"
-        app:srcCompat="@drawable/ic_account_card" />
-
-      <androidx.appcompat.widget.SwitchCompat
-        android:id="@+id/invite_only"
-        style="@style/Widget.CoreSettings.PrimaryItemSwitch"
-        android:text="@string/addchat_channel_invite_only" />
-    </LinearLayout>
-
-    <LinearLayout
+      android:layout_height="match_parent">
+
+      <LinearLayout style="@style/Widget.CoreSettings.Wrapper">
+
+        <de.kuschku.ui.spinner.MaterialSpinnerLayout
+          style="@style/Widget.CustomSpinnerLayout"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:hint="@string/label_network">
+
+          <androidx.appcompat.widget.AppCompatSpinner
+            android:id="@+id/network"
+            style="@style/Widget.MaterialSpinner"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            tools:listitem="@layout/widget_spinner_item_material" />
+        </de.kuschku.ui.spinner.MaterialSpinnerLayout>
+
+        <com.google.android.material.textfield.TextInputLayout
+          style="@style/Widget.CustomTextInput"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:hint="@string/label_channel_name">
+
+          <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/name"
+            style="@style/Widget.CoreSettings.EditText"
+            tools:text="#trees" />
+        </com.google.android.material.textfield.TextInputLayout>
+
+        <LinearLayout
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:minHeight="48dp">
+
+          <androidx.appcompat.widget.AppCompatImageView
+            style="@style/Widget.CoreSettings.PrimaryItemIcon"
+            app:srcCompat="@drawable/ic_eye_off" />
+
+          <androidx.appcompat.widget.SwitchCompat
+            android:id="@+id/hidden"
+            style="@style/Widget.CoreSettings.PrimaryItemSwitch"
+            android:text="@string/addchat_channel_hidden" />
+        </LinearLayout>
+
+        <LinearLayout
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:minHeight="48dp">
+
+          <androidx.appcompat.widget.AppCompatImageView
+            style="@style/Widget.CoreSettings.PrimaryItemIcon"
+            app:srcCompat="@drawable/ic_account_card" />
+
+          <androidx.appcompat.widget.SwitchCompat
+            android:id="@+id/invite_only"
+            style="@style/Widget.CoreSettings.PrimaryItemSwitch"
+            android:text="@string/addchat_channel_invite_only" />
+        </LinearLayout>
+
+        <LinearLayout
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:minHeight="48dp">
+
+          <androidx.appcompat.widget.AppCompatImageView
+            style="@style/Widget.CoreSettings.PrimaryItemIcon"
+            app:srcCompat="@drawable/ic_key_variant" />
+
+          <androidx.appcompat.widget.SwitchCompat
+            android:id="@+id/password_protected"
+            style="@style/Widget.CoreSettings.PrimaryItemSwitch"
+            android:text="@string/addchat_channel_password_protected" />
+        </LinearLayout>
+
+        <LinearLayout
+          android:id="@+id/password_group"
+          style="@style/Widget.CoreSettings.DependentGroup"
+          tools:visibility="visible">
+
+          <com.google.android.material.textfield.TextInputLayout
+            style="@style/Widget.CustomTextInput"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="@string/addchat_channel_password"
+            app:passwordToggleEnabled="true">
+
+            <com.google.android.material.textfield.TextInputEditText
+              android:id="@+id/password"
+              style="@style/Widget.CoreSettings.EditText"
+              android:inputType="textPassword"
+              tools:text="NickServ" />
+          </com.google.android.material.textfield.TextInputLayout>
+        </LinearLayout>
+      </LinearLayout>
+    </androidx.core.widget.NestedScrollView>
+
+    <de.kuschku.quasseldroid.util.ui.view.ShadowView
       android:layout_width="match_parent"
+      android:layout_height="@dimen/shadow_height"
+      android:layout_gravity="bottom"
+      android:gravity="bottom" />
+  </FrameLayout>
+
+  <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="end"
+    android:paddingLeft="16dp"
+    android:paddingTop="4dp"
+    android:paddingRight="16dp"
+    android:paddingBottom="4dp">
+
+    <com.google.android.material.button.MaterialButton
+      android:id="@+id/save"
+      style="@style/Widget.Button.Colored"
+      android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      android:minHeight="48dp">
-
-      <androidx.appcompat.widget.AppCompatImageView
-        style="@style/Widget.CoreSettings.PrimaryItemIcon"
-        app:srcCompat="@drawable/ic_key_variant" />
-
-      <androidx.appcompat.widget.SwitchCompat
-        android:id="@+id/password_protected"
-        style="@style/Widget.CoreSettings.PrimaryItemSwitch"
-        android:text="@string/addchat_channel_password_protected" />
-    </LinearLayout>
-
-    <LinearLayout
-      android:id="@+id/password_group"
-      style="@style/Widget.CoreSettings.DependentGroup"
-      tools:visibility="visible">
-
-      <com.google.android.material.textfield.TextInputLayout
-        style="@style/Widget.CustomTextInput"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:hint="@string/addchat_channel_password"
-        app:passwordToggleEnabled="true">
-
-        <com.google.android.material.textfield.TextInputEditText
-          android:id="@+id/password"
-          style="@style/Widget.CoreSettings.EditText"
-          android:inputType="textPassword"
-          tools:text="NickServ" />
-      </com.google.android.material.textfield.TextInputLayout>
-    </LinearLayout>
+      android:text="@string/label_save" />
   </LinearLayout>
-</androidx.core.widget.NestedScrollView>
+</LinearLayout>
diff --git a/app/src/main/res/layout/chat_chatlist.xml b/app/src/main/res/layout/chat_chatlist.xml
index 2e499427e..894fddef0 100644
--- a/app/src/main/res/layout/chat_chatlist.xml
+++ b/app/src/main/res/layout/chat_chatlist.xml
@@ -85,7 +85,7 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="bottom|end"
-    app:layout_behavior="@string/speedial_behavior"
+    app:layout_behavior="@string/speeddial_scrolling_view_snackbar_behavior"
     app:sdMainFabClosedSrc="@drawable/ic_add"
     app:sdOverlayLayout="@id/fab_chatlist_overlay" />
 
diff --git a/app/src/main/res/layout/preferences_about_header.xml b/app/src/main/res/layout/preferences_about_header.xml
index 0efdb6100..0d76441e1 100644
--- a/app/src/main/res/layout/preferences_about_header.xml
+++ b/app/src/main/res/layout/preferences_about_header.xml
@@ -67,22 +67,22 @@
     </LinearLayout>
   </LinearLayout>
 
-  <LinearLayout
+  <com.google.android.flexbox.FlexboxLayout
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginTop="8dp"
     android:layout_marginBottom="8dp"
-    android:orientation="horizontal"
-    android:theme="?attr/actionBarTheme">
+    android:theme="?attr/actionBarTheme"
+    app:alignContent="flex_start"
+    app:alignItems="flex_start"
+    app:flexWrap="wrap">
 
     <com.google.android.material.button.MaterialButton
       android:id="@+id/action_website"
       style="@style/Widget.Button.Colored"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      android:text="@string/label_website"
-      android:textColor="?colorControlNormal"
-      app:backgroundTint="?colorPrimary" />
+      android:text="@string/label_website" />
 
     <Space
       android:layout_width="8dp"
@@ -106,7 +106,7 @@
       android:layout_height="wrap_content"
       android:text="@string/advertisement_support_button" />
 
-  </LinearLayout>
+  </com.google.android.flexbox.FlexboxLayout>
 
   <TextView
     style="@style/Widget.RtlConformTextView"
diff --git a/app/src/main/res/layout/settings_network.xml b/app/src/main/res/layout/settings_network.xml
index 1e78a6874..49a3e9ef3 100644
--- a/app/src/main/res/layout/settings_network.xml
+++ b/app/src/main/res/layout/settings_network.xml
@@ -22,7 +22,8 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
-  android:scrollbars="vertical">
+  android:scrollbars="vertical"
+  tools:ignore="UnusedAttribute">
 
   <LinearLayout style="@style/Widget.CoreSettings.Wrapper">
 
@@ -146,6 +147,8 @@
         <com.google.android.material.textfield.TextInputEditText
           android:id="@+id/sasl_account"
           style="@style/Widget.CoreSettings.EditText"
+          android:autofillHints="username"
+          android:importantForAutofill="yes"
           tools:text="justjanne" />
       </com.google.android.material.textfield.TextInputLayout>
 
@@ -159,6 +162,8 @@
         <com.google.android.material.textfield.TextInputEditText
           android:id="@+id/sasl_password"
           style="@style/Widget.CoreSettings.EditText"
+          android:autofillHints="password"
+          android:importantForAutofill="yes"
           android:inputType="textPassword"
           tools:text="thisisasecurepassword" />
       </com.google.android.material.textfield.TextInputLayout>
@@ -184,7 +189,16 @@
     <LinearLayout
       android:id="@+id/autoidentify_group"
       style="@style/Widget.CoreSettings.DependentGroup"
-      tools:visibility="gone">
+      tools:visibility="visible">
+
+      <de.kuschku.quasseldroid.util.ui.view.InlineSnackBar
+        android:id="@+id/autoidentify_warning"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:layout_marginBottom="8dp"
+        app:buttonText="@string/settings_network_sasl_upgrade"
+        app:text="@string/settings_network_sasl_supported" />
 
       <com.google.android.material.textfield.TextInputLayout
         style="@style/Widget.CustomTextInput"
@@ -208,6 +222,8 @@
         <com.google.android.material.textfield.TextInputEditText
           android:id="@+id/autoidentify_password"
           style="@style/Widget.CoreSettings.EditText"
+          android:autofillHints="password"
+          android:importantForAutofill="yes"
           android:inputType="textPassword"
           tools:text="thisisasecurepassword" />
       </com.google.android.material.textfield.TextInputLayout>
diff --git a/app/src/main/res/layout/widget_inline_snackbar.xml b/app/src/main/res/layout/widget_inline_snackbar.xml
new file mode 100644
index 000000000..53e224bef
--- /dev/null
+++ b/app/src/main/res/layout/widget_inline_snackbar.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Quasseldroid - Quassel client for Android
+
+  Copyright (c) 2019 Janne Koschinski
+  Copyright (c) 2019 The Quassel Project
+
+  This program is free software: you can redistribute it and/or modify it
+  under the terms of the GNU General Public License version 3 as published
+  by the Free Software Foundation.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along
+  with this program. If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<com.google.android.material.card.MaterialCardView 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"
+  app:cardBackgroundColor="?colorBackgroundSnackbar">
+
+  <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="48dp"
+    android:orientation="horizontal">
+
+    <TextView
+      android:id="@+id/text"
+      android:layout_width="0dip"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center_vertical"
+      android:layout_marginStart="16dp"
+      android:layout_marginLeft="16dp"
+      android:layout_weight="1"
+      android:textColor="?colorOnPrimary"
+      tools:text="This server supports SASL" />
+
+    <com.google.android.material.button.MaterialButton
+      android:id="@+id/button"
+      style="@style/Widget.MaterialComponents.Button.TextButton.Snackbar"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center_vertical"
+      android:layout_marginLeft="8dp"
+      android:layout_marginTop="6dp"
+      android:layout_marginRight="8dp"
+      android:layout_marginBottom="6dp"
+      android:textColor="?colorOnPrimary"
+      tools:text="Upgrade" />
+  </LinearLayout>
+
+</com.google.android.material.card.MaterialCardView>
diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml
index e742db1ab..1bcd124bb 100644
--- a/app/src/main/res/values-fr-rCA/strings.xml
+++ b/app/src/main/res/values-fr-rCA/strings.xml
@@ -134,4 +134,4 @@
 
   <string name="info_copied_version">Version copiƩe au clipboard</string>
 
-</resources>
+  </resources>
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 9f524160d..9d8f1e83b 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -81,4 +81,4 @@
   <string name="label_translators">Vertalers</string>
   <string name="label_yes">Ja</string>
 
-</resources>
+  </resources>
diff --git a/app/src/main/res/values-nl/strings_info.xml b/app/src/main/res/values-nl/strings_info.xml
index c985cffec..11c649ce4 100644
--- a/app/src/main/res/values-nl/strings_info.xml
+++ b/app/src/main/res/values-nl/strings_info.xml
@@ -17,4 +17,5 @@
   with this program. If not, see <http://www.gnu.org/licenses/>.
   -->
 
-<resources></resources>
+<resources>
+  </resources>
diff --git a/app/src/main/res/values-nl/strings_preferences.xml b/app/src/main/res/values-nl/strings_preferences.xml
index c985cffec..11c649ce4 100644
--- a/app/src/main/res/values-nl/strings_preferences.xml
+++ b/app/src/main/res/values-nl/strings_preferences.xml
@@ -17,4 +17,5 @@
   with this program. If not, see <http://www.gnu.org/licenses/>.
   -->
 
-<resources></resources>
+<resources>
+  </resources>
diff --git a/app/src/main/res/values/strings_constants.xml b/app/src/main/res/values/strings_constants.xml
index 5b12ed27c..92b980cb5 100644
--- a/app/src/main/res/values/strings_constants.xml
+++ b/app/src/main/res/values/strings_constants.xml
@@ -25,5 +25,4 @@
   <string name="notification_channel_old_highlight" translatable="false">old_highlight</string>
 
   <string name="drag_intercept_bottom_sheet_behavior" translatable="false">de.kuschku.quasseldroid.util.ui.DragInterceptBottomSheetBehavior</string>
-  <string name="speedial_behavior" translatable="false">com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior</string>
 </resources>
diff --git a/app/src/main/res/values/strings_settings.xml b/app/src/main/res/values/strings_settings.xml
index dcbc6845a..a49a497e5 100644
--- a/app/src/main/res/values/strings_settings.xml
+++ b/app/src/main/res/values/strings_settings.xml
@@ -25,6 +25,8 @@
   <string name="settings_network_sasl_enabled">SASL</string>
   <string name="settings_network_sasl_account">Account</string>
   <string name="settings_network_sasl_password">Password</string>
+  <string name="settings_network_sasl_supported">This network supports SASL</string>
+  <string name="settings_network_sasl_upgrade">Use SASL</string>
   <string name="settings_network_autoidentify_enabled">Auto Identify</string>
   <string name="settings_network_autoidentify_service">Service</string>
   <string name="settings_network_autoidentify_password">Password</string>
diff --git a/app/src/main/res/values/styles_widgets.xml b/app/src/main/res/values/styles_widgets.xml
index b0dfaff28..fa9ab1b31 100644
--- a/app/src/main/res/values/styles_widgets.xml
+++ b/app/src/main/res/values/styles_widgets.xml
@@ -37,7 +37,8 @@
   </style>
 
   <style name="Widget.Button.Colored" parent="Widget.MaterialComponents.Button">
-    <item name="android:textColor">?attr/colorTextPrimaryInverse</item>
+    <item name="android:textColor">?attr/colorOnPrimary</item>
+    <item name="backgroundTint">?attr/colorPrimary</item>
   </style>
 
   <style name="Widget.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
@@ -398,6 +399,12 @@
     <attr name="mode" />
   </declare-styleable>
 
+  <!-- InlineSnackBar -->
+  <declare-styleable name="InlineSnackBar">
+    <attr name="text" />
+    <attr name="buttonText" />
+  </declare-styleable>
+
   <!-- BannerView -->
   <declare-styleable name="BannerView">
     <attr name="icon" />
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
index b8d2066a5..5efdac19b 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
@@ -25,6 +25,7 @@ import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.syncables.interfaces.IBufferSyncer
 import de.kuschku.libquassel.session.ISession
 import de.kuschku.libquassel.session.NotificationManager
+import de.kuschku.libquassel.util.Optional
 import de.kuschku.libquassel.util.flag.hasFlag
 import de.kuschku.libquassel.util.irc.IrcCaseMappers
 import io.reactivex.Observable
@@ -313,6 +314,27 @@ class BufferSyncer constructor(
     bufferName == null || caseMapper.equalsIgnoreCaseNullable(it.bufferName, bufferName)
   }
 
+  fun liveAll(
+    bufferName: String? = null,
+    bufferId: BufferId? = null,
+    networkId: NetworkId? = null,
+    type: Buffer_Types? = null,
+    groupId: Int? = null
+  ) = liveBufferInfos().map {
+    it.values.filter {
+      bufferId == null || it.bufferId == bufferId
+    }.filter {
+      networkId == null || it.networkId == networkId
+    }.filter {
+      type == null || it.type == type
+    }.filter {
+      groupId == null || it.groupId == groupId
+    }.filter {
+      val caseMapper = IrcCaseMappers[session.networks[it.networkId]?.support("CASEMAPPING")]
+      bufferName == null || caseMapper.equalsIgnoreCaseNullable(it.bufferName, bufferName)
+    }
+  }
+
   fun find(
     bufferName: String? = null,
     bufferId: BufferId? = null,
@@ -321,6 +343,16 @@ class BufferSyncer constructor(
     groupId: Int? = null
   ) = all(bufferName, bufferId, networkId, type, groupId).firstOrNull()
 
+  fun liveFind(
+    bufferName: String? = null,
+    bufferId: BufferId? = null,
+    networkId: NetworkId? = null,
+    type: Buffer_Types? = null,
+    groupId: Int? = null
+  ) = liveAll(bufferName, bufferId, networkId, type, groupId).map {
+    Optional.ofNullable(it.firstOrNull())
+  }
+
   override fun toString(): String {
     return "BufferSyncer(_lastSeenMsg=$_lastSeenMsg, _markerLines=$_markerLines, _bufferActivities=$_bufferActivities, _highlightCounts=$_highlightCounts, _bufferInfos=$_bufferInfos)"
   }
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Network.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Network.kt
index b309cc1ef..f5ea7a754 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Network.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Network.kt
@@ -729,7 +729,7 @@ class Network constructor(
   }
 
   override fun initSetCaps(caps: QVariantMap) {
-    caps.entries.map { (key, value) -> key to value.value("") }.toMap(_supports)
+    caps.entries.map { (key, value) -> key to value.value("") }.toMap(_caps)
   }
 
   override fun initSetCapsEnabled(capsEnabled: QVariantList) {
-- 
GitLab