diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d8c6bfeb3207a422759ac9724420cd9db3da3e98..f78d12dbf4ba744e3885a6dcfe3599063ed05c15 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -239,6 +239,13 @@ android:parentActivityName=".ui.setup.accounts.selection.AccountSelectionActivity" android:windowSoftInputMode="adjustResize" /> + <!-- Core Setup Flow --> + <activity + android:name=".ui.setup.core.CoreSetupActivity" + android:exported="false" + android:label="@string/setup_core_title" + android:parentActivityName=".ui.chat.ChatActivity" + android:windowSoftInputMode="adjustResize" /> <!-- Core User Setup Flow --> <activity android:name=".ui.setup.user.UserSetupActivity" @@ -246,6 +253,7 @@ android:label="@string/setup_user_title" android:parentActivityName=".ui.chat.ChatActivity" android:windowSoftInputMode="adjustResize" /> + <!-- Network Setup Flow --> <activity android:name=".ui.setup.network.NetworkSetupActivity" android:exported="false" 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 21b8e918abbfcb298af01854ad9b61c619a75fb1..0d955e3b22988bf883b2f02d0bb8435f45f97194 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.core.CoreSetupActivity +import de.kuschku.quasseldroid.ui.setup.core.CoreSetupFragmentProvider import de.kuschku.quasseldroid.ui.setup.network.NetworkSetupActivity import de.kuschku.quasseldroid.ui.setup.network.NetworkSetupFragmentProvider import de.kuschku.quasseldroid.ui.setup.user.UserSetupActivity @@ -207,6 +209,10 @@ abstract class ActivityModule { @ContributesAndroidInjector(modules = [NetworkSetupFragmentProvider::class, SettingsModule::class, DatabaseModule::class, ActivityBaseModule::class]) abstract fun bindNetworkSetupActivity(): NetworkSetupActivity + @ActivityScope + @ContributesAndroidInjector(modules = [CoreSetupFragmentProvider::class, SettingsModule::class, DatabaseModule::class, ActivityBaseModule::class]) + abstract fun bindCoreSetupActivity(): CoreSetupActivity + @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 dad3a2e534a4e8e7873ad5a89b00137c6b110658..dbde648ea40f9ef7f94d014466480df812df7cf7 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 @@ -60,6 +60,7 @@ import de.kuschku.libquassel.protocol.Buffer_Type import de.kuschku.libquassel.protocol.Message import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.libquassel.protocol.NetworkId +import de.kuschku.libquassel.protocol.coresetup.CoreSetupData import de.kuschku.libquassel.protocol.message.HandshakeMessage import de.kuschku.libquassel.session.Error import de.kuschku.libquassel.session.ISession @@ -84,6 +85,7 @@ 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.core.CoreSetupActivity import de.kuschku.quasseldroid.ui.setup.network.LinkNetwork import de.kuschku.quasseldroid.ui.setup.network.NetworkSetupActivity import de.kuschku.quasseldroid.ui.setup.user.UserSetupActivity @@ -337,11 +339,21 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc when (it) { is Error.HandshakeError -> it.message.let { when (it) { + is HandshakeMessage.ClientInitAck -> + if (it.coreConfigured == false) + CoreSetupActivity.launch( + this, + accountDatabase.accounts().findById(accountId), + CoreSetupData.of(it) + ) is HandshakeMessage.ClientInitReject -> MaterialDialog.Builder(this) .title(R.string.label_error_init) .content(Html.fromHtml(it.errorString)) - .neutralText(R.string.label_close) + .negativeText(R.string.label_disconnect) + .onNegative { _, _ -> + disconnect() + } .titleColorAttr(R.attr.colorTextPrimary) .backgroundColorAttr(R.attr.colorBackgroundCard) .contentColorAttr(R.attr.colorTextPrimary) @@ -351,7 +363,10 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc MaterialDialog.Builder(this) .title(R.string.label_error_setup) .content(Html.fromHtml(it.errorString)) - .neutralText(R.string.label_close) + .negativeText(R.string.label_disconnect) + .onNegative { _, _ -> + disconnect() + } .titleColorAttr(R.attr.colorTextPrimary) .backgroundColorAttr(R.attr.colorBackgroundCard) .contentColorAttr(R.attr.colorTextPrimary) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSetupActivity.kt index b0c89b6cfc2c1039a744d8645c84a3009fe1705c..5966688b4eddacbd0231ac802774cb78c8001b21 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSetupActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSetupActivity.kt @@ -22,16 +22,13 @@ package de.kuschku.quasseldroid.ui.setup import android.content.Context import android.content.SharedPreferences import android.content.pm.PackageManager +import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle -import android.os.Parcelable -import android.util.SparseArray -import android.view.ViewGroup import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.appcompat.widget.ActionMenuView -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter +import androidx.core.graphics.drawable.DrawableCompat import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.viewpager.widget.ViewPager @@ -123,13 +120,20 @@ abstract class ServiceBoundSetupActivity : private lateinit var pageChangeListener: SetupActivityViewPagerPageChangeListener private fun pageChanged() { - currentPage.value = adapter.getItem(viewPager.currentItem) - val drawable = if (viewPager.currentItem == adapter.totalCount - 1) - R.drawable.ic_check - else - R.drawable.ic_arrow_right - button.setImageResource(drawable) - currentPage.value?.requestFocus() + val page = adapter.getItem(viewPager.currentItem) + currentPage.value = page + + val finish = viewPager.currentItem == adapter.totalCount - 1 + button.contentDescription = + if (finish) descriptionFinish + else descriptionNext + button.setTooltip() + button.setImageDrawable( + if (finish) drawableFinish + else drawableNext + ) + + page.requestFocus() } fun updateRecentsHeader() { @@ -143,6 +147,12 @@ abstract class ServiceBoundSetupActivity : updateRecentsHeader() } + private var drawableFinish: Drawable? = null + private var drawableNext: Drawable? = null + + private var descriptionFinish: String? = null + private var descriptionNext: String? = null + override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.Theme_SetupTheme) super.onCreate(savedInstanceState) @@ -152,6 +162,16 @@ abstract class ServiceBoundSetupActivity : setContentView(R.layout.activity_setup) ButterKnife.bind(this) + drawableFinish = getVectorDrawableCompat(R.drawable.ic_check)?.mutate()?.also { + DrawableCompat.setTint(it, -1) + } + drawableNext = getVectorDrawableCompat(R.drawable.ic_arrow_right)?.mutate()?.also { + DrawableCompat.setTint(it, -1) + } + + descriptionFinish = getString(R.string.label_finish) + descriptionNext = getString(R.string.label_next) + menuView.popupTheme = R.style.Widget_PopupOverlay_Light menuInflater.inflate(R.menu.activity_setup, menuView.menu) menuView.setOnMenuItemClickListener { @@ -239,70 +259,6 @@ abstract class ServiceBoundSetupActivity : pageChanged() } - private class SlidePagerAdapter(private val fragmentManager: FragmentManager) : - FragmentStatePagerAdapter(fragmentManager) { - private val retainedFragments = SparseArray<SlideFragment>() - - val result = Bundle() - get() { - (0 until retainedFragments.size()).map(retainedFragments::valueAt).forEach { - it.getData(field) - } - return field - } - - var lastValidItem = -1 - set(value) { - field = value - notifyDataSetChanged() - } - private val list = mutableListOf<SlideFragment>() - - override fun getItem(position: Int): SlideFragment { - return retainedFragments.get(position) ?: list[position] - } - - override fun getCount() = Math.min(list.size, lastValidItem + 2) - val totalCount get() = list.size - fun addFragment(fragment: SlideFragment) { - list.add(fragment) - } - - override fun instantiateItem(container: ViewGroup, position: Int): Any { - val fragment = super.instantiateItem(container, position) - storeNewFragment(position, fragment as SlideFragment) - return fragment - } - - override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { - retainedFragments.get(position)?.getData(result) - retainedFragments.remove(position) - super.destroyItem(container, position, `object`) - } - - override fun restoreState(state: Parcelable?, loader: ClassLoader?) { - super.restoreState(state, loader) - if (state != null) { - val bundle = state as Bundle - val keys = bundle.keySet() - for (key in keys) { - if (key.startsWith("f")) { - val index = Integer.parseInt(key.substring(1)) - val f = fragmentManager.getFragment(bundle, key) - if (f != null && f is SlideFragment) { - storeNewFragment(index, f) - } - } - } - } - } - - private fun storeNewFragment(index: Int, fragment: SlideFragment) { - fragment.initData = result - retainedFragments.put(index, fragment) - } - } - override fun onStart() { if (Settings.appearance(this) != appearanceSettings) { recreate() diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt index 9b1df118651b2ebe9a7f2ef36787c9fadbc34bc9..fcc7790be186b44bf5485c7436b52f408d21d41d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SetupActivity.kt @@ -24,15 +24,10 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle -import android.os.Parcelable -import android.util.SparseArray -import android.view.ViewGroup import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.appcompat.widget.ActionMenuView import androidx.core.graphics.drawable.DrawableCompat -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentStatePagerAdapter import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.viewpager.widget.ViewPager @@ -225,70 +220,6 @@ abstract class SetupActivity : DaggerAppCompatActivity() { pageChanged() } - private class SlidePagerAdapter(private val fragmentManager: FragmentManager) : - FragmentStatePagerAdapter(fragmentManager) { - private val retainedFragments = SparseArray<SlideFragment>() - - val result = Bundle() - get() { - (0 until retainedFragments.size()).map(retainedFragments::valueAt).forEach { - it.getData(field) - } - return field - } - - var lastValidItem = -1 - set(value) { - field = value - notifyDataSetChanged() - } - private val list = mutableListOf<SlideFragment>() - - override fun getItem(position: Int): SlideFragment { - return retainedFragments.get(position) ?: list[position] - } - - override fun getCount() = Math.min(list.size, lastValidItem + 2) - val totalCount get() = list.size - fun addFragment(fragment: SlideFragment) { - list.add(fragment) - } - - override fun instantiateItem(container: ViewGroup, position: Int): Any { - val fragment = super.instantiateItem(container, position) - storeNewFragment(position, fragment as SlideFragment) - return fragment - } - - override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { - retainedFragments.get(position)?.getData(result) - retainedFragments.remove(position) - super.destroyItem(container, position, `object`) - } - - override fun restoreState(state: Parcelable?, loader: ClassLoader?) { - super.restoreState(state, loader) - if (state != null) { - val bundle = state as Bundle - val keys = bundle.keySet() - for (key in keys) { - if (key.startsWith("f")) { - val index = Integer.parseInt(key.substring(1)) - val f = fragmentManager.getFragment(bundle, key) - if (f != null && f is SlideFragment) { - storeNewFragment(index, f) - } - } - } - } - } - - private fun storeNewFragment(index: Int, fragment: SlideFragment) { - fragment.initData = result - retainedFragments.put(index, fragment) - } - } - override fun onBackPressed() { if (viewPager.currentItem == 0) super.onBackPressed() diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SlideFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SlideFragment.kt index 7e95b20ed6b07b45d89e176482d4961565a820b0..96d5c5ef00a648116d1d1ad6fe314b27be530cff 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SlideFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SlideFragment.kt @@ -92,6 +92,15 @@ abstract class SlideFragment : DaggerFragment() { this.view?.requestFocus() } + private var hasChangedListener: ((SlideFragment) -> Unit)? = null + fun setHasChangedListener(listener: ((SlideFragment) -> Unit)?) { + hasChangedListener = listener + } + + protected fun hasChanged() { + hasChangedListener?.invoke(this) + } + protected abstract fun onCreateContent(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SlidePagerAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SlidePagerAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..6db79e0793ea60a95b91290e2673b0fbceb923da --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/SlidePagerAdapter.kt @@ -0,0 +1,99 @@ +/* + * 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.os.Bundle +import android.os.Parcelable +import android.util.SparseArray +import android.view.ViewGroup +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentStatePagerAdapter + +class SlidePagerAdapter(private val fragmentManager: FragmentManager) : + FragmentStatePagerAdapter(fragmentManager) { + private val retainedFragments = SparseArray<SlideFragment>() + + val result = Bundle() + get() { + (0 until retainedFragments.size()).map(retainedFragments::valueAt).forEach { + it.getData(field) + } + return field + } + + var lastValidItem = -1 + set(value) { + field = value + notifyDataSetChanged() + } + private val list = mutableListOf<SlideFragment>() + + override fun getItem(position: Int): SlideFragment { + return retainedFragments.get(position) ?: list[position] + } + + override fun getCount() = Math.min(list.size, lastValidItem + 2) + val totalCount get() = list.size + fun addFragment(fragment: SlideFragment) { + list.add(fragment) + } + + override fun instantiateItem(container: ViewGroup, position: Int): Any { + val fragment = super.instantiateItem(container, position) + storeNewFragment(position, fragment as SlideFragment) + return fragment + } + + override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) { + retainedFragments.get(position)?.getData(result) + retainedFragments.remove(position) + super.destroyItem(container, position, `object`) + } + + override fun restoreState(state: Parcelable?, loader: ClassLoader?) { + super.restoreState(state, loader) + if (state != null) { + val bundle = state as Bundle + val keys = bundle.keySet() + for (key in keys) { + if (key.startsWith("f")) { + val index = Integer.parseInt(key.substring(1)) + val f = fragmentManager.getFragment(bundle, key) + if (f != null && f is SlideFragment) { + storeNewFragment(index, f) + } + } + } + } + } + + private fun storeNewFragment(index: Int, fragment: SlideFragment) { + fragment.initData = result + fragment.setHasChangedListener { hasChanged(index, fragment) } + retainedFragments.put(index, fragment) + } + + fun hasChanged(index: Int, fragment: SlideFragment) { + fragment.getData(result) + if (index > -1 && index < totalCount) { + getItem(index + 1).setData(result) + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreAuthenticatorBackendChooseSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreAuthenticatorBackendChooseSlide.kt new file mode 100644 index 0000000000000000000000000000000000000000..25749886198b259d51e2ce40bfe1fe2062090531 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreAuthenticatorBackendChooseSlide.kt @@ -0,0 +1,31 @@ +/* + * 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.core + +import de.kuschku.libquassel.protocol.coresetup.CoreSetupData +import de.kuschku.quasseldroid.R + +class CoreAuthenticatorBackendChooseSlide : CoreBackendChooseSlide() { + override val title = R.string.slide_core_authenticator_select_title + override val description = R.string.slide_core_authenticator_select_description + + override val inputKey = CoreSetupData::authenticatorInfo + override val outputKey = "authenticator" +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreAuthenticatorBackendSetupSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreAuthenticatorBackendSetupSlide.kt new file mode 100644 index 0000000000000000000000000000000000000000..9e98906377b70179127df24cd08c51b60574c20e --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreAuthenticatorBackendSetupSlide.kt @@ -0,0 +1,30 @@ +/* + * 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.core + +import de.kuschku.quasseldroid.R + +class CoreAuthenticatorBackendSetupSlide : CoreBackendSetupSlide() { + override val title = R.string.slide_core_backend_setup_title + override val description = R.string.slide_core_backend_setup_description + + override val inputKey = "authenticator" + override val outputKey = "authenticatorSetup" +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..6ac4024e6cbd3ab98f65f2fd93de87f311c826bc --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendAdapter.kt @@ -0,0 +1,150 @@ +/* + * 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.core + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.widget.AppCompatRadioButton +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.libquassel.protocol.coresetup.CoreSetupBackend +import de.kuschku.quasseldroid.R + +class CoreBackendAdapter : RecyclerView.Adapter<CoreBackendAdapter.BackendViewHolder>() { + private val selectionListeners = mutableSetOf<(CoreSetupBackend) -> Unit>() + private var selectedItem: Pair<CoreSetupBackend?, CoreSetupBackend?> = Pair(null, null) + + fun selection() = selectedItem.second + + private val clickListener = { item: CoreSetupBackend -> + selectionListener.invoke(item) + } + + fun updateSelection(item: CoreSetupBackend) { + selectedItem = Pair(selectedItem.second, item) + submitList() + } + + private val selectionListener = { item: CoreSetupBackend -> + updateSelection(item) + for (selectionListener in selectionListeners) { + selectionListener.invoke(item) + } + } + + private var list: List<Pair<Boolean, CoreSetupBackend>> = emptyList() + + fun submitList( + list: List<CoreSetupBackend> = this.list.map(Pair<Boolean, CoreSetupBackend>::second)) { + val oldList = this.list + + val oldSelected = selectedItem.first + val newSelected = selectedItem.second + + val newList = list.map { + Pair(it == newSelected, it) + } + this.list = newList + + DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldList[oldItemPosition].second + val newItem = newList[newItemPosition].second + + return oldItem == newItem + } + + override fun getOldListSize(): Int { + return oldList.size + } + + override fun getNewListSize(): Int { + return newList.size + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = oldList[oldItemPosition].second + val newItem = newList[newItemPosition].second + + return oldItem == newItem && + oldItem != newSelected && + newItem != newSelected && + oldItem != oldSelected && + newItem != oldSelected + } + }).dispatchUpdatesTo(this) + } + + fun addSelectionListener(f: (CoreSetupBackend) -> Unit) { + selectionListeners.add(f) + } + + override fun onBindViewHolder(holder: BackendViewHolder, position: Int) { + val item = list[position] + holder.bind(item.second, item.first) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackendViewHolder { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.widget_core_backend, parent, false) + return BackendViewHolder(view, clickListener) + } + + override fun getItemCount() = list.size + + class BackendViewHolder(itemView: View, clickListener: (CoreSetupBackend) -> Unit) : + RecyclerView.ViewHolder(itemView) { + @BindView(R.id.backend_name) + lateinit var backendName: TextView + + @BindView(R.id.backend_description) + lateinit var backendDescription: TextView + + @BindView(R.id.backend_select) + lateinit var backendSelect: AppCompatRadioButton + + private var item: CoreSetupBackend? = null + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + item?.let(clickListener) + } + } + + fun bind(backend: CoreSetupBackend, selected: Boolean) { + item = backend + backendName.text = backend.displayName + backendDescription.text = backend.description + backendSelect.isChecked = selected + } + + fun clear() { + item = null + backendName.text = "" + backendDescription.text = "" + backendSelect.isChecked = false + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendChooseSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendChooseSlide.kt new file mode 100644 index 0000000000000000000000000000000000000000..98613658bf49b5c289a5f7681de5f482e479c356 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendChooseSlide.kt @@ -0,0 +1,75 @@ +/* + * 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.core + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.libquassel.protocol.coresetup.CoreSetupBackend +import de.kuschku.libquassel.protocol.coresetup.CoreSetupData +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.ui.setup.SlideFragment + +abstract class CoreBackendChooseSlide : SlideFragment() { + @BindView(R.id.account_list) + lateinit var backendList: RecyclerView + + override fun isValid() = adapter.selection() != null + + protected val adapter = CoreBackendAdapter() + + abstract val outputKey: String + abstract val inputKey: ((CoreSetupData) -> List<CoreSetupBackend>) + + override fun getData(data: Bundle) { + data.putSerializable( + outputKey, + adapter.selection() + ) + } + + override fun setData(data: Bundle) { + val message = data.getSerializable("data") as? CoreSetupData + adapter.submitList(message?.let(inputKey).orEmpty()) + (data.getSerializable(outputKey) as? CoreSetupBackend)?.let(adapter::updateSelection) + } + + override fun onCreateContent(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + val view = inflater.inflate(R.layout.setup_select_account, container, false) + ButterKnife.bind(this, view) + + backendList.layoutManager = LinearLayoutManager(context) + backendList.itemAnimator = DefaultItemAnimator() + backendList.adapter = adapter + adapter.addSelectionListener { + updateValidity() + hasChanged() + } + + return view + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendSetupSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendSetupSlide.kt new file mode 100644 index 0000000000000000000000000000000000000000..7f18d97a70137b07fc0cdc70132902c66b498e75 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreBackendSetupSlide.kt @@ -0,0 +1,80 @@ +/* + * 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.core + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.libquassel.protocol.QVariant_ +import de.kuschku.libquassel.protocol.coresetup.CoreSetupBackend +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.ui.setup.SlideFragment + +abstract class CoreBackendSetupSlide : SlideFragment() { + @BindView(R.id.frame) + lateinit var frame: LinearLayout + + @BindView(R.id.no_options_info) + lateinit var noOptionsInfo: View + + override fun isValid() = true + + abstract val inputKey: String + abstract val outputKey: String + + private var widgets = mutableListOf<QuasselSetupEntry>() + + override fun setData(data: Bundle) { + widgets.clear() + frame.removeAllViews() + noOptionsInfo.visibility = View.VISIBLE + + val backend = data.getSerializable(inputKey) as? CoreSetupBackend + for (configEntry in backend?.setupData.orEmpty()) { + val entry = QuasselSetupEntry(frame.context, + null, + 0, + configEntry) + widgets.add(entry) + frame.addView(entry) + noOptionsInfo.visibility = View.GONE + } + } + + override fun getData(data: Bundle) { + val setupData = HashMap<String, QVariant_>() + for (configEntry in widgets) { + setupData[configEntry.key()] = configEntry.value() + } + data.putSerializable(outputKey, setupData) + } + + override fun onCreateContent(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View { + val view = inflater.inflate(R.layout.setup_core_backend_configure, container, false) + ButterKnife.bind(this, view) + + return view + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreSetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreSetupActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..d19699b254f7936a74a34573c3b7aaa47bf450a9 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreSetupActivity.kt @@ -0,0 +1,108 @@ +/* + * 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.core + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import de.kuschku.libquassel.protocol.QVariant_ +import de.kuschku.libquassel.protocol.coresetup.CoreSetupBackend +import de.kuschku.libquassel.protocol.coresetup.CoreSetupData +import de.kuschku.libquassel.protocol.message.HandshakeMessage +import de.kuschku.libquassel.quassel.ExtendedFeature +import de.kuschku.libquassel.util.helpers.value +import de.kuschku.quasseldroid.persistence.AccountDatabase +import de.kuschku.quasseldroid.ui.setup.ServiceBoundSetupActivity + +class CoreSetupActivity : ServiceBoundSetupActivity() { + override val initData = Bundle() + + override fun onCreate(savedInstanceState: Bundle?) { + initData.clear() + initData.putAll(intent.extras) + super.onCreate(savedInstanceState) + } + + override fun onDone(data: Bundle) { + val user = initData.getString("user") + val pass = initData.getString("pass") + + val storageBackend = data.getSerializable("storage") as? CoreSetupBackend + val storageBackendSetup = data.getSerializable("storageSetup") as? HashMap<String, QVariant_> + + val authenticatorBackend = data.getSerializable("authenticator") as? CoreSetupBackend + val authenticatorBackendSetup = data.getSerializable("authenticatorSetup") as? HashMap<String, QVariant_> + + val setupData = HandshakeMessage.CoreSetupData( + adminUser = user, + adminPassword = pass, + backend = storageBackend?.backendId, + setupData = storageBackendSetup.orEmpty(), + authenticator = authenticatorBackend?.backendId, + authSetupData = authenticatorBackendSetup.orEmpty() + ) + + viewModel.sessionManager.value?.orNull()?.setupCore(setupData) + println(setupData) + + setResult(Activity.RESULT_OK) + finish() + } + + override val fragments + get() = if ((initData.getSerializable("data") as? CoreSetupData) + ?.features + ?.hasFeature(ExtendedFeature.Authenticators) == true) { + listOf( + CoreStorageBackendChooseSlide(), + CoreStorageBackendSetupSlide(), + CoreAuthenticatorBackendChooseSlide(), + CoreAuthenticatorBackendSetupSlide() + ) + } else { + listOf( + CoreStorageBackendChooseSlide(), + CoreStorageBackendSetupSlide() + ) + } + + companion object { + fun launch( + context: Context, + account: AccountDatabase.Account? = null, + data: CoreSetupData? = null + ) = context.startActivity(intent(context, account, data)) + + fun intent( + context: Context, + account: AccountDatabase.Account? = null, + data: CoreSetupData? = null + ) = Intent(context, CoreSetupActivity::class.java).apply { + if (account != null) { + putExtra("user", account.user) + putExtra("pass", account.pass) + } + if (data != null) { + putExtra("data", data) + } + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreSetupFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreSetupFragmentProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..e5f524b9510684207bd410cb51f3903ac1df35ed --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreSetupFragmentProvider.kt @@ -0,0 +1,43 @@ +/* + * 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.core + +import androidx.fragment.app.FragmentActivity +import dagger.Binds +import dagger.Module +import dagger.android.ContributesAndroidInjector + +@Module +abstract class CoreSetupFragmentProvider { + @Binds + abstract fun bindFragmentActivity(activity: CoreSetupActivity): FragmentActivity + + @ContributesAndroidInjector + abstract fun bindCoreStorageBackendChooseSlide(): CoreStorageBackendChooseSlide + + @ContributesAndroidInjector + abstract fun bindCoreStorageBackendSetupSlide(): CoreStorageBackendSetupSlide + + @ContributesAndroidInjector + abstract fun bindCoreAuthenticatorBackendChooseSlide(): CoreAuthenticatorBackendChooseSlide + + @ContributesAndroidInjector + abstract fun bindCoreAuthenticatorBackendSetupSlide(): CoreAuthenticatorBackendSetupSlide +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreStorageBackendChooseSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreStorageBackendChooseSlide.kt new file mode 100644 index 0000000000000000000000000000000000000000..74ba5f5801c519964940676ca631948378509b51 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreStorageBackendChooseSlide.kt @@ -0,0 +1,31 @@ +/* + * 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.core + +import de.kuschku.libquassel.protocol.coresetup.CoreSetupData +import de.kuschku.quasseldroid.R + +class CoreStorageBackendChooseSlide : CoreBackendChooseSlide() { + override val title = R.string.slide_core_backend_select_title + override val description = R.string.slide_core_backend_select_description + + override val inputKey = CoreSetupData::backendInfo + override val outputKey = "storage" +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreStorageBackendSetupSlide.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreStorageBackendSetupSlide.kt new file mode 100644 index 0000000000000000000000000000000000000000..b9b3c0539b0377cb96603a3ee677288d7574e5a7 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/CoreStorageBackendSetupSlide.kt @@ -0,0 +1,30 @@ +/* + * 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.core + +import de.kuschku.quasseldroid.R + +class CoreStorageBackendSetupSlide : CoreBackendSetupSlide() { + override val title = R.string.slide_core_backend_setup_title + override val description = R.string.slide_core_backend_setup_description + + override val inputKey = "storage" + override val outputKey = "storageSetup" +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/QuasselSetupEntry.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/QuasselSetupEntry.kt new file mode 100644 index 0000000000000000000000000000000000000000..8c7f376ea087588fb0405a3e0d500479e01e88c7 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/core/QuasselSetupEntry.kt @@ -0,0 +1,116 @@ +/* + * 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.core + +import android.content.Context +import android.text.InputType +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import butterknife.BindView +import butterknife.ButterKnife +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import de.kuschku.libquassel.protocol.QVariant_ +import de.kuschku.libquassel.protocol.Type +import de.kuschku.libquassel.protocol.coresetup.CoreSetupBackendConfigElement +import de.kuschku.libquassel.protocol.value +import de.kuschku.quasseldroid.R + +class QuasselSetupEntry : FrameLayout { + @BindView(R.id.wrapper) + lateinit var wrapper: TextInputLayout + + @BindView(R.id.field) + lateinit var field: TextInputEditText + + private var data: CoreSetupBackendConfigElement? = null + + constructor(context: Context) : + this(context, null) + + constructor(context: Context, attrs: AttributeSet?) : + this(context, attrs, 0) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : + this(context, attrs, defStyleAttr, null) + + constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, + data: CoreSetupBackendConfigElement? = null + ) : super(context, attrs, defStyleAttr) { + + LayoutInflater.from(context).inflate(R.layout.widget_quassel_setup_entry, this, true) + ButterKnife.bind(this) + + if (data != null) { + this.data = data + + wrapper.hint = data.displayName + when { + data.defaultValue.type == Type.QString && + data.key.contains("password", ignoreCase = true) -> { + wrapper.isPasswordVisibilityToggleEnabled = true + field.inputType = + InputType.TYPE_CLASS_TEXT or + InputType.TYPE_TEXT_VARIATION_PASSWORD + field.setText(data.defaultValue.value("")) + } + data.defaultValue.type == Type.QString && + data.key.contains("hostname", ignoreCase = true) -> { + field.inputType = + InputType.TYPE_CLASS_TEXT or + InputType.TYPE_TEXT_VARIATION_URI + field.setText(data.defaultValue.value("")) + } + data.defaultValue.type == Type.QString -> { + field.inputType = + InputType.TYPE_CLASS_TEXT or + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or + InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS + field.setText(data.defaultValue.value("")) + } + data.defaultValue.type == Type.Int -> { + field.inputType = + InputType.TYPE_CLASS_NUMBER + field.setText(data.defaultValue.value<Int>()?.toString()) + } + } + } + } + + fun key() = data?.key ?: "" + + fun value(): QVariant_ { + val rawValue = field.text.toString() + val data = this.data + + return when (data?.defaultValue?.type) { + Type.QString -> { + QVariant_.of(rawValue, data.defaultValue.type) + } + Type.Int -> { + QVariant_.of(rawValue.toInt(), data.defaultValue.type) + } + else -> { + QVariant_.of("", Type.QString) + } + } + } +} diff --git a/app/src/main/res/layout/setup_core_backend_configure.xml b/app/src/main/res/layout/setup_core_backend_configure.xml new file mode 100644 index 0000000000000000000000000000000000000000..00676f878007d892effdb778b9a69b9ec63514a0 --- /dev/null +++ b/app/src/main/res/layout/setup_core_backend_configure.xml @@ -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/>. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="32dp"> + + <TextView + android:id="@+id/no_options_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/setup_core_backend_no_options" /> + + <LinearLayout + android:id="@+id/frame" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" /> +</LinearLayout> diff --git a/app/src/main/res/layout/widget_core_backend.xml b/app/src/main/res/layout/widget_core_backend.xml new file mode 100644 index 0000000000000000000000000000000000000000..85324d285564a52917284a0e02f67afaed78fb80 --- /dev/null +++ b/app/src/main/res/layout/widget_core_backend.xml @@ -0,0 +1,79 @@ +<!-- + 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:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:baselineAligned="false" + android:clickable="true" + android:focusable="true" + android:focusableInTouchMode="false" + android:minHeight="72dp" + android:orientation="horizontal" + android:paddingLeft="16dp" + android:paddingRight="16dp"> + + <LinearLayout + android:layout_width="48dp" + android:layout_height="72dp"> + + <androidx.appcompat.widget.AppCompatRadioButton + android:id="@+id/backend_select" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="center_vertical" + android:background="@null" + android:clickable="false" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:layout_weight="1" + android:gravity="center_vertical|start" + android:minHeight="72dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/backend_name" + style="@style/Widget.RtlConformTextView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical|start" + android:lines="1" + android:singleLine="true" + android:textSize="14sp" + tools:text="Remote" /> + + <TextView + android:id="@+id/backend_description" + style="@style/Widget.RtlConformTextView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="sans-serif" + android:gravity="center_vertical|start" + android:textSize="14sp" + tools:text="testUser on localhost" /> + </LinearLayout> +</LinearLayout> diff --git a/app/src/main/res/layout/widget_quassel_setup_entry.xml b/app/src/main/res/layout/widget_quassel_setup_entry.xml new file mode 100644 index 0000000000000000000000000000000000000000..315e876877359e485172a93cfee4b8f5418d9582 --- /dev/null +++ b/app/src/main/res/layout/widget_quassel_setup_entry.xml @@ -0,0 +1,33 @@ +<?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.textfield.TextInputLayout 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:id="@+id/wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:ignore="LabelFor"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:errorEnabled="true" /> +</com.google.android.material.textfield.TextInputLayout> diff --git a/app/src/main/res/values/strings_setup.xml b/app/src/main/res/values/strings_setup.xml index e7f1f0dbed85d6dc885cd37462f2563ff7c42900..728420f946301f80c1ed1389c4b324bedce979bd 100644 --- a/app/src/main/res/values/strings_setup.xml +++ b/app/src/main/res/values/strings_setup.xml @@ -54,6 +54,8 @@ <!-- Core Setup --> + <string name="setup_core_title">Setup Core</string> + <!-- 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,10 +64,14 @@ <string name="slide_core_backend_select_title">Select Storage Backend</string> <string name="slide_core_backend_select_description">Please select a database backend for the Quassel Core storage to store the backlog and other data in.</string> + <string name="label_backend">Backend</string> + <!-- Core Backend Config --> <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> + <string name="setup_core_backend_no_options">This backend has no configuration options, you’re done here!</string> + <!-- User Setup --> <string name="setup_user_title">Setup User</string> diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/QVariant.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/QVariant.kt index 68ca2d858dc2cad8fbbd6e9f425bda6308cdba6f..471ee1211485677188ce2ecd03aad90984dec66a 100644 --- a/lib/src/main/java/de/kuschku/libquassel/protocol/QVariant.kt +++ b/lib/src/main/java/de/kuschku/libquassel/protocol/QVariant.kt @@ -24,7 +24,7 @@ import de.kuschku.libquassel.protocol.primitive.serializer.Serializer sealed class QVariant<T> constructor(val data: T?, val type: Type, val serializer: Serializer<T>) { class Typed<T> internal constructor(data: T?, type: Type, serializer: Serializer<T>) : QVariant<T>(data, type, serializer) { - override fun toString() = "QVariant.Typed(${type.serializableName}, $data})" + override fun toString() = "QVariant.Typed(${type.serializableName}, $data)" override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Typed<*>) return false diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupBackend.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupBackend.kt new file mode 100644 index 0000000000000000000000000000000000000000..bd0d861b9ac37cb40e5ac57847eb26f6d5bbab4b --- /dev/null +++ b/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupBackend.kt @@ -0,0 +1,58 @@ +/* + * 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.libquassel.protocol.coresetup + +import de.kuschku.libquassel.protocol.* +import de.kuschku.libquassel.util.helpers.getOr +import java.io.Serializable + +data class CoreSetupBackend( + val backendId: String, + val displayName: String, + val description: String, + val setupData: List<CoreSetupBackendConfigElement> +) : Serializable { + companion object { + fun of(props: QVariantMap): CoreSetupBackend { + val entries = if (!props.containsKey("SetupData")) { + val result = mutableListOf<CoreSetupBackendConfigElement>() + val setupDefaults = props["SetupDefaults"]?.value<QVariantMap>(emptyMap()).orEmpty() + for (key in props["SetupKeys"]?.value<QStringList>(emptyList()).orEmpty()) { + val default = setupDefaults.getOr(key ?: "", QVariant_.of("", Type.QString)) + result.add(CoreSetupBackendConfigElement(key ?: "", key ?: "", default)) + } + result + } else { + props["SetupData"]?.value<QVariantList>(emptyList()).orEmpty().chunked(3) { (key, displayName, defaultValue) -> + CoreSetupBackendConfigElement(key.value(""), displayName.value(""), defaultValue) + } + } + + val fallback = QVariant_.of("", Type.QString) + + return CoreSetupBackend( + displayName = props.getOr("DisplayName", fallback).value(""), + backendId = props.getOr("BackendId", props.getOr("DisplayName", fallback)).value(""), + description = props.getOr("Description", fallback).value(""), + setupData = entries + ) + } + } +} diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupBackendConfigElement.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupBackendConfigElement.kt new file mode 100644 index 0000000000000000000000000000000000000000..a56f1053f1256638dfb9e9cf6e96e3496e8ecc04 --- /dev/null +++ b/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupBackendConfigElement.kt @@ -0,0 +1,79 @@ +/* + * 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.libquassel.protocol.coresetup + +import de.kuschku.libquassel.protocol.* +import java.io.Serializable + +class CoreSetupBackendConfigElement : Serializable { + val key: String + val displayName: String + private val typeId: Int + private val customType: String + private val rawDefaultValue: Any? + val defaultValue: QVariant_ + get() { + val type = Type.of(typeId) + return if (type == Type.UserType) { + val name = customType + val qType = QType.of(name) ?: throw IllegalArgumentException("No such type: $name") + QVariant.of<All_>(rawDefaultValue, qType) + } else { + QVariant.of<All_>(rawDefaultValue, + type ?: throw IllegalArgumentException("No such type: $type")) + } + } + + constructor(key: String, displayName: String, defaultValue: QVariant_) { + this.key = key + this.displayName = displayName + this.typeId = defaultValue.type.id + this.customType = defaultValue.type.serializableName + this.rawDefaultValue = defaultValue.data + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CoreSetupBackendConfigElement + + if (key != other.key) return false + if (displayName != other.displayName) return false + if (typeId != other.typeId) return false + if (customType != other.customType) return false + if (rawDefaultValue != other.rawDefaultValue) return false + + return true + } + + override fun hashCode(): Int { + var result = key.hashCode() + result = 31 * result + displayName.hashCode() + result = 31 * result + typeId + result = 31 * result + customType.hashCode() + result = 31 * result + (rawDefaultValue?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "CoreBackendSetupDataEntry(key='$key', displayName='$displayName', defaultValue='$defaultValue')" + } +} diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupData.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupData.kt new file mode 100644 index 0000000000000000000000000000000000000000..7bdd78a10a5ca879982c236bd4feaf8671a11f54 --- /dev/null +++ b/lib/src/main/java/de/kuschku/libquassel/protocol/coresetup/CoreSetupData.kt @@ -0,0 +1,46 @@ +/* + * 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.libquassel.protocol.coresetup + +import de.kuschku.libquassel.protocol.message.HandshakeMessage +import de.kuschku.libquassel.protocol.value +import de.kuschku.libquassel.quassel.QuasselFeatures +import java.io.Serializable + +data class CoreSetupData( + val backendInfo: List<CoreSetupBackend>, + val authenticatorInfo: List<CoreSetupBackend>, + val features: QuasselFeatures +) : Serializable { + companion object { + fun of(data: HandshakeMessage.ClientInitAck): CoreSetupData { + + return CoreSetupData( + backendInfo = data.backendInfo.orEmpty().map { + CoreSetupBackend.of(it.value(emptyMap())) + }, + authenticatorInfo = data.authenticatorInfo.orEmpty().map { + CoreSetupBackend.of(it.value(emptyMap())) + }, + features = QuasselFeatures(data.coreFeatures, data.featureList) + ) + } + } +} diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/message/HandshakeMessage.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/message/HandshakeMessage.kt index 45c8a17d5e65c9b1294a19178409ca894a39c880..f504c0aed950ac5f62b390ecacf37197ca6f8237 100644 --- a/lib/src/main/java/de/kuschku/libquassel/protocol/message/HandshakeMessage.kt +++ b/lib/src/main/java/de/kuschku/libquassel/protocol/message/HandshakeMessage.kt @@ -23,8 +23,9 @@ import de.kuschku.libquassel.protocol.Legacy_Features import de.kuschku.libquassel.protocol.QVariantList import de.kuschku.libquassel.protocol.QVariantMap import de.kuschku.libquassel.protocol.value +import java.io.Serializable -sealed class HandshakeMessage { +sealed class HandshakeMessage : Serializable { class ClientInit( val clientVersion: String?, val buildDate: String?, val clientFeatures: Legacy_Features?, val featureList: List<String> @@ -55,7 +56,7 @@ sealed class HandshakeMessage { val authSetupData: QVariantMap?) : HandshakeMessage() { override fun toString(): String { - return "CoreSetupData" + return "CoreSetupData(adminUser=$adminUser, adminPassword=$adminPassword, backend=$backend, setupData=$setupData, authenticator=$authenticator, authSetupData=$authSetupData)" } } diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/BufferInfo.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/BufferInfo.kt index 6cb5745716bfb8e003120403b42d1cef4de66516..89efe81ceb0171fea10b369a75bab3e4545332a3 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/BufferInfo.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/BufferInfo.kt @@ -19,6 +19,7 @@ package de.kuschku.libquassel.quassel +import de.kuschku.libquassel.protocol.Buffer_Type import de.kuschku.libquassel.protocol.Buffer_Types import de.kuschku.libquassel.util.flag.Flag import de.kuschku.libquassel.util.flag.Flags @@ -26,11 +27,11 @@ import de.kuschku.libquassel.util.flag.ShortFlag import de.kuschku.libquassel.util.flag.ShortFlags data class BufferInfo( - var bufferId: Int, - var networkId: Int, - var type: Buffer_Types, - var groupId: Int, - var bufferName: String? + var bufferId: Int = -1, + var networkId: Int = -1, + var type: Buffer_Types = Buffer_Type.of(), + var groupId: Int = -1, + var bufferName: String? = null ) { enum class Type(override val bit: Short) : ShortFlag<Type> { InvalidBuffer(0x00), diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/QuasselFeatures.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/QuasselFeatures.kt index 808585a4fad1e29a2316ac91d34252f53c0d7593..dd56be021ee17602cd63e2c8ff3ca1f3f544cf3d 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/QuasselFeatures.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/QuasselFeatures.kt @@ -21,11 +21,12 @@ package de.kuschku.libquassel.quassel import de.kuschku.libquassel.protocol.Legacy_Feature import de.kuschku.libquassel.protocol.Legacy_Features +import java.io.Serializable class QuasselFeatures( val enabledFeatures: Set<ExtendedFeature>, val unknownFeatures: Set<String> = emptySet() -) { +) : Serializable { constructor(legacyFeatures: Legacy_Features?, extendedFeatures: Collection<String>) : this( legacyFeatures?.enabledValues()?.map(Legacy_Feature::toExtended).orEmpty() union extendedFeatures.mapNotNull(ExtendedFeature.Companion::of), @@ -38,6 +39,11 @@ class QuasselFeatures( infix fun hasFeature(feature: ExtendedFeature) = enabledFeatures.contains(feature) + override fun toString(): String { + return "QuasselFeatures(enabledFeatures=$enabledFeatures, unknownFeatures=$unknownFeatures)" + } + + companion object { fun empty() = QuasselFeatures(emptySet(), emptySet()) fun all() = QuasselFeatures( diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt index f122fe96f9fd4bfc048f7c210d35f7d30f7572fb..8e0e595e74e50b8d56d89420cd80a713f72acbbc 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt @@ -23,6 +23,7 @@ import de.kuschku.libquassel.connection.ConnectionState import de.kuschku.libquassel.connection.Features import de.kuschku.libquassel.protocol.IdentityId import de.kuschku.libquassel.protocol.NetworkId +import de.kuschku.libquassel.protocol.message.HandshakeMessage import de.kuschku.libquassel.quassel.QuasselFeatures import de.kuschku.libquassel.quassel.syncables.* import de.kuschku.libquassel.util.Optional @@ -65,6 +66,7 @@ interface ISession : Closeable { val lag: Observable<Long> fun login(user: String, pass: String) + fun setupCore(setupData: HandshakeMessage.CoreSetupData) companion object { val NULL = object : ISession { @@ -101,6 +103,7 @@ interface ISession : Closeable { override fun identity(id: IdentityId): Identity? = null override fun login(user: String, pass: String) = Unit + override fun setupCore(setupData: HandshakeMessage.CoreSetupData) = Unit override fun close() = Unit } } diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt index fbafa47d4f1880091c6589a1d192d006c6e566f5..48a65e5d2851a678df6657be210a7b0ff5461e87 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt @@ -144,6 +144,10 @@ class Session( login() } + override fun setupCore(setupData: HandshakeMessage.CoreSetupData) { + dispatch(setupData) + } + override fun handle(f: HandshakeMessage.CoreSetupAck): Boolean { login() return true diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt index b240aa1580cdb2237705bbcd9cf3ffd97da97227..d5f18920ccf38d88d17e0d61e8a1bae3dbe3fce3 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt @@ -23,6 +23,7 @@ import de.kuschku.libquassel.connection.ConnectionState import de.kuschku.libquassel.connection.HostnameVerifier import de.kuschku.libquassel.connection.SocketAddress import de.kuschku.libquassel.protocol.ClientData +import de.kuschku.libquassel.protocol.message.HandshakeMessage import de.kuschku.libquassel.quassel.syncables.interfaces.invokers.Invokers import de.kuschku.libquassel.util.compatibility.HandlerService import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log @@ -105,6 +106,10 @@ class SessionManager( inProgressSession.value.login(user, pass) } + fun setupCore(setupData: HandshakeMessage.CoreSetupData) { + inProgressSession.value.setupCore(setupData) + } + fun connect( clientData: ClientData, trustManager: X509TrustManager,