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>