diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 63b7ac7aff96cc0e57b7624786fc82d3e91c84ba..d8c6bfeb3207a422759ac9724420cd9db3da3e98 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -66,6 +66,15 @@
         <category android:name="android.intent.category.DEFAULT" />
         <data android:mimeType="text/plain" />
       </intent-filter>
+      <intent-filter>
+        <action android:name="android.intent.action.VIEW" />
+
+        <category android:name="android.intent.category.DEFAULT" />
+        <category android:name="android.intent.category.BROWSABLE" />
+
+        <data android:scheme="irc" />
+        <data android:scheme="ircs" />
+      </intent-filter>
     </activity>
     <activity
       android:name=".ui.chat.info.user.UserInfoActivity"
@@ -234,7 +243,13 @@
     <activity
       android:name=".ui.setup.user.UserSetupActivity"
       android:exported="false"
-      android:label="@string/settings_identity_title"
+      android:label="@string/setup_user_title"
+      android:parentActivityName=".ui.chat.ChatActivity"
+      android:windowSoftInputMode="adjustResize" />
+    <activity
+      android:name=".ui.setup.network.NetworkSetupActivity"
+      android:exported="false"
+      android:label="@string/setup_network_title"
       android:parentActivityName=".ui.chat.ChatActivity"
       android:windowSoftInputMode="adjustResize" />
 
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 d78a47ea2f071797b85f1ec0f34d8cb56af56c2d..21b8e918abbfcb298af01854ad9b61c619a75fb1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt
@@ -80,6 +80,8 @@ import de.kuschku.quasseldroid.ui.setup.accounts.selection.AccountSelectionActiv
 import de.kuschku.quasseldroid.ui.setup.accounts.selection.AccountSelectionFragmentProvider
 import de.kuschku.quasseldroid.ui.setup.accounts.setup.AccountSetupActivity
 import de.kuschku.quasseldroid.ui.setup.accounts.setup.AccountSetupFragmentProvider
+import de.kuschku.quasseldroid.ui.setup.network.NetworkSetupActivity
+import de.kuschku.quasseldroid.ui.setup.network.NetworkSetupFragmentProvider
 import de.kuschku.quasseldroid.ui.setup.user.UserSetupActivity
 import de.kuschku.quasseldroid.ui.setup.user.UserSetupFragmentProvider
 
@@ -201,6 +203,10 @@ abstract class ActivityModule {
   @ContributesAndroidInjector(modules = [UserSetupFragmentProvider::class, SettingsModule::class, DatabaseModule::class, ActivityBaseModule::class])
   abstract fun bindUserSetupActivity(): UserSetupActivity
 
+  @ActivityScope
+  @ContributesAndroidInjector(modules = [NetworkSetupFragmentProvider::class, SettingsModule::class, DatabaseModule::class, ActivityBaseModule::class])
+  abstract fun bindNetworkSetupActivity(): NetworkSetupActivity
+
   @ActivityScope
   @ContributesAndroidInjector(modules = [QuasselServiceModule::class, SettingsModule::class, DatabaseModule::class])
   abstract fun bindQuasselService(): QuasselService
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 4c9bf568d4a8b44813c5f4c9a82f43478b847518..dad3a2e534a4e8e7873ad5a89b00137c6b110658 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
@@ -67,11 +67,13 @@ import de.kuschku.libquassel.util.Optional
 import de.kuschku.libquassel.util.flag.and
 import de.kuschku.libquassel.util.flag.hasFlag
 import de.kuschku.libquassel.util.flag.or
+import de.kuschku.libquassel.util.helpers.nullIf
 import de.kuschku.libquassel.util.helpers.value
 import de.kuschku.libquassel.util.irc.SenderColorUtil
 import de.kuschku.quasseldroid.GlideApp
 import de.kuschku.quasseldroid.Keys
 import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.defaults.DefaultNetworkServer
 import de.kuschku.quasseldroid.persistence.AccountDatabase
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.AutoCompleteSettings
@@ -82,11 +84,14 @@ import de.kuschku.quasseldroid.ui.chat.input.ChatlineFragment
 import de.kuschku.quasseldroid.ui.clientsettings.client.ClientSettingsActivity
 import de.kuschku.quasseldroid.ui.coresettings.CoreSettingsActivity
 import de.kuschku.quasseldroid.ui.setup.accounts.selection.AccountSelectionActivity
+import de.kuschku.quasseldroid.ui.setup.network.LinkNetwork
+import de.kuschku.quasseldroid.ui.setup.network.NetworkSetupActivity
 import de.kuschku.quasseldroid.ui.setup.user.UserSetupActivity
 import de.kuschku.quasseldroid.util.ColorContext
 import de.kuschku.quasseldroid.util.avatars.AvatarHelper
 import de.kuschku.quasseldroid.util.backport.OsConstants
 import de.kuschku.quasseldroid.util.helper.*
+import de.kuschku.quasseldroid.util.irc.IrcPorts
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.missingfeatures.MissingFeaturesDialog
 import de.kuschku.quasseldroid.util.missingfeatures.RequiredFeatures
@@ -228,6 +233,28 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
             }
           }
         }
+        intent.scheme == "irc" ||
+        intent.scheme == "ircs"                                         -> {
+          val uri = intent.data
+          if (uri != null) {
+            val channelString = (uri.path.let { it ?: "" }.trimStart('/')) +
+                                (uri.fragment?.let { "#$it" }.let { it ?: "" })
+            NetworkSetupActivity.launch(
+              this,
+              network = LinkNetwork(
+                name = "",
+                server = DefaultNetworkServer(
+                  host = uri.host ?: "",
+                  port = uri.port.nullIf { it == -1 }
+                         ?: if (uri.scheme == "irc") IrcPorts.normal
+                         else IrcPorts.secure,
+                  secure = uri.scheme == "ircs"
+                )
+              ),
+              channels = channelString.split(",").toTypedArray()
+            )
+          }
+        }
       }
     }
   }
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 6ac3cf3652f249a8e6f9ec1d101dcd2df3a7c286..dceb01b7869d6776d9e6271a220250e8eb1f6b19 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
@@ -165,7 +165,7 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) :
         identityAdapter.submitList(it)
         if (selectOriginal) {
           this.network?.let { (_, data) ->
-            identityAdapter.indexOf(data.networkId())?.let(identity::setSelection)
+            identityAdapter.indexOf(data.identity())?.let(identity::setSelection)
           }
         }
       }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/networkserver/NetworkServerFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/networkserver/NetworkServerFragment.kt
index 63b013ef7e4ebb6ae13d2251346e0e53179bd428..540895b74d9b011f0abb8cec8fcb71195bc9804e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/networkserver/NetworkServerFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/networkserver/NetworkServerFragment.kt
@@ -116,10 +116,10 @@ class NetworkServerFragment : SettingsFragment(), SettingsFragment.Savable,
     sslEnabled.setOnCheckedChangeListener { _, isChecked ->
       sslVerify.isEnabled = isChecked
       val portValue = port.text.trim().toString()
-      if (isChecked && portValue == IrcPorts.normal) {
-        port.setText(IrcPorts.secure)
-      } else if (!isChecked && portValue == IrcPorts.secure) {
-        port.setText(IrcPorts.normal)
+      if (isChecked && portValue == IrcPorts.normal.toString()) {
+        port.setText(IrcPorts.secure.toString())
+      } else if (!isChecked && portValue == IrcPorts.secure.toString()) {
+        port.setText(IrcPorts.normal.toString())
       }
     }
     sslVerify.isEnabled = sslEnabled.isChecked
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSlideFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSlideFragment.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f383b42d90d2db8acdc774a3aa50ce2a4a163fb2
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSlideFragment.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.ui.setup
+
+import android.content.Context
+import android.os.Bundle
+import de.kuschku.libquassel.session.Backend
+import de.kuschku.libquassel.util.Optional
+import de.kuschku.quasseldroid.Keys
+import de.kuschku.quasseldroid.util.service.BackendServiceConnection
+import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
+import io.reactivex.subjects.BehaviorSubject
+import javax.inject.Inject
+
+abstract class ServiceBoundSlideFragment : SlideFragment() {
+  @Inject
+  lateinit var viewModel: QuasselViewModel
+
+  private val connection = BackendServiceConnection()
+  protected val backend: BehaviorSubject<Optional<Backend>>
+    get() = connection.backend
+
+  protected fun runInBackground(f: () -> Unit) {
+    connection.backend.value.ifPresent {
+      it.sessionManager().handlerService.backend(f)
+    }
+  }
+
+  protected fun runInBackgroundDelayed(delayMillis: Long, f: () -> Unit) {
+    connection.backend.value.ifPresent {
+      it.sessionManager().handlerService.backendDelayed(delayMillis, f)
+    }
+  }
+
+  protected var accountId: Long = -1
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    accountId = context?.getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE)
+      ?.getLong(Keys.Status.selectedAccount, -1) ?: -1
+
+    connection.context = context
+    lifecycle.addObserver(connection)
+    super.onCreate(savedInstanceState)
+  }
+
+  override fun onStart() {
+    super.onStart()
+    accountId = context?.getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE)
+      ?.getLong(Keys.Status.selectedAccount, -1) ?: -1
+  }
+
+  override fun onDestroy() {
+    lifecycle.removeObserver(connection)
+    super.onDestroy()
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/LinkNetwork.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/LinkNetwork.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ac823f78c9a4ff758424f3c92d1b0be9e3ac9fc0
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/LinkNetwork.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.ui.setup.network
+
+import de.kuschku.quasseldroid.defaults.DefaultNetworkServer
+import java.io.Serializable
+
+data class LinkNetwork(
+  val name: String,
+  val defaultChannels: List<String> = emptyList(),
+  val server: DefaultNetworkServer
+) : Serializable
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1ed76e38c5ea9a942fc6900292508df176062440
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupActivity.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.ui.setup.network
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import de.kuschku.libquassel.protocol.Buffer_Type
+import de.kuschku.libquassel.protocol.IdentityId
+import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
+import de.kuschku.libquassel.util.helpers.value
+import de.kuschku.quasseldroid.ui.setup.ServiceBoundSetupActivity
+
+class NetworkSetupActivity : ServiceBoundSetupActivity() {
+  private lateinit var arguments: Bundle
+  override val initData: Bundle
+    get() = arguments
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    arguments = intent.getBundleExtra("link")
+    super.onCreate(savedInstanceState)
+  }
+
+  override fun onDone(data: Bundle) {
+    val network = data.getSerializable("network") as? LinkNetwork
+    val networkId = data.getInt("network_id", -1)
+    val identity = data.getInt("identity", -1)
+    if (networkId != -1 || (network != null && identity != -1)) {
+      viewModel.backend?.value?.ifPresent { backend ->
+        val session = viewModel.session.value?.orNull()
+        session?.apply {
+          rpcHandler?.apply {
+            when {
+              networkId != -1                  -> {
+                val buffer = bufferSyncer?.find(networkId = networkId,
+                                                type = Buffer_Type.of(Buffer_Type.StatusBuffer))
+                if (buffer != null) {
+                  data.getStringArray("channels")?.toList().orEmpty().forEach {
+                    sendInput(buffer, "/join $it")
+                  }
+                }
+              }
+              network != null &&
+              network.name.isNotBlank() &&
+              network.server.host.isNotBlank() -> {
+                createNetwork(INetwork.NetworkInfo(
+                  networkName = network.name,
+                  identity = identity,
+                  serverList = listOf(INetwork.Server(
+                    host = network.server.host,
+                    port = network.server.port,
+                    useSsl = network.server.secure
+                  ))
+                ), data.getStringArray("channels")?.toList().orEmpty())
+                backend.requestConnectNewNetwork()
+              }
+            }
+          }
+        }
+      }
+    }
+
+    setResult(Activity.RESULT_OK)
+    finish()
+  }
+
+  override val fragments = listOf(
+    NetworkSetupNetworkSlide(),
+    NetworkSetupChannelsSlide()
+  )
+
+  companion object {
+    fun launch(
+      context: Context,
+      network: LinkNetwork? = null,
+      identity: IdentityId? = null,
+      channels: Array<String>? = null
+    ) = context.startActivity(intent(context, network, identity, channels))
+
+    fun intent(
+      context: Context,
+      network: LinkNetwork? = null,
+      identity: IdentityId? = null,
+      channels: Array<String>? = null
+    ) = Intent(context, NetworkSetupActivity::class.java).apply {
+      if (network != null || identity != null || channels != null) {
+        val bundle = Bundle()
+        if (network != null) {
+          bundle.putSerializable("network", network)
+        }
+        if (identity != null) {
+          bundle.putInt("identity", identity)
+        }
+        if (channels != null) {
+          bundle.putStringArray("channels", channels)
+        }
+        putExtra("link", bundle)
+      }
+    }
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupChannelsSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupChannelsSlide.kt
new file mode 100644
index 0000000000000000000000000000000000000000..dab28b0b636fc5ceb8ccb7cb2ed23d01a1aeb52f
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupChannelsSlide.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.ui.setup.network
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.EditText
+import butterknife.BindView
+import butterknife.ButterKnife
+import com.google.android.material.textfield.TextInputLayout
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.ui.setup.SlideFragment
+
+class NetworkSetupChannelsSlide : SlideFragment() {
+  @BindView(R.id.channelsWrapper)
+  lateinit var channelsWrapper: TextInputLayout
+
+  @BindView(R.id.channels)
+  lateinit var channelsField: EditText
+
+  override fun isValid() = true
+
+  override val title = R.string.slide_user_channels_title
+  override val description = R.string.slide_user_channels_description
+
+  override fun setData(data: Bundle) {
+    if (data.containsKey("channels"))
+      channelsField.setText(data.getStringArray("channels")?.joinToString("\n"))
+    updateValidity()
+  }
+
+  override fun getData(data: Bundle) {
+    data.putStringArray("channels",
+                        channelsField.text.toString()
+                          .split('\n', ' ', ',', ';')
+                          .map(String::trim)
+                          .filter(String::isNotBlank)
+                          .toTypedArray()
+    )
+  }
+
+  override fun onCreateContent(inflater: LayoutInflater, container: ViewGroup?,
+                               savedInstanceState: Bundle?): View {
+    val view = inflater.inflate(R.layout.setup_user_channels, container, false)
+    ButterKnife.bind(this, view)
+    return view
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupFragmentProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a82365e43e63449101891157a52a2e792a8f2771
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupFragmentProvider.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.ui.setup.network
+
+import androidx.fragment.app.FragmentActivity
+import dagger.Binds
+import dagger.Module
+import dagger.android.ContributesAndroidInjector
+
+@Module
+abstract class NetworkSetupFragmentProvider {
+  @Binds
+  abstract fun bindFragmentActivity(activity: NetworkSetupActivity): FragmentActivity
+
+  @ContributesAndroidInjector
+  abstract fun bindNetworkSetupNetworkSlide(): NetworkSetupNetworkSlide
+
+  @ContributesAndroidInjector
+  abstract fun bindNetworkSetupChannelsSlide(): NetworkSetupChannelsSlide
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupNetworkSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupNetworkSlide.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2a0d41763b82b42fe400aa55194048da5110180d
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/network/NetworkSetupNetworkSlide.kt
@@ -0,0 +1,263 @@
+/*
+ * 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.ui.setup.network
+
+import android.os.Bundle
+import android.text.Editable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.EditText
+import android.widget.Spinner
+import androidx.appcompat.widget.SwitchCompat
+import androidx.lifecycle.Observer
+import butterknife.BindView
+import butterknife.ButterKnife
+import com.google.android.material.textfield.TextInputLayout
+import de.kuschku.libquassel.quassel.syncables.Identity
+import de.kuschku.libquassel.quassel.syncables.Network
+import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.defaults.DefaultNetworkServer
+import de.kuschku.quasseldroid.ui.coresettings.chatlist.NetworkAdapter
+import de.kuschku.quasseldroid.ui.coresettings.network.IdentityAdapter
+import de.kuschku.quasseldroid.ui.setup.ServiceBoundSlideFragment
+import de.kuschku.quasseldroid.util.Patterns
+import de.kuschku.quasseldroid.util.TextValidator
+import de.kuschku.quasseldroid.util.helper.combineLatest
+import de.kuschku.quasseldroid.util.helper.toLiveData
+import de.kuschku.quasseldroid.util.irc.IrcPorts
+import de.kuschku.quasseldroid.util.ui.AnimationHelper
+
+class NetworkSetupNetworkSlide : ServiceBoundSlideFragment() {
+  @BindView(R.id.identity)
+  lateinit var identity: Spinner
+
+  @BindView(R.id.network)
+  lateinit var network: Spinner
+
+  @BindView(R.id.network_group)
+  lateinit var networkGroup: ViewGroup
+
+  @BindView(R.id.nameWrapper)
+  lateinit var nameWrapper: TextInputLayout
+
+  @BindView(R.id.name)
+  lateinit var nameField: EditText
+
+  @BindView(R.id.hostWrapper)
+  lateinit var hostWrapper: TextInputLayout
+
+  @BindView(R.id.host)
+  lateinit var hostField: EditText
+
+  @BindView(R.id.portWrapper)
+  lateinit var portWrapper: TextInputLayout
+
+  @BindView(R.id.port)
+  lateinit var portField: EditText
+
+  @BindView(R.id.ssl_enabled)
+  lateinit var sslEnabled: SwitchCompat
+
+  private val identityAdapter = IdentityAdapter()
+  private val networkAdapter = NetworkAdapter()
+
+  override fun isValid(): Boolean {
+    return (this.network.selectedItemPosition != -1 &&
+            networkAdapter.getItemId(this.network.selectedItemPosition) != -1L) ||
+           ((this.identity.selectedItemPosition != -1 &&
+             identityAdapter.getItemId(this.identity.selectedItemPosition) != -1L) &&
+            (nameValidator.isValid && hostValidator.isValid && portValidator.isValid))
+  }
+
+  override val title = R.string.slide_user_network_title
+  override val description = R.string.slide_user_network_description
+
+  private var data: Bundle? = null
+  private var networks: List<INetwork.NetworkInfo>? = null
+
+  override fun setData(data: Bundle) {
+    this.data = data
+    update()
+  }
+
+  override fun getData(data: Bundle) {
+    data.putSerializable(
+      "identity",
+      identityAdapter.getItemId(this.identity.selectedItemPosition)
+    )
+    val networkId = (network.selectedItem as? INetwork.NetworkInfo)?.networkId
+    if (networkId != null) {
+      data.putInt("network_id", networkId)
+    } else {
+      data.putSerializable(
+        "network",
+        LinkNetwork(
+          name = nameField.text.toString(),
+          server = DefaultNetworkServer(
+            host = hostField.text.toString(),
+            port = portField.text.toString().toIntOrNull()
+                   ?: if (sslEnabled.isChecked) 6697
+                   else 6667,
+            secure = sslEnabled.isChecked
+          )
+        )
+      )
+    }
+  }
+
+  override fun onCreateContent(inflater: LayoutInflater, container: ViewGroup?,
+                               savedInstanceState: Bundle?): View {
+    val view = inflater.inflate(R.layout.setup_network_network, container, false)
+    ButterKnife.bind(this, view)
+    nameValidator = object : TextValidator(
+      requireActivity(), nameWrapper::setError, resources.getString(R.string.hint_invalid_name)
+    ) {
+      override fun validate(text: Editable) = text.isNotBlank()
+
+      override fun onChanged() = updateValidity()
+    }
+    hostValidator = object : TextValidator(
+      requireActivity(), hostWrapper::setError, resources.getString(R.string.hint_invalid_host)
+    ) {
+      override fun validate(text: Editable) =
+        text.toString().matches(Patterns.DOMAIN_NAME)
+
+      override fun onChanged() = updateValidity()
+    }
+    portValidator = object : TextValidator(
+      requireActivity(), portWrapper::setError, resources.getString(R.string.hint_invalid_port)
+    ) {
+      override fun validate(text: Editable) = text.toString().toIntOrNull() in (0 until 65536)
+
+      override fun onChanged() = updateValidity()
+    }
+
+    nameField.addTextChangedListener(nameValidator)
+    hostField.addTextChangedListener(hostValidator)
+    portField.addTextChangedListener(portValidator)
+    nameValidator.afterTextChanged(nameField.text)
+    hostValidator.afterTextChanged(hostField.text)
+    portValidator.afterTextChanged(portField.text)
+
+    network.adapter = networkAdapter
+    network.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+      fun selected(item: INetwork.NetworkInfo?) {
+        if (item == null) {
+          AnimationHelper.expand(networkGroup)
+        } else {
+          AnimationHelper.collapse(networkGroup)
+        }
+        updateValidity()
+      }
+
+      override fun onNothingSelected(parent: AdapterView<*>?) = selected(null)
+
+      override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) =
+        selected(networkAdapter.getItem(position))
+    }
+
+    identity.adapter = identityAdapter
+
+    viewModel.identities.switchMap {
+      combineLatest(it.values.map(Identity::liveUpdates)).map {
+        it.sortedBy(Identity::identityName)
+      }
+    }.toLiveData().observe(this, Observer {
+      if (it != null) {
+        identityAdapter.submitList(it)
+      }
+    })
+
+    viewModel.networks.toLiveData().observe(this, Observer {
+      if (it != null) {
+        this.networks = it.values.map(Network::networkInfo)
+        update()
+      }
+    })
+
+    identity.adapter = identityAdapter
+
+    sslEnabled.setOnCheckedChangeListener { _, isChecked ->
+      val portValue = portField.text.trim().toString()
+      if (isChecked && portValue == IrcPorts.normal.toString()) {
+        portField.setText(IrcPorts.secure.toString())
+      } else if (!isChecked && portValue == IrcPorts.secure.toString()) {
+        portField.setText(IrcPorts.normal.toString())
+      }
+    }
+
+    return view
+  }
+
+  private var hasSetUi = false
+  private var hasSetNetwork = false
+
+  private fun update() {
+    val data = this.data
+    val networks = this.networks
+
+    if (data != null && networks != null) {
+      networkAdapter.submitList(listOf(null) + networks)
+      val linkNetwork = data.getSerializable("network") as? LinkNetwork
+
+      val existingNetwork = networks.firstOrNull {
+        it.serverList.any {
+          it.host == linkNetwork?.server?.host
+        }
+      }
+
+      if (!hasSetNetwork) {
+        val networkPosition = networkAdapter.indexOf(existingNetwork?.networkId ?: -1) ?: -1
+        if (networkPosition != -1) {
+          network.setSelection(networkPosition)
+          hasSetNetwork = true
+        }
+      }
+
+      if (!hasSetUi) {
+        if (linkNetwork != null && !hasSetUi) {
+          nameField.setText(linkNetwork.name)
+          hostField.setText(linkNetwork.server.host)
+          portField.setText("${linkNetwork.server.port}")
+          sslEnabled.isChecked = linkNetwork.server.secure
+        }
+
+        if (data.containsKey("identity")) {
+          val identity = data.getInt("identity", -1)
+          if (identity != -1) {
+            val position = identityAdapter.indexOf(identity)
+            if (position == -1) {
+              this.identity.setSelection(-1)
+            }
+          }
+        }
+      }
+
+      updateValidity()
+    }
+  }
+
+  private lateinit var nameValidator: TextValidator
+  private lateinit var hostValidator: TextValidator
+  private lateinit var portValidator: TextValidator
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/user/UserSetupNetworkSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/user/UserSetupNetworkSlide.kt
index 4bfe7870b93a06a59b9a2af813c873eac8d120b7..27d71714f5ff8856f8b0acb5afa1c1db4ca4f64e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/user/UserSetupNetworkSlide.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/user/UserSetupNetworkSlide.kt
@@ -79,8 +79,8 @@ class UserSetupNetworkSlide : SlideFragment() {
            (nameValidator.isValid && hostValidator.isValid && portValidator.isValid)
   }
 
-  override val title = R.string.slide_account_connection_title
-  override val description = R.string.slide_account_connection_description
+  override val title = R.string.slide_user_network_title
+  override val description = R.string.slide_user_network_description
 
   override fun setData(data: Bundle) {
     if (data.containsKey("network")) {
@@ -162,6 +162,7 @@ class UserSetupNetworkSlide : SlideFragment() {
         } else {
           AnimationHelper.collapse(networkGroup)
         }
+        updateValidity()
       }
 
       override fun onNothingSelected(parent: AdapterView<*>?) = selected(null)
@@ -177,10 +178,10 @@ class UserSetupNetworkSlide : SlideFragment() {
 
     sslEnabled.setOnCheckedChangeListener { _, isChecked ->
       val portValue = portField.text.trim().toString()
-      if (isChecked && portValue == IrcPorts.normal) {
-        portField.setText(IrcPorts.secure)
-      } else if (!isChecked && portValue == IrcPorts.secure) {
-        portField.setText(IrcPorts.normal)
+      if (isChecked && portValue == IrcPorts.normal.toString()) {
+        portField.setText(IrcPorts.secure.toString())
+      } else if (!isChecked && portValue == IrcPorts.secure.toString()) {
+        portField.setText(IrcPorts.normal.toString())
       }
     }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/IrcPorts.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/IrcPorts.kt
index 425b53ef26490c9e935a7edad6c7dcc4840acd92..b2ac159e981a3e00fb23ecd02c67ec8794d9ed50 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/IrcPorts.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/IrcPorts.kt
@@ -20,6 +20,6 @@
 package de.kuschku.quasseldroid.util.irc
 
 object IrcPorts {
-  const val normal = "6667"
-  const val secure = "6697"
+  const val normal = 6667
+  const val secure = 6697
 }
diff --git a/app/src/main/res/layout/setup_network_network.xml b/app/src/main/res/layout/setup_network_network.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1a135c926479ed168f7f632ae8df09a6eb1cdb0e
--- /dev/null
+++ b/app/src/main/res/layout/setup_network_network.xml
@@ -0,0 +1,112 @@
+<!--
+  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/>.
+  -->
+
+<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:orientation="vertical"
+  android:padding="32dp">
+
+  <TextView
+    style="@style/Widget.CoreSettings.EditTextHeader"
+    android:text="@string/settings_network_title" />
+
+  <Spinner
+    android:id="@+id/network"
+    style="@style/Widget.FullWidthSpinner"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    tools:listitem="@layout/widget_spinner_item_inline" />
+
+  <LinearLayout
+    android:id="@+id/network_group"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+      style="@style/Widget.CoreSettings.EditTextHeader"
+      android:text="@string/settings_identity_title" />
+
+    <Spinner
+      android:id="@+id/identity"
+      style="@style/Widget.FullWidthSpinner"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      tools:listitem="@layout/widget_spinner_item_inline" />
+
+    <com.google.android.material.textfield.TextInputLayout
+      android:id="@+id/nameWrapper"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:hint="@string/settings_network_network_name"
+      tools:ignore="LabelFor">
+
+      <EditText
+        android:id="@+id/name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inputType="textUri|textNoSuggestions"
+        app:errorEnabled="true" />
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <com.google.android.material.textfield.TextInputLayout
+      android:id="@+id/hostWrapper"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:hint="@string/settings_networkserver_host"
+      tools:ignore="LabelFor">
+
+      <EditText
+        android:id="@+id/host"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inputType="textUri|textNoSuggestions"
+        app:errorEnabled="true" />
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <com.google.android.material.textfield.TextInputLayout
+      android:id="@+id/portWrapper"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:hint="@string/settings_networkserver_port"
+      app:passwordToggleEnabled="true"
+      tools:ignore="LabelFor">
+
+      <EditText
+        android:id="@+id/port"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inputType="number"
+        android:text="6667"
+        app:errorEnabled="true"
+        tools:ignore="HardcodedText" />
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <androidx.appcompat.widget.SwitchCompat
+      android:id="@+id/ssl_enabled"
+      style="@style/Widget.CoreSettings.PrimaryItemSwitch"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/settings_networkserver_ssl_enabled" />
+  </LinearLayout>
+
+</LinearLayout>
diff --git a/app/src/main/res/values/strings_setup.xml b/app/src/main/res/values/strings_setup.xml
index 484a07084b088c88a56afbd31afd16b173aa29c3..e7f1f0dbed85d6dc885cd37462f2563ff7c42900 100644
--- a/app/src/main/res/values/strings_setup.xml
+++ b/app/src/main/res/values/strings_setup.xml
@@ -23,6 +23,8 @@
   <string name="slide_account_select_description">Please select an account from the list or add one</string>
   <string name="label_user_on_host">%1$s on %2$s:%3$d</string>
 
+  <!-- Account Setup -->
+
   <!-- Account Connection -->
   <string name="slide_account_connection_title">Connection</string>
   <string name="slide_account_connection_description">First, please choose which server your core is hosted on.</string>
@@ -50,6 +52,8 @@
 
   <string name="hint_invalid_name">Name can not be empty</string>
 
+  <!-- Core Setup -->
+
   <!-- Core Authenticator Select -->
   <string name="slide_core_authenticator_select_title">Select Authentication Backend</string>
   <string name="slide_core_authenticator_select_description">Please select an authentication backend for the Quassel Core to use for authenticating users.</string>
@@ -62,6 +66,10 @@
   <string name="slide_core_backend_setup_title">Configure Storage Backend</string>
   <string name="slide_core_backend_setup_description">Please configure the selected database backend.</string>
 
+  <!-- User Setup -->
+
+  <string name="setup_user_title">Setup User</string>
+
   <!-- User Identity -->
   <string name="slide_user_identity_title">Setup Identity</string>
   <string name="slide_user_identity_description">Please choose a nickname</string>
@@ -79,4 +87,8 @@
   <string name="slide_user_channels_description">Select what channels to join.</string>
 
   <string name="label_channels">Channels</string>
+
+  <!-- Network Setup -->
+
+  <string name="setup_network_title">Setup Network</string>
 </resources>