From e557c94575153622db794f5ba69a6fcdc8e5e46f Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Wed, 17 Apr 2019 14:00:36 +0200 Subject: [PATCH] Major cleanup of viewmodel handling --- app/src/main/AndroidManifest.xml | 2 +- .../quasseldroid/dagger/ActivityBaseModule.kt | 11 +- .../quasseldroid/dagger/ActivityModule.kt | 2 +- .../quasseldroid/service/BacklogRequester.kt | 7 +- .../service/QuasselNotificationBackend.kt | 3 +- .../quasseldroid/ui/chat/ChatActivity.kt | 52 +- .../quasseldroid/ui/chat/ToolbarFragment.kt | 8 +- .../chat/add/create/ChannelCreateFragment.kt | 17 +- .../chat/buffers/BufferViewConfigFragment.kt | 55 +- .../ui/chat/input/AutoCompleteHelper.kt | 11 +- .../ui/chat/input/ChatlineFragment.kt | 24 +- .../ui/chat/messages/MessageListFragment.kt | 60 +- .../chat/messages/QuasselMessageRenderer.kt | 5 +- .../ui/chat/nicks/NickListFragment.kt | 12 +- .../ui/{info => chat}/topic/TopicActivity.kt | 3 +- .../ui/chat/topic/TopicFragment.kt | 17 +- .../ui/chat/topic/TopicFragmentProvider.kt | 1 - .../ui/coresettings/CoreSettingsFragment.kt | 13 +- .../aliasitem/AliasItemFragment.kt | 9 +- .../aliaslist/AliasListFragment.kt | 7 +- .../chatlist/ChatListBaseFragment.kt | 11 +- .../chatlist/ChatListCreateFragment.kt | 2 +- .../chatlist/ChatListEditFragment.kt | 2 +- .../highlightlist/HighlightListFragment.kt | 9 +- .../identity/IdentityBaseFragment.kt | 9 +- .../identity/IdentityCreateFragment.kt | 2 +- .../identity/IdentityEditFragment.kt | 2 +- .../ignorelist/IgnoreListFragment.kt | 7 +- .../network/NetworkBaseFragment.kt | 15 +- .../network/NetworkCreateFragment.kt | 4 +- .../network/NetworkEditFragment.kt | 2 +- .../networkconfig/NetworkConfigFragment.kt | 7 +- .../passwordchange/PasswordChangeFragment.kt | 16 +- .../certificate/CertificateInfoFragment.kt | 6 +- .../ui/info/channel/ChannelInfoFragment.kt | 12 +- .../info/channellist/ChannelListFragment.kt | 8 +- .../ui/info/core/CoreInfoFragment.kt | 27 +- .../ui/info/user/UserInfoFragment.kt | 14 +- .../ui/setup/ServiceBoundSlideFragment.kt | 3 - .../ui/setup/core/CoreSetupActivity.kt | 7 +- .../ui/setup/network/NetworkSetupActivity.kt | 9 +- .../setup/network/NetworkSetupNetworkSlide.kt | 9 +- .../ui/setup/user/UserSetupActivity.kt | 11 +- .../kuschku/quasseldroid/util/ColorContext.kt | 3 +- .../util/ShortcutCreationHelper.kt | 3 +- .../util/service/ServiceBoundActivity.kt | 4 +- .../util/service/ServiceBoundFragment.kt | 2 +- viewmodel/build.gradle.kts | 2 + .../quasseldroid/viewmodel/ChatViewModel.kt | 70 +++ .../quasseldroid/viewmodel/EditorViewModel.kt | 145 ----- .../viewmodel/QuasselViewModel.kt | 553 +----------------- .../viewmodel/helper/ChatViewModelHelper.kt | 434 ++++++++++++++ .../viewmodel/helper/EditorViewModelHelper.kt | 155 +++++ .../helper/QuasselViewModelHelper.kt | 110 ++++ 54 files changed, 1100 insertions(+), 894 deletions(-) rename app/src/main/java/de/kuschku/quasseldroid/ui/{info => chat}/topic/TopicActivity.kt (92%) create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/ChatViewModel.kt create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/EditorViewModelHelper.kt create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/QuasselViewModelHelper.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 96c17e9a8..ad2730061 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -97,7 +97,7 @@ android:parentActivityName=".ui.chat.ChatActivity" android:windowSoftInputMode="adjustResize" /> <activity - android:name="de.kuschku.quasseldroid.ui.info.topic.TopicActivity" + android:name="de.kuschku.quasseldroid.ui.chat.topic.TopicActivity" android:exported="false" android:label="@string/label_topic" android:windowSoftInputMode="adjustResize" /> diff --git a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityBaseModule.kt b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityBaseModule.kt index 7887f9287..044409099 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityBaseModule.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityBaseModule.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.ViewModelProviders import dagger.Module import dagger.Provides import de.kuschku.quasseldroid.ui.setup.accounts.selection.AccountViewModel +import de.kuschku.quasseldroid.viewmodel.ChatViewModel import de.kuschku.quasseldroid.viewmodel.EditorViewModel import de.kuschku.quasseldroid.viewmodel.QuasselViewModel @@ -50,12 +51,18 @@ object ActivityBaseModule { @ActivityScope @Provides @JvmStatic - fun provideAccountViewModel(viewModelProvider: ViewModelProvider) = - viewModelProvider[AccountViewModel::class.java] + fun provideChatViewModel(viewModelProvider: ViewModelProvider) = + viewModelProvider[ChatViewModel::class.java] @ActivityScope @Provides @JvmStatic fun provideEditorViewModel(viewModelProvider: ViewModelProvider) = viewModelProvider[EditorViewModel::class.java] + + @ActivityScope + @Provides + @JvmStatic + fun provideAccountViewModel(viewModelProvider: ViewModelProvider) = + viewModelProvider[AccountViewModel::class.java] } 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 845a4fc90..5a0572b9b 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityModule.kt @@ -83,7 +83,7 @@ import de.kuschku.quasseldroid.ui.info.channellist.ChannelListActivity import de.kuschku.quasseldroid.ui.info.channellist.ChannelListFragmentProvider import de.kuschku.quasseldroid.ui.info.core.CoreInfoActivity import de.kuschku.quasseldroid.ui.info.core.CoreInfoFragmentProvider -import de.kuschku.quasseldroid.ui.info.topic.TopicActivity +import de.kuschku.quasseldroid.ui.chat.topic.TopicActivity import de.kuschku.quasseldroid.ui.info.user.UserInfoActivity import de.kuschku.quasseldroid.ui.info.user.UserInfoFragmentProvider import de.kuschku.quasseldroid.ui.setup.accounts.edit.AccountEditActivity diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/BacklogRequester.kt b/app/src/main/java/de/kuschku/quasseldroid/service/BacklogRequester.kt index 41cfe8e37..0302c6cfd 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/service/BacklogRequester.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/service/BacklogRequester.kt @@ -23,6 +23,7 @@ import de.kuschku.libquassel.protocol.BufferId import de.kuschku.libquassel.protocol.Message import de.kuschku.libquassel.protocol.MsgId import de.kuschku.libquassel.session.ISession +import de.kuschku.libquassel.util.Optional import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG import de.kuschku.libquassel.util.helpers.value @@ -31,10 +32,10 @@ import de.kuschku.quasseldroid.persistence.dao.get import de.kuschku.quasseldroid.persistence.db.AccountDatabase import de.kuschku.quasseldroid.persistence.db.QuasselDatabase import de.kuschku.quasseldroid.persistence.util.QuasselBacklogStorage -import de.kuschku.quasseldroid.viewmodel.QuasselViewModel +import io.reactivex.Observable class BacklogRequester( - private val viewModel: QuasselViewModel, + private val session: Observable<Optional<ISession>>, private val database: QuasselDatabase, private val accountDatabase: AccountDatabase ) { @@ -46,7 +47,7 @@ class BacklogRequester( "BacklogRequester", "requested(buffer: $buffer, amount: $amount, pageSize: $pageSize, lastMessageId: $lastMessageId, untilAllVisible: $untilAllVisible)") var missing = amount - viewModel.session.value?.orNull()?.let { session: ISession -> + session.value?.orNull()?.let { session: ISession -> session.backlogManager.let { val filtered = database.filtered().get(accountId, buffer, diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt index 13105edf5..9f35585b1 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselNotificationBackend.kt @@ -56,6 +56,7 @@ import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.irc.format.ContentFormatter import de.kuschku.quasseldroid.util.ui.drawable.TextDrawable import de.kuschku.quasseldroid.viewmodel.EditorViewModel +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper.Companion.IGNORED_CHARS import org.threeten.bp.Instant import java.util.concurrent.Executors import java.util.concurrent.TimeUnit @@ -299,7 +300,7 @@ class QuasselNotificationBackend @Inject constructor( fun obtainAvatar(nickName: String, ident: String, realName: String, avatarUrl: String, self: Boolean): Drawable { val senderColorIndex = SenderColorUtil.senderColor(nickName) - val rawInitial = nickName.trimStart(*EditorViewModel.IGNORED_CHARS) + val rawInitial = nickName.trimStart(*IGNORED_CHARS) .firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() val senderColor = when (messageSettings.colorizeNicknames) { 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 05d6bee4e..72f906dc2 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 @@ -98,7 +98,9 @@ import de.kuschku.quasseldroid.util.ui.drawable.DrawerToggleActivityDrawable import de.kuschku.quasseldroid.util.ui.drawable.NickCountDrawable import de.kuschku.quasseldroid.util.ui.view.MaterialContentLoadingProgressBar import de.kuschku.quasseldroid.util.ui.view.WarningBarView +import de.kuschku.quasseldroid.viewmodel.ChatViewModel import de.kuschku.quasseldroid.viewmodel.data.BufferData +import de.kuschku.quasseldroid.viewmodel.helper.ChatViewModelHelper import io.reactivex.BackpressureStrategy import org.threeten.bp.Instant import org.threeten.bp.ZoneId @@ -126,6 +128,12 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc @BindView(R.id.autocomplete_list) lateinit var autoCompleteList: RecyclerView + @Inject + lateinit var modelHelper: ChatViewModelHelper + + @Inject + lateinit var chatViewModel: ChatViewModel + @Inject lateinit var database: QuasselDatabase @@ -171,8 +179,8 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } } intent.hasExtra(KEY_BUFFER_ID) -> { - viewModel.buffer.onNext(BufferId(intent.getIntExtra(KEY_BUFFER_ID, -1))) - viewModel.bufferOpened.onNext(Unit) + chatViewModel.buffer.onNext(BufferId(intent.getIntExtra(KEY_BUFFER_ID, -1))) + chatViewModel.bufferOpened.onNext(Unit) if (intent.hasExtra(KEY_ACCOUNT_ID)) { val accountId = intent.getLongExtra(ChatActivity.KEY_ACCOUNT_ID, -1) if (accountId != this.accountId) { @@ -196,7 +204,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc val networkId = NetworkId(intent.getIntExtra(KEY_NETWORK_ID, -1)) val channel = intent.getStringExtra(KEY_CHANNEL) - viewModel.session.filter(Optional<ISession>::isPresent).firstElement().subscribe { + modelHelper.session.filter(Optional<ISession>::isPresent).firstElement().subscribe { it.orNull()?.also { session -> val info = session.bufferSyncer.find( bufferName = channel, @@ -207,7 +215,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc if (info != null) { ChatActivity.launch(this, bufferId = info.bufferId) } else { - viewModel.allBuffers.map { + modelHelper.allBuffers.map { listOfNotNull(it.find { it.networkId == networkId && it.bufferName == channel && @@ -268,7 +276,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc setSupportActionBar(toolbar) - viewModel.bufferOpened.toLiveData().observe(this, Observer { + chatViewModel.bufferOpened.toLiveData().observe(this, Observer { actionMode?.finish() if (drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.closeDrawer(GravityCompat.START, true) @@ -303,7 +311,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc }) val maxBufferActivity = combineLatest( - viewModel.bufferList, + modelHelper.bufferList, database.filtered().listenRx(accountId).toObservable().map { it.associateBy(Filtered::bufferId, Filtered::filtered) }, @@ -397,7 +405,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } // If we connect to a new network without statusbuffer, the bufferid may be -networkId. // In that case, once we’re connected (and a status buffer exists), we want to switch to it. - combineLatest(viewModel.allBuffers, viewModel.buffer).map { (buffers, current) -> + combineLatest(modelHelper.allBuffers, chatViewModel.buffer).map { (buffers, current) -> if (current.isValidId()) Optional.empty() else Optional.ofNullable(buffers.firstOrNull { it.networkId == NetworkId(-current.id) && it.type.hasFlag(Buffer_Type.StatusBuffer) @@ -409,7 +417,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc }) // User-actionable errors that require immediate action, and should show up as dialog - viewModel.errors.toLiveData(BackpressureStrategy.BUFFER).observe(this, Observer { error -> + modelHelper.errors.toLiveData(BackpressureStrategy.BUFFER).observe(this, Observer { error -> error?.let { when (it) { is Error.HandshakeError -> it.message.let { @@ -666,7 +674,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc }) // Connection errors that should show up as toast - viewModel.connectionErrors.toLiveData().observe(this, Observer { error -> + modelHelper.connectionErrors.toLiveData().observe(this, Observer { error -> error?.let { val cause = it.cause when { @@ -712,19 +720,19 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc }) // After initial connect, open the drawer - viewModel.connectionProgress + modelHelper.connectionProgress .filter { (it, _, _) -> it == ConnectionState.CONNECTED } .firstElement() .toLiveData() .observe(this, Observer { if (connectedAccount != accountId) { if (resources.getBoolean(R.bool.buffer_drawer_exists) && - viewModel.buffer.value == BufferId.MAX_VALUE && + chatViewModel.buffer.value == BufferId.MAX_VALUE && !restoredDrawerState) { drawerLayout.openDrawer(GravityCompat.START) } connectedAccount = accountId - viewModel.session.value?.orNull()?.let { session -> + modelHelper.session.value?.orNull()?.let { session -> if (session.identities.isEmpty()) { UserSetupActivity.launch(this) } @@ -754,7 +762,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc }) connectionStatusDisplay.setOnClickListener { - viewModel.sessionManager.value?.orNull()?.apply { + modelHelper.sessionManager.value?.orNull()?.apply { ifDisconnected { autoReconnect() } @@ -762,7 +770,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } // Show Connection Progress Bar - viewModel.connectionProgress.toLiveData().observe(this, Observer { + modelHelper.connectionProgress.toLiveData().observe(this, Observer { val (state, progress, max) = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0) when (state) { ConnectionState.DISCONNECTED, @@ -805,7 +813,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc }) // Only show nick list when we’re in a channel buffer - viewModel.bufferDataThrottled.toLiveData().observe(this, Observer { + modelHelper.bufferDataThrottled.toLiveData().observe(this, Observer { bufferData = it if (bufferData?.info?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) @@ -881,8 +889,9 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - outState.putInt(KEY_OPEN_BUFFER, viewModel.buffer.value.id) - outState.putInt(KEY_OPEN_BUFFERVIEWCONFIG, viewModel.bufferViewConfigId.value ?: -1) + // TODO: store entire chatviewmodel + outState.putInt(KEY_OPEN_BUFFER, chatViewModel.buffer.value.id) + outState.putInt(KEY_OPEN_BUFFERVIEWCONFIG, chatViewModel.bufferViewConfigId.value ?: -1) outState.putLong(KEY_CONNECTED_ACCOUNT, connectedAccount) outState.putBoolean(KEY_OPEN_DRAWER_START, drawerLayout.isDrawerOpen(GravityCompat.START)) outState.putBoolean(KEY_OPEN_DRAWER_END, drawerLayout.isDrawerOpen(GravityCompat.END)) @@ -890,8 +899,9 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc override fun onRestoreInstanceState(savedInstanceState: Bundle?) { super.onRestoreInstanceState(savedInstanceState) - viewModel.buffer.onNext(BufferId(savedInstanceState?.getInt(KEY_OPEN_BUFFER, -1) ?: -1)) - viewModel.bufferViewConfigId.onNext(savedInstanceState?.getInt(KEY_OPEN_BUFFERVIEWCONFIG, -1) + // TODO: restore entire chatviewmodel + chatViewModel.buffer.onNext(BufferId(savedInstanceState?.getInt(KEY_OPEN_BUFFER, -1) ?: -1)) + chatViewModel.bufferViewConfigId.onNext(savedInstanceState?.getInt(KEY_OPEN_BUFFERVIEWCONFIG, -1) ?: -1) connectedAccount = savedInstanceState?.getLong(KEY_CONNECTED_ACCOUNT, -1L) ?: -1L @@ -942,7 +952,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } R.id.action_filter_messages -> { runInBackground { - viewModel.buffer { buffer -> + chatViewModel.buffer { buffer -> val filteredRaw = database.filtered().get( accountId, buffer, @@ -1070,7 +1080,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc connectedAccount = -1L restoredDrawerState = false ChatActivity.launch(this, bufferId = BufferId.MAX_VALUE) - viewModel.resetAccount() + chatViewModel.resetAccount() } override fun onSelectAccount() { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ToolbarFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ToolbarFragment.kt index 5ba0e602c..55a290928 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ToolbarFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ToolbarFragment.kt @@ -44,6 +44,7 @@ import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.SpanFormatter +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import javax.inject.Inject class ToolbarFragment : ServiceBoundFragment() { @@ -59,6 +60,9 @@ class ToolbarFragment : ServiceBoundFragment() { @BindView(R.id.toolbar_action_area) lateinit var actionArea: View + @Inject + lateinit var modelHelper: EditorViewModelHelper + @Inject lateinit var ircFormatDeserializer: IrcFormatDeserializer @@ -100,7 +104,7 @@ class ToolbarFragment : ServiceBoundFragment() { val colorContext = ColorContext(requireContext(), messageSettings) - combineLatest(viewModel.bufferDataThrottled, viewModel.lag).map { + combineLatest(modelHelper.bufferDataThrottled, modelHelper.lag).map { val avatarInfo = it.first?.ircUser?.let { user -> val avatarUrls = AvatarHelper.avatar(messageSettings, user, avatarSize) @@ -156,7 +160,7 @@ class ToolbarFragment : ServiceBoundFragment() { }) actionArea.setOnClickListener { - val bufferData = viewModel.bufferData.value + val bufferData = modelHelper.bufferData.value bufferData?.info?.let { info -> when (info.type.toInt()) { BufferInfo.Type.QueryBuffer.toInt() -> { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/ChannelCreateFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/ChannelCreateFragment.kt index 3ef7d379c..4de526216 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/ChannelCreateFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/create/ChannelCreateFragment.kt @@ -42,7 +42,9 @@ import de.kuschku.quasseldroid.util.helper.combineLatest import de.kuschku.quasseldroid.util.helper.setDependent import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment +import de.kuschku.quasseldroid.viewmodel.helper.QuasselViewModelHelper import io.reactivex.Observable +import javax.inject.Inject class ChannelCreateFragment : ServiceBoundSettingsFragment() { @BindView(R.id.network) @@ -69,6 +71,9 @@ class ChannelCreateFragment : ServiceBoundSettingsFragment() { @BindView(R.id.save) lateinit var save: Button + @Inject + lateinit var modelHelper: QuasselViewModelHelper + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.add_create, container, false) @@ -77,7 +82,7 @@ class ChannelCreateFragment : ServiceBoundSettingsFragment() { val networkAdapter = NetworkAdapter() network.adapter = networkAdapter - viewModel.networks.switchMap { + modelHelper.networks.switchMap { combineLatest(it.values.map(Network::liveNetworkInfo)).map { it.map { NetworkItem( @@ -106,13 +111,13 @@ class ChannelCreateFragment : ServiceBoundSettingsFragment() { val isPasswordProtected = passwordProtected.isChecked val channelPassword = password.text.toString().trim() - viewModel.bufferSyncer.value?.orNull()?.let { bufferSyncer -> + modelHelper.bufferSyncer.value?.orNull()?.let { bufferSyncer -> val existingBuffer = bufferSyncer.find( networkId = networkId, type = Buffer_Type.of(Buffer_Type.ChannelBuffer), bufferName = channelName ) - val existingChannel = viewModel.networks.value?.get(networkId)?.ircChannel(channelName) + val existingChannel = modelHelper.networks.value?.get(networkId)?.ircChannel(channelName) .nullIf { it == IrcChannel.NULL } if (existingBuffer != null) { if (existingChannel == null) { @@ -120,7 +125,7 @@ class ChannelCreateFragment : ServiceBoundSettingsFragment() { networkId = networkId, type = Buffer_Type.of(Buffer_Type.StatusBuffer) )?.let { statusBuffer -> - viewModel.session.value?.orNull()?.rpcHandler?.apply { + modelHelper.session.value?.orNull()?.rpcHandler?.apply { sendInput(statusBuffer, "/join $channelName") } } @@ -137,9 +142,9 @@ class ChannelCreateFragment : ServiceBoundSettingsFragment() { networkId = networkId, type = Buffer_Type.of(Buffer_Type.StatusBuffer) )?.let { statusBuffer -> - viewModel.session.value?.orNull()?.rpcHandler?.apply { + modelHelper.session.value?.orNull()?.rpcHandler?.apply { sendInput(statusBuffer, "/join $channelName") - viewModel.networks.switchMap { + modelHelper.networks.switchMap { it[networkId]?.liveIrcChannel(channelName) ?: Observable.empty() }.subscribe { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt index 6b917836d..7d48ca42d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt @@ -75,6 +75,7 @@ import de.kuschku.quasseldroid.viewmodel.data.BufferHiddenState import de.kuschku.quasseldroid.viewmodel.data.BufferListItem import de.kuschku.quasseldroid.viewmodel.data.BufferState import de.kuschku.quasseldroid.viewmodel.data.BufferStatus +import de.kuschku.quasseldroid.viewmodel.helper.ChatViewModelHelper import javax.inject.Inject class BufferViewConfigFragment : ServiceBoundFragment() { @@ -117,16 +118,19 @@ class BufferViewConfigFragment : ServiceBoundFragment() { @Inject lateinit var ircFormatDeserializer: IrcFormatDeserializer + @Inject + lateinit var modelHelper: ChatViewModelHelper + private var actionMode: ActionMode? = null private val actionModeCallback = object : ActionMode.Callback { override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { - val selected = viewModel.selectedBuffer.value + val selected = modelHelper.selectedBuffer.value val info = selected?.info - val session = viewModel.session.value?.orNull() + val session = modelHelper.session.value?.orNull() val bufferSyncer = session?.bufferSyncer val network = session?.networks?.get(selected?.info?.networkId) - val bufferViewConfig = viewModel.bufferViewConfig.value + val bufferViewConfig = modelHelper.bufferViewConfig.value return if (info != null && session != null) { when (item?.itemId) { @@ -258,7 +262,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { ButterKnife.bind(this, view) val adapter = BufferViewConfigAdapter() - viewModel.bufferViewConfigs.switchMap { + modelHelper.bufferViewConfigs.switchMap { combineLatest(it.map(BufferViewConfig::liveUpdates)) }.toLiveData().observe(this, Observer { if (it != null) { @@ -269,30 +273,30 @@ class BufferViewConfigFragment : ServiceBoundFragment() { var hasSetBufferViewConfigId = false adapter.setOnUpdateFinishedListener { if (!hasSetBufferViewConfigId) { - chatListSpinner.setSelection(adapter.indexOf(viewModel.bufferViewConfigId.value).nullIf { it == -1 } - ?: 0) - viewModel.bufferViewConfigId.onNext(chatListSpinner.selectedItemId.toInt()) + chatListSpinner.setSelection(adapter.indexOf(modelHelper.chat.bufferViewConfigId.value).nullIf { it == -1 } + ?: 0) + modelHelper.chat.bufferViewConfigId.onNext(chatListSpinner.selectedItemId.toInt()) hasSetBufferViewConfigId = true } } chatListSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onNothingSelected(adapter: AdapterView<*>?) { if (hasSetBufferViewConfigId) - viewModel.bufferViewConfigId.onNext(-1) + modelHelper.chat.bufferViewConfigId.onNext(-1) } override fun onItemSelected(adapter: AdapterView<*>?, element: View?, position: Int, id: Long) { if (hasSetBufferViewConfigId) - viewModel.bufferViewConfigId.onNext(id.toInt()) + modelHelper.chat.bufferViewConfigId.onNext(id.toInt()) } } chatListSpinner.adapter = adapter listAdapter = BufferListAdapter( messageSettings, - viewModel.selectedBufferId, - viewModel.expandedNetworks + modelHelper.chat.selectedBufferId, + modelHelper.chat.expandedNetworks ) val avatarSize = resources.getDimensionPixelSize(R.dimen.avatar_size_buffer) @@ -318,16 +322,17 @@ class BufferViewConfigFragment : ServiceBoundFragment() { } } - viewModel.negotiatedFeatures.toLiveData().observe(this, Observer { (connected, features) -> + modelHelper.negotiatedFeatures.toLiveData().observe(this, Observer { (connected, features) -> featureContextBufferActivitySync.setMode( if (!connected || features.hasFeature(ExtendedFeature.BufferActivitySync)) WarningBarView.MODE_NONE else WarningBarView.MODE_ICON ) }) - combineLatest(viewModel.bufferList, - viewModel.expandedNetworks, - viewModel.selectedBuffer, + combineLatest( + modelHelper.bufferList, + modelHelper.chat.expandedNetworks, + modelHelper.selectedBuffer, database.filtered().listenRx(accountId).toObservable(), accountDatabase.accounts().listenDefaultFiltered(accountId, 0).toObservable() ).toLiveData().observe(this, Observer { it -> @@ -405,7 +410,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { listAdapter.setOnLongClickListener(this@BufferViewConfigFragment::longClickListener) chatList.adapter = listAdapter - viewModel.selectedBuffer.toLiveData().observe(this, Observer { buffer -> + modelHelper.selectedBuffer.toLiveData().observe(this, Observer { buffer -> if (buffer != null) { val menu = actionMode?.menu if (menu != null) { @@ -480,17 +485,17 @@ class BufferViewConfigFragment : ServiceBoundFragment() { }) chatListToolbar.inflateMenu(R.menu.context_bufferlist) - chatListToolbar.menu.findItem(R.id.action_search).isChecked = viewModel.bufferSearchTemporarilyVisible.value + chatListToolbar.menu.findItem(R.id.action_search).isChecked = modelHelper.chat.bufferSearchTemporarilyVisible.value chatListToolbar.setOnMenuItemClickListener { item -> when (item.itemId) { R.id.action_search -> { item.isChecked = !item.isChecked - viewModel.bufferSearchTemporarilyVisible.onNext(item.isChecked) + modelHelper.chat.bufferSearchTemporarilyVisible.onNext(item.isChecked) true } R.id.action_show_hidden -> { item.isChecked = !item.isChecked - viewModel.showHidden.onNext(item.isChecked) + modelHelper.chat.showHidden.onNext(item.isChecked) true } else -> false @@ -502,17 +507,17 @@ class BufferViewConfigFragment : ServiceBoundFragment() { chatList.itemAnimator = DefaultItemAnimator() chatList.setItemViewCacheSize(10) - viewModel.stateReset.toLiveData().observe(this, Observer { + modelHelper.chat.stateReset.toLiveData().observe(this, Observer { listAdapter.submitList(emptyList()) hasSetBufferViewConfigId = false }) - val bufferSearchPermanentlyVisible = viewModel.bufferViewConfig + val bufferSearchPermanentlyVisible = modelHelper.bufferViewConfig .mapMap(BufferViewConfig::showSearch) .mapOrElse(false) - combineLatest(viewModel.bufferSearchTemporarilyVisible.distinctUntilChanged(), - bufferSearchPermanentlyVisible) + combineLatest(modelHelper.chat.bufferSearchTemporarilyVisible.distinctUntilChanged(), + bufferSearchPermanentlyVisible) .toLiveData().observe(this, Observer { (temporarily, permanently) -> val visible = temporarily || permanently @@ -527,7 +532,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { bufferSearch.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable) { - viewModel.bufferSearch.onNext(s.toString()) + modelHelper.chat.bufferSearch.onNext(s.toString()) } override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit @@ -627,7 +632,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { longClickListener(bufferId) } else { context?.let { - viewModel.bufferSearchTemporarilyVisible.onNext(false) + modelHelper.chat.bufferSearchTemporarilyVisible.onNext(false) ChatActivity.launch(it, bufferId = bufferId) } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt index ac177c683..1d139eadf 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt @@ -45,16 +45,17 @@ import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.viewmodel.EditorViewModel -import de.kuschku.quasseldroid.viewmodel.EditorViewModel.Companion.IGNORED_CHARS import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem import de.kuschku.quasseldroid.viewmodel.data.BufferStatus +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper.Companion.IGNORED_CHARS class AutoCompleteHelper( activity: FragmentActivity, private val autoCompleteSettings: AutoCompleteSettings, private val messageSettings: MessageSettings, private val ircFormatDeserializer: IrcFormatDeserializer, - private val viewModel: EditorViewModel + private val helper: EditorViewModelHelper ) { private var autocompleteListener: ((AutoCompletionState) -> Unit)? = null private var dataListeners: List<((List<AutoCompleteItem>) -> Unit)> = emptyList() @@ -85,7 +86,7 @@ class AutoCompleteHelper( private val colorContext = ColorContext(activity, messageSettings) init { - viewModel.autoCompleteData.toLiveData().observe(activity, Observer { + helper.autoCompleteData.toLiveData().observe(activity, Observer { val query = it?.first ?: "" val shouldShowResults = (autoCompleteSettings.auto && query.length >= 3) || @@ -260,10 +261,10 @@ class AutoCompleteHelper( } fun autoComplete(reverse: Boolean = false) { - viewModel.lastWord.switchMap { it }.value?.let { originalWord -> + helper.editor.lastWord.switchMap { it }.value?.let { originalWord -> val previous = autoCompletionState if (!originalWord.second.isEmpty()) { - val autoCompletedWords = viewModel.rawAutoCompleteData.value?.let { (sessionOptional, id, lastWord) -> + val autoCompletedWords = helper.rawAutoCompleteData.value?.let { (sessionOptional, id, lastWord) -> fullAutoComplete(sessionOptional, id, lastWord) }?.filter { it is AutoCompleteItem.AliasItem && autoCompleteSettings.aliases || diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt index e5a2925b9..90052c05e 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt @@ -42,7 +42,9 @@ import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.irc.format.IrcFormatSerializer import de.kuschku.quasseldroid.util.service.ServiceBoundFragment +import de.kuschku.quasseldroid.viewmodel.ChatViewModel import de.kuschku.quasseldroid.viewmodel.EditorViewModel +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import javax.inject.Inject class ChatlineFragment : ServiceBoundFragment() { @@ -91,15 +93,15 @@ class ChatlineFragment : ServiceBoundFragment() { @Inject lateinit var autoCompleteAdapter: AutoCompleteAdapter - @Inject - lateinit var editorViewModel: EditorViewModel - lateinit var editorHelper: EditorHelper lateinit var autoCompleteHelper: AutoCompleteHelper lateinit var historyBottomSheet: BottomSheetBehavior<View> + @Inject + lateinit var modelHelper: EditorViewModelHelper + val panelSlideListener = object : BottomSheetBehavior.BottomSheetCallback() { override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit @@ -113,14 +115,12 @@ class ChatlineFragment : ServiceBoundFragment() { val view = inflater.inflate(R.layout.chat_chatline, container, false) ButterKnife.bind(this, view) - editorViewModel.quasselViewModel.onNext(viewModel) - autoCompleteHelper = AutoCompleteHelper( requireActivity(), autoCompleteSettings, messageSettings, ircFormatDeserializer, - editorViewModel + modelHelper ) editorHelper = EditorHelper( @@ -132,7 +132,7 @@ class ChatlineFragment : ServiceBoundFragment() { appearanceSettings ) - editorViewModel.lastWord.onNext(editorHelper.lastWord) + modelHelper.editor.lastWord.onNext(editorHelper.lastWord) val autoCompleteBottomSheet = BottomSheetBehavior.from(autoCompleteList) if (autoCompleteSettings.prefix || autoCompleteSettings.auto) { @@ -162,7 +162,7 @@ class ChatlineFragment : ServiceBoundFragment() { historyBottomSheet.state = BottomSheetBehavior.STATE_HIDDEN } messageHistory.adapter = messageHistoryAdapter - viewModel.recentlySentMessages.toLiveData() + modelHelper.chat.recentlySentMessages.toLiveData() .observe(this, Observer(messageHistoryAdapter::submitList)) messageHistoryAdapter.setOnUpdateFinishedListener { messageHistory.scrollToPosition(0) @@ -181,11 +181,11 @@ class ChatlineFragment : ServiceBoundFragment() { } for ((stripped, _) in lines) { - viewModel.addRecentlySentMessage(stripped) + modelHelper.chat.addRecentlySentMessage(stripped) } - viewModel.session { sessionOptional -> + modelHelper.session { sessionOptional -> val session = sessionOptional.orNull() - viewModel.buffer { bufferId -> + modelHelper.chat.buffer { bufferId -> session?.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo -> val output = mutableListOf<IAliasManager.Command>() for ((_, formatted) in lines) { @@ -228,7 +228,7 @@ class ChatlineFragment : ServiceBoundFragment() { fun replaceText(text: CharSequence) { if (chatline.safeText.isNotEmpty()) { - chatline.safeText.lineSequence().forEach(viewModel::addRecentlySentMessage) + chatline.safeText.lineSequence().forEach(modelHelper.chat::addRecentlySentMessage) } editorHelper.replaceText(text) } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt index ed05bd91a..3a5a2fedc 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt @@ -72,7 +72,9 @@ import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.LinkLongClickMenuHelper import de.kuschku.quasseldroid.util.ui.SpanFormatter +import de.kuschku.quasseldroid.viewmodel.ChatViewModel import de.kuschku.quasseldroid.viewmodel.data.Avatar +import de.kuschku.quasseldroid.viewmodel.helper.ChatViewModelHelper import org.threeten.bp.ZoneId import org.threeten.bp.ZonedDateTime import org.threeten.bp.temporal.ChronoUnit @@ -110,6 +112,12 @@ class MessageListFragment : ServiceBoundFragment() { @Inject lateinit var adapter: MessageAdapter + @Inject + lateinit var chatViewModel: ChatViewModel + + @Inject + lateinit var modelHelper: ChatViewModelHelper + private lateinit var linearLayoutManager: LinearLayoutManager private lateinit var backlogRequester: BacklogRequester @@ -123,9 +131,9 @@ class MessageListFragment : ServiceBoundFragment() { private val actionModeCallback = object : ActionMode.Callback { override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = when (item?.itemId) { R.id.action_user_info -> { - viewModel.selectedMessages.value.values.firstOrNull()?.let { msg -> - viewModel.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> - viewModel.bufferData.value?.info?.let(BufferInfo::networkId)?.let { networkId -> + modelHelper.chat.selectedMessages.value.values.firstOrNull()?.let { msg -> + modelHelper.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> + modelHelper.bufferData.value?.info?.let(BufferInfo::networkId)?.let { networkId -> UserInfoActivity.launch( requireContext(), openBuffer = false, @@ -145,7 +153,7 @@ class MessageListFragment : ServiceBoundFragment() { } R.id.action_copy -> { val builder = SpannableStringBuilder() - viewModel.selectedMessages.value.values.asSequence().sortedBy { + modelHelper.chat.selectedMessages.value.values.asSequence().sortedBy { it.original.messageId }.map { if (it.name != null && it.content != null) { @@ -174,7 +182,7 @@ class MessageListFragment : ServiceBoundFragment() { } R.id.action_share -> { val builder = SpannableStringBuilder() - viewModel.selectedMessages.value.values.asSequence().sortedBy { + modelHelper.chat.selectedMessages.value.values.asSequence().sortedBy { it.original.messageId }.map { if (it.name != null && it.content != null) { @@ -223,7 +231,7 @@ class MessageListFragment : ServiceBoundFragment() { override fun onDestroyActionMode(mode: ActionMode?) { actionMode = null - viewModel.selectedMessages.onNext(emptyMap()) + modelHelper.chat.selectedMessages.onNext(emptyMap()) } } @@ -244,18 +252,18 @@ class MessageListFragment : ServiceBoundFragment() { linearLayoutManager = LinearLayoutManager(context) linearLayoutManager.reverseLayout = true - backlogRequester = BacklogRequester(viewModel, database, accountDatabase) + backlogRequester = BacklogRequester(modelHelper.session, database, accountDatabase) adapter.setOnClickListener { msg -> if (actionMode != null) { - when (viewModel.selectedMessagesToggle(msg.original.messageId, msg)) { + when (modelHelper.chat.selectedMessagesToggle(msg.original.messageId, msg)) { 0 -> actionMode?.finish() 1 -> actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = true else -> actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = false } } else if (msg.hasSpoilers) { - val value = viewModel.expandedMessages.value - viewModel.expandedMessages.onNext( + val value = modelHelper.chat.expandedMessages.value + modelHelper.chat.expandedMessages.onNext( if (value.contains(msg.original.messageId)) value - msg.original.messageId else value + msg.original.messageId ) @@ -265,7 +273,7 @@ class MessageListFragment : ServiceBoundFragment() { if (actionMode == null) { activity?.startActionMode(actionModeCallback) } - when (viewModel.selectedMessagesToggle(msg.original.messageId, msg)) { + when (modelHelper.chat.selectedMessagesToggle(msg.original.messageId, msg)) { 0 -> actionMode?.finish() 1 -> actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = true else -> actionMode?.menu?.findItem(R.id.action_user_info)?.isVisible = false @@ -280,8 +288,8 @@ class MessageListFragment : ServiceBoundFragment() { ) } adapter.setOnSenderIconClickListener { msg -> - viewModel.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> - viewModel.bufferData.value?.info?.let(BufferInfo::networkId)?.let { networkId -> + modelHelper.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> + modelHelper.bufferData.value?.info?.let(BufferInfo::networkId)?.let { networkId -> UserInfoActivity.launch( requireContext(), openBuffer = false, @@ -361,10 +369,10 @@ class MessageListFragment : ServiceBoundFragment() { } } - val data = combineLatest(viewModel.buffer, - viewModel.selectedMessages, - viewModel.expandedMessages, - viewModel.markerLine) + val data = combineLatest(modelHelper.chat.buffer, + modelHelper.chat.selectedMessages, + modelHelper.chat.expandedMessages, + modelHelper.markerLine) .toLiveData().switchMapNotNull { (buffer, selected, expanded, markerLine) -> accountDatabase.accounts().listen(accountId).switchMap { database.filtered().listen(accountId, @@ -387,19 +395,19 @@ class MessageListFragment : ServiceBoundFragment() { } } - val lastMessageId = viewModel.buffer.toLiveData().switchMapNotNull { + val lastMessageId = modelHelper.chat.buffer.toLiveData().switchMapNotNull { database.message().lastMsgId(it) } - viewModel.buffer.toLiveData().observe(this, Observer { bufferId -> + modelHelper.chat.buffer.toLiveData().observe(this, Observer { bufferId -> swipeRefreshLayout.isEnabled = (bufferId != null || bufferId?.isValidId() == true) }) - viewModel.sessionManager.mapSwitchMap(SessionManager::state).distinctUntilChanged().toLiveData().observe( + modelHelper.sessionManager.mapSwitchMap(SessionManager::state).distinctUntilChanged().toLiveData().observe( this, Observer { if (it?.orNull() == ConnectionState.CONNECTED) { runInBackgroundDelayed(16) { - viewModel.buffer { bufferId -> + modelHelper.chat.buffer { bufferId -> val filtered = database.filtered().get(accountId, bufferId, accountDatabase.accounts().findById(accountId)?.defaultFiltered @@ -416,7 +424,7 @@ class MessageListFragment : ServiceBoundFragment() { } }) - viewModel.session.toLiveData().zip(lastMessageId).observe( + modelHelper.session.toLiveData().zip(lastMessageId).observe( this, Observer { runInBackground { val session = it?.first?.orNull() @@ -496,11 +504,11 @@ class MessageListFragment : ServiceBoundFragment() { list?.let(adapter::submitList) } - val buffer = viewModel.buffer.value + val buffer = modelHelper.chat.buffer.value ?: BufferId(-1) if (buffer != lastBuffer) { adapter.clearCache() - viewModel.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> + modelHelper.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> onBufferChange(lastBuffer, buffer, firstVisibleMessageId, bufferSyncer) } lastBuffer = buffer @@ -549,7 +557,7 @@ class MessageListFragment : ServiceBoundFragment() { val previous = lastBuffer val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition() val messageId = adapter[firstVisibleItemPosition]?.content?.messageId - val bufferSyncer = viewModel.session.value?.orNull()?.bufferSyncer + val bufferSyncer = modelHelper.session.value?.orNull()?.bufferSyncer if (previous != null && messageId != null) { bufferSyncer?.requestSetMarkerLine(previous, messageId) } @@ -559,7 +567,7 @@ class MessageListFragment : ServiceBoundFragment() { private fun loadMore(initial: Boolean = false, lastMessageId: MsgId? = null) { // This can be called *after* we’re already detached from the activity activity?.runOnUiThread { - viewModel.buffer { bufferId -> + modelHelper.chat.buffer { bufferId -> if (bufferId.isValidId() && bufferId != BufferId.MAX_VALUE) { if (initial) swipeRefreshLayout.isRefreshing = true runInBackground { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt index 03b4767ea..81f57b07d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt @@ -48,6 +48,7 @@ import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.ui.SpanFormatter import de.kuschku.quasseldroid.viewmodel.EditorViewModel import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper.Companion.IGNORED_CHARS import org.threeten.bp.ZoneId import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.FormatStyle @@ -263,7 +264,7 @@ class QuasselMessageRenderer @Inject constructor( message.content.networkId) val nickName = HostmaskHelper.nick(message.content.sender) val senderColorIndex = SenderColorUtil.senderColor(nickName) - val rawInitial = nickName.trimStart(*EditorViewModel.IGNORED_CHARS) + val rawInitial = nickName.trimStart(*IGNORED_CHARS) .firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() val useSelfColor = when (messageSettings.colorizeNicknames) { @@ -298,7 +299,7 @@ class QuasselMessageRenderer @Inject constructor( Message_Type.Action -> { val nickName = HostmaskHelper.nick(message.content.sender) val senderColorIndex = SenderColorUtil.senderColor(nickName) - val rawInitial = nickName.trimStart(*EditorViewModel.IGNORED_CHARS) + val rawInitial = nickName.trimStart(*IGNORED_CHARS) .firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() val useSelfColor = when (messageSettings.colorizeNicknames) { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt index 082863605..997f840ab 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt @@ -54,8 +54,9 @@ import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.service.ServiceBoundFragment -import de.kuschku.quasseldroid.viewmodel.EditorViewModel.Companion.IGNORED_CHARS import de.kuschku.quasseldroid.viewmodel.data.Avatar +import de.kuschku.quasseldroid.viewmodel.helper.ChatViewModelHelper +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper.Companion.IGNORED_CHARS import javax.inject.Inject class NickListFragment : ServiceBoundFragment() { @@ -71,6 +72,9 @@ class NickListFragment : ServiceBoundFragment() { @Inject lateinit var ircFormatDeserializer: IrcFormatDeserializer + @Inject + lateinit var modelHelper: ChatViewModelHelper + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.chat_nicklist, container, false) @@ -101,7 +105,7 @@ class NickListFragment : ServiceBoundFragment() { val colorContext = ColorContext(requireContext(), messageSettings) val avatarSize = resources.getDimensionPixelSize(R.dimen.avatar_size) - viewModel.nickDataThrottled.toLiveData().observe(this, Observer { + modelHelper.nickDataThrottled.toLiveData().observe(this, Observer { runInBackground { it?.asSequence()?.map { val nickName = it.nick @@ -188,8 +192,8 @@ class NickListFragment : ServiceBoundFragment() { } private val clickListener: ((String) -> Unit)? = { nick -> - viewModel.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> - viewModel.bufferData.value?.info?.let(BufferInfo::networkId)?.let { networkId -> + modelHelper.session.value?.orNull()?.bufferSyncer?.let { bufferSyncer -> + modelHelper.bufferData.value?.info?.let(BufferInfo::networkId)?.let { networkId -> UserInfoActivity.launch( requireContext(), openBuffer = false, diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/topic/TopicActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicActivity.kt similarity index 92% rename from app/src/main/java/de/kuschku/quasseldroid/ui/info/topic/TopicActivity.kt rename to app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicActivity.kt index 468a6de9d..cc1a45d3f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/topic/TopicActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicActivity.kt @@ -17,11 +17,10 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -package de.kuschku.quasseldroid.ui.info.topic +package de.kuschku.quasseldroid.ui.chat.topic import android.content.Context import android.content.Intent -import de.kuschku.quasseldroid.ui.chat.topic.TopicFragment import de.kuschku.quasseldroid.util.ui.settings.ServiceBoundSettingsActivity class TopicActivity : ServiceBoundSettingsActivity(TopicFragment()) { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragment.kt index e6b056c1d..6180fea5a 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragment.kt @@ -43,6 +43,7 @@ import de.kuschku.quasseldroid.util.irc.format.IrcFormatSerializer import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment import de.kuschku.quasseldroid.viewmodel.EditorViewModel +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import javax.inject.Inject class TopicFragment : ServiceBoundSettingsFragment(), Savable { @@ -74,7 +75,7 @@ class TopicFragment : ServiceBoundSettingsFragment(), Savable { lateinit var autoCompleteAdapter: AutoCompleteAdapter @Inject - lateinit var editorViewModel: EditorViewModel + lateinit var modelHelper: EditorViewModelHelper private lateinit var editorHelper: EditorHelper @@ -83,14 +84,12 @@ class TopicFragment : ServiceBoundSettingsFragment(), Savable { val view = inflater.inflate(R.layout.info_topic, container, false) ButterKnife.bind(this, view) - editorViewModel.quasselViewModel.onNext(viewModel) - val autoCompleteHelper = AutoCompleteHelper( requireActivity(), autoCompleteSettings, messageSettings, formatDeserializer, - editorViewModel + modelHelper ) editorHelper = EditorHelper( @@ -102,7 +101,7 @@ class TopicFragment : ServiceBoundSettingsFragment(), Savable { appearanceSettings ) - editorViewModel.lastWord.onNext(editorHelper.lastWord) + modelHelper.editor.lastWord.onNext(editorHelper.lastWord) if (autoCompleteSettings.prefix || autoCompleteSettings.auto) { val autoCompleteBottomSheet = BottomSheetBehavior.from(autoCompleteList) @@ -119,8 +118,8 @@ class TopicFragment : ServiceBoundSettingsFragment(), Savable { } val bufferId = BufferId(arguments?.getInt("buffer", -1) ?: -1) - viewModel.buffer.onNext(bufferId) - viewModel.bufferData.filter { + modelHelper.chat.buffer.onNext(bufferId) + modelHelper.bufferData.filter { it.info != null }.firstElement().toLiveData().observe(this, Observer { chatline.setText(formatDeserializer.formatString(it?.description, true)) @@ -130,9 +129,9 @@ class TopicFragment : ServiceBoundSettingsFragment(), Savable { } override fun onSave(): Boolean { - viewModel.session { sessionOptional -> + modelHelper.session { sessionOptional -> val session = sessionOptional.orNull() - viewModel.buffer { bufferId -> + modelHelper.chat.buffer { bufferId -> session?.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo -> val topic = formatSerializer.toEscapeCodes(chatline.safeText) session.rpcHandler.sendInput(bufferInfo, "/topic $topic") diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragmentProvider.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragmentProvider.kt index 0ec82a4a4..c4d418209 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragmentProvider.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/topic/TopicFragmentProvider.kt @@ -23,7 +23,6 @@ import androidx.fragment.app.FragmentActivity import dagger.Binds import dagger.Module import dagger.android.ContributesAndroidInjector -import de.kuschku.quasseldroid.ui.info.topic.TopicActivity @Module abstract class TopicFragmentProvider { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/CoreSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/CoreSettingsFragment.kt index d1c2c46ab..b426457df 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/CoreSettingsFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/CoreSettingsFragment.kt @@ -57,7 +57,9 @@ import de.kuschku.quasseldroid.util.missingfeatures.MissingFeaturesDialog import de.kuschku.quasseldroid.util.missingfeatures.RequiredFeatures import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.view.BannerView +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import io.reactivex.Observable +import javax.inject.Inject class CoreSettingsFragment : ServiceBoundFragment() { @BindView(R.id.feature_context_missing) @@ -99,6 +101,9 @@ class CoreSettingsFragment : ServiceBoundFragment() { @BindView(R.id.networkconfig) lateinit var networkconfig: View + @Inject + lateinit var modelHelper: EditorViewModelHelper + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.settings_list, container, false) @@ -131,7 +136,7 @@ class CoreSettingsFragment : ServiceBoundFragment() { networks.addItemDecoration(itemDecoration) ViewCompat.setNestedScrollingEnabled(networks, false) - viewModel.networks.switchMap { + modelHelper.networks.switchMap { if (it.isEmpty()) { Observable.just(emptyList()) } else { @@ -150,7 +155,7 @@ class CoreSettingsFragment : ServiceBoundFragment() { identities.addItemDecoration(itemDecoration) ViewCompat.setNestedScrollingEnabled(identities, false) - viewModel.identities.switchMap { + modelHelper.identities.switchMap { if (it.isEmpty()) { Observable.just(emptyList()) } else { @@ -169,7 +174,7 @@ class CoreSettingsFragment : ServiceBoundFragment() { chatlists.addItemDecoration(itemDecoration) ViewCompat.setNestedScrollingEnabled(chatlists, false) - viewModel.bufferViewConfigMap.switchMap { + modelHelper.bufferViewConfigMap.switchMap { combineLatest(it.values.map(BufferViewConfig::liveUpdates)).map { it.map { SettingsItem(it.bufferViewId(), it.bufferViewName()) @@ -180,7 +185,7 @@ class CoreSettingsFragment : ServiceBoundFragment() { }) var missingFeatureList: List<MissingFeature> = emptyList() - viewModel.negotiatedFeatures.toLiveData().observe(this, Observer { (connected, features) -> + modelHelper.negotiatedFeatures.toLiveData().observe(this, Observer { (connected, features) -> missingFeatureList = RequiredFeatures.features.filter { it.feature !in features.enabledFeatures } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliasitem/AliasItemFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliasitem/AliasItemFragment.kt index 1dfa2e2f9..bb55abfda 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliasitem/AliasItemFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliasitem/AliasItemFragment.kt @@ -44,6 +44,7 @@ import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment import de.kuschku.quasseldroid.viewmodel.EditorViewModel +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import javax.inject.Inject class AliasItemFragment : ServiceBoundSettingsFragment(), Savable, Changeable { @@ -78,7 +79,7 @@ class AliasItemFragment : ServiceBoundSettingsFragment(), Savable, Changeable { lateinit var autoCompleteAdapter: AutoCompleteAdapter @Inject - lateinit var editorViewModel: EditorViewModel + lateinit var modelHelper: EditorViewModelHelper private lateinit var editorHelper: EditorHelper @@ -94,14 +95,12 @@ class AliasItemFragment : ServiceBoundSettingsFragment(), Savable, Changeable { rule = it } - editorViewModel.quasselViewModel.onNext(viewModel) - val autoCompleteHelper = AutoCompleteHelper( requireActivity(), autoCompleteSettings, messageSettings, formatDeserializer, - editorViewModel + modelHelper ) editorHelper = EditorHelper( @@ -113,7 +112,7 @@ class AliasItemFragment : ServiceBoundSettingsFragment(), Savable, Changeable { appearanceSettings ) - editorViewModel.lastWord.onNext(editorHelper.lastWord) + modelHelper.editor.lastWord.onNext(editorHelper.lastWord) if (autoCompleteSettings.prefix || autoCompleteSettings.auto) { val autoCompleteBottomSheet = BottomSheetBehavior.from(autoCompleteList) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliaslist/AliasListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliaslist/AliasListFragment.kt index 433e9f017..1ea45ff8d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliaslist/AliasListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/aliaslist/AliasListFragment.kt @@ -39,6 +39,8 @@ import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import de.kuschku.quasseldroid.viewmodel.helper.QuasselViewModelHelper import javax.inject.Inject class AliasListFragment : ServiceBoundSettingsFragment(), Savable, Changeable { @@ -51,6 +53,9 @@ class AliasListFragment : ServiceBoundSettingsFragment(), Savable, Changeable { @Inject lateinit var adapter: AliasListAdapter + @Inject + lateinit var modelHelper: QuasselViewModelHelper + private var aliasManager: Pair<AliasManager, AliasManager>? = null private lateinit var helper: ItemTouchHelper @@ -76,7 +81,7 @@ class AliasListFragment : ServiceBoundSettingsFragment(), Savable, Changeable { startActivityForResult(AliasItemActivity.intent(requireContext()), REQUEST_CREATE_ITEM) } - viewModel.aliasManager + modelHelper.aliasManager .filter(Optional<AliasManager>::isPresent) .map(Optional<AliasManager>::get) .toLiveData().observe(this, Observer { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListBaseFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListBaseFragment.kt index d624d7078..466153b14 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListBaseFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListBaseFragment.kt @@ -48,6 +48,8 @@ import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject abstract class ChatListBaseFragment(private val initDefault: Boolean) : ServiceBoundSettingsFragment(), Savable, Changeable { @@ -84,6 +86,9 @@ abstract class ChatListBaseFragment(private val initDefault: Boolean) : @BindView(R.id.hide_inactive_networks) lateinit var hideInactiveNetworks: SwitchCompat + @Inject + lateinit var modelHelper: EditorViewModelHelper + protected var chatlist: Pair<BufferViewConfig?, BufferViewConfig>? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @@ -116,7 +121,7 @@ abstract class ChatListBaseFragment(private val initDefault: Boolean) : val networkAdapter = NetworkAdapter(R.string.settings_chatlist_network_all) networkId.adapter = networkAdapter - viewModel.networks.switchMap { + modelHelper.networks.switchMap { combineLatest(it.values.map(Network::liveNetworkInfo)).map { it.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, INetwork.NetworkInfo::networkName)) } @@ -133,7 +138,7 @@ abstract class ChatListBaseFragment(private val initDefault: Boolean) : }) if (initDefault) { - viewModel.session + modelHelper.session .filter(Optional<ISession>::isPresent) .map(Optional<ISession>::get) .firstElement() @@ -145,7 +150,7 @@ abstract class ChatListBaseFragment(private val initDefault: Boolean) : } }) } else { - viewModel.bufferViewConfigMap.map { Optional.ofNullable(it[chatlistId]) } + modelHelper.bufferViewConfigMap.map { Optional.ofNullable(it[chatlistId]) } .filter(Optional<BufferViewConfig>::isPresent) .map(Optional<BufferViewConfig>::get) .firstElement() diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListCreateFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListCreateFragment.kt index 42204dd07..57a87610c 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListCreateFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListCreateFragment.kt @@ -24,7 +24,7 @@ import de.kuschku.libquassel.util.helpers.value class ChatListCreateFragment : ChatListBaseFragment(true) { override fun onSave() = chatlist?.let { (_, data) -> applyChanges(data, null) - viewModel.bufferViewManager.value?.orNull()?.requestCreateBufferView(data.toVariantMap()) + modelHelper.bufferViewManager.value?.orNull()?.requestCreateBufferView(data.toVariantMap()) true } ?: false } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListEditFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListEditFragment.kt index 2c7cc4067..b94bc5eac 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListEditFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/chatlist/ChatListEditFragment.kt @@ -32,7 +32,7 @@ class ChatListEditFragment : ChatListBaseFragment(false), Deletable { override fun onDelete() { chatlist?.let { (it, _) -> it?.let { - viewModel.bufferViewManager.value?.orNull()?.requestDeleteBufferView(it.bufferViewId()) + modelHelper.bufferViewManager.value?.orNull()?.requestDeleteBufferView(it.bufferViewId()) } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/highlightlist/HighlightListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/highlightlist/HighlightListFragment.kt index eb777d056..a07992970 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/highlightlist/HighlightListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/highlightlist/HighlightListFragment.kt @@ -46,6 +46,8 @@ import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment import de.kuschku.quasseldroid.util.ui.view.WarningBarView +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject class HighlightListFragment : ServiceBoundSettingsFragment(), Savable, Changeable { @BindView(R.id.feature_context_coresidehighlights) @@ -69,6 +71,9 @@ class HighlightListFragment : ServiceBoundSettingsFragment(), Savable, Changeabl @BindView(R.id.new_highlight_ignore_rule) lateinit var newHighlightIgnoreRule: Button + @Inject + lateinit var modelHelper: EditorViewModelHelper + private var ruleManager: Pair<HighlightRuleManager, HighlightRuleManager>? = null private lateinit var rulesHelper: ItemTouchHelper @@ -128,7 +133,7 @@ class HighlightListFragment : ServiceBoundSettingsFragment(), Savable, Changeabl )) highlightNickType.adapter = highlightNickTypeAdapter - viewModel.highlightRuleManager + modelHelper.highlightRuleManager .filter(Optional<HighlightRuleManager>::isPresent) .map(Optional<HighlightRuleManager>::get) .toLiveData().observe(this, Observer { @@ -146,7 +151,7 @@ class HighlightListFragment : ServiceBoundSettingsFragment(), Savable, Changeabl } }) - viewModel.negotiatedFeatures.toLiveData().observe(this, Observer { (connected, features) -> + modelHelper.negotiatedFeatures.toLiveData().observe(this, Observer { (connected, features) -> featureContextCoreSideHighlights.setMode( if (!connected || features.hasFeature(ExtendedFeature.CoreSideHighlights)) WarningBarView.MODE_NONE else WarningBarView.MODE_ICON diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityBaseFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityBaseFragment.kt index 1b2154fe2..b0bd1f629 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityBaseFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityBaseFragment.kt @@ -45,6 +45,8 @@ import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject abstract class IdentityBaseFragment(private val initDefault: Boolean) : ServiceBoundSettingsFragment(), Savable, Changeable { @@ -85,6 +87,9 @@ abstract class IdentityBaseFragment(private val initDefault: Boolean) : @BindView(R.id.detach_away_reason) lateinit var detachAwayReason: EditText + @Inject + lateinit var modelHelper: EditorViewModelHelper + protected var identity: Pair<Identity?, Identity>? = null private lateinit var adapter: IdentityNicksAdapter @@ -122,7 +127,7 @@ abstract class IdentityBaseFragment(private val initDefault: Boolean) : } if (initDefault) { - viewModel.session + modelHelper.session .filter(Optional<ISession>::isPresent) .map(Optional<ISession>::get) .firstElement() @@ -132,7 +137,7 @@ abstract class IdentityBaseFragment(private val initDefault: Boolean) : } }) } else { - viewModel.identities.map { Optional.ofNullable(it[identityId]) } + modelHelper.identities.map { Optional.ofNullable(it[identityId]) } .filter(Optional<Identity>::isPresent) .map(Optional<Identity>::get) .firstElement() diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityCreateFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityCreateFragment.kt index d3797640e..55679aae3 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityCreateFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityCreateFragment.kt @@ -22,7 +22,7 @@ package de.kuschku.quasseldroid.ui.coresettings.identity import de.kuschku.libquassel.util.helpers.value class IdentityCreateFragment : IdentityBaseFragment(true) { - override fun onSave() = viewModel.session.value?.orNull()?.let { session -> + override fun onSave() = modelHelper.session.value?.orNull()?.let { session -> identity?.let { (_, data) -> applyChanges(data) session.rpcHandler.createIdentity(data, mapOf()) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityEditFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityEditFragment.kt index 88d913fc8..4523eb3b4 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityEditFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/identity/IdentityEditFragment.kt @@ -32,7 +32,7 @@ class IdentityEditFragment : IdentityBaseFragment(false), Deletable { override fun onDelete() { identity?.let { (it, _) -> it?.let { - viewModel.session.value?.orNull()?.rpcHandler?.removeIdentity(it.id()) + modelHelper.session.value?.orNull()?.rpcHandler?.removeIdentity(it.id()) } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/ignorelist/IgnoreListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/ignorelist/IgnoreListFragment.kt index 5be49568d..8799beb7e 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/ignorelist/IgnoreListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/ignorelist/IgnoreListFragment.kt @@ -42,6 +42,8 @@ import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject class IgnoreListFragment : ServiceBoundSettingsFragment(), Savable, Changeable { @@ -51,6 +53,9 @@ class IgnoreListFragment : ServiceBoundSettingsFragment(), Savable, @BindView(R.id.add) lateinit var add: FloatingActionButton + @Inject + lateinit var modelHelper: EditorViewModelHelper + private var ignoreListManager: Pair<IgnoreListManager, IgnoreListManager>? = null private lateinit var helper: ItemTouchHelper @@ -82,7 +87,7 @@ class IgnoreListFragment : ServiceBoundSettingsFragment(), Savable, REQUEST_CREATE_RULE) } - viewModel.ignoreListManager + modelHelper.ignoreListManager .filter(Optional<IgnoreListManager>::isPresent) .map(Optional<IgnoreListManager>::get) .toLiveData().observe(this, Observer { 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 87b20b9d0..3bbf7e6a6 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 @@ -56,6 +56,8 @@ import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment import de.kuschku.quasseldroid.util.ui.view.InlineSnackBar +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject import kotlin.math.roundToInt abstract class NetworkBaseFragment(private val initDefault: Boolean) : @@ -135,6 +137,9 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) : @BindView(R.id.customratelimits_delay) lateinit var customratelimitsDelay: EditText + @Inject + lateinit var modelHelper: EditorViewModelHelper + protected var network: Pair<Network?, Network>? = null private lateinit var adapter: NetworkServerAdapter @@ -166,7 +171,7 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) : val identityAdapter = IdentityAdapter() identity.adapter = identityAdapter - viewModel.identities.switchMap { + modelHelper.identities.switchMap { combineLatest(it.values.map(Identity::liveUpdates)).map { it.sortedBy(Identity::identityName) } @@ -183,7 +188,7 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) : }) if (initDefault) { - viewModel.session + modelHelper.session .filter(Optional<ISession>::isPresent) .map(Optional<ISession>::get) .firstElement() @@ -193,7 +198,7 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) : } }) } else { - viewModel.networks.map { Optional.ofNullable(it[networkId]) } + modelHelper.networks.map { Optional.ofNullable(it[networkId]) } .filter(Optional<Network>::isPresent) .map(Optional<Network>::get) .firstElement() @@ -203,7 +208,7 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) : update(it, identityAdapter) } }) - viewModel.networks.map { Optional.ofNullable(it[networkId]) } + modelHelper.networks.map { Optional.ofNullable(it[networkId]) } .filter(Optional<Network>::isPresent) .map(Optional<Network>::get) .switchMap(Network::liveCaps) @@ -215,7 +220,7 @@ abstract class NetworkBaseFragment(private val initDefault: Boolean) : autoidentifyWarning.setOnClickListener { - val identity = viewModel.identities.value?.get(IdentityId(identity.selectedItemId.toInt())) + val identity = modelHelper.identities.value?.get(IdentityId(identity.selectedItemId.toInt())) if (identity != null) { saslEnabled.isChecked = true saslAccount.setText(identity.nicks().firstOrNull()) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkCreateFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkCreateFragment.kt index 7a5cde498..30a627384 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkCreateFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkCreateFragment.kt @@ -23,10 +23,10 @@ import de.kuschku.libquassel.session.Backend import de.kuschku.libquassel.util.helpers.value class NetworkCreateFragment : NetworkBaseFragment(true) { - override fun onSave() = viewModel.session.value?.orNull()?.let { session -> + override fun onSave() = modelHelper.session.value?.orNull()?.let { session -> network?.let { (_, data) -> applyChanges(data) - viewModel.backend.value?.ifPresent(Backend::requestConnectNewNetwork) + modelHelper.backend.value?.ifPresent(Backend::requestConnectNewNetwork) session.rpcHandler.createNetwork(data.networkInfo(), emptyList()) true } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkEditFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkEditFragment.kt index 6924fe05c..1c76a2a7d 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkEditFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/network/NetworkEditFragment.kt @@ -32,7 +32,7 @@ class NetworkEditFragment : NetworkBaseFragment(false), Deletable { override fun onDelete() { network?.let { (it, _) -> it?.let { - viewModel.session.value?.orNull()?.rpcHandler?.removeNetwork(it.networkId()) + modelHelper.session.value?.orNull()?.rpcHandler?.removeNetwork(it.networkId()) } } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/networkconfig/NetworkConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/networkconfig/NetworkConfigFragment.kt index 7b1d1d5db..f1aa0e864 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/networkconfig/NetworkConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/networkconfig/NetworkConfigFragment.kt @@ -36,6 +36,8 @@ import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.ui.settings.fragment.Changeable import de.kuschku.quasseldroid.util.ui.settings.fragment.Savable import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject class NetworkConfigFragment : ServiceBoundSettingsFragment(), Savable, Changeable { @@ -69,6 +71,9 @@ class NetworkConfigFragment : ServiceBoundSettingsFragment(), Savable, @BindView(R.id.standard_ctcp) lateinit var standardCtcp: SwitchCompat + @Inject + lateinit var modelHelper: EditorViewModelHelper + private var networkConfig: Pair<NetworkConfig, NetworkConfig>? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @@ -76,7 +81,7 @@ class NetworkConfigFragment : ServiceBoundSettingsFragment(), Savable, val view = inflater.inflate(R.layout.settings_networkconfig, container, false) ButterKnife.bind(this, view) - viewModel.networkConfig + modelHelper.networkConfig .filter(Optional<NetworkConfig>::isPresent) .map(Optional<NetworkConfig>::get) .firstElement() diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/passwordchange/PasswordChangeFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/passwordchange/PasswordChangeFragment.kt index 63c62f011..c77211353 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/passwordchange/PasswordChangeFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/passwordchange/PasswordChangeFragment.kt @@ -43,6 +43,7 @@ import de.kuschku.quasseldroid.persistence.models.Account import de.kuschku.quasseldroid.util.TextValidator import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.service.ServiceBoundFragment +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import me.zhanghai.android.materialprogressbar.MaterialProgressBar import javax.inject.Inject @@ -77,6 +78,9 @@ class PasswordChangeFragment : ServiceBoundFragment() { @Inject lateinit var accountDatabase: AccountDatabase + @Inject + lateinit var modelHelper: EditorViewModelHelper + private var waiting: Account? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @@ -88,7 +92,7 @@ class PasswordChangeFragment : ServiceBoundFragment() { user.setText(account?.user) - viewModel.session + modelHelper.session .mapMapNullable(ISession::rpcHandler) .mapSwitchMap(RpcHandler::passwordChanged) .filter(Optional<Boolean>::isPresent) @@ -140,10 +144,12 @@ class PasswordChangeFragment : ServiceBoundFragment() { waiting = account?.copy(pass = pass) - viewModel.session.value?.orNull()?.rpcHandler?.changePassword(0L, - user.text.toString(), - oldPassword.text.toString(), - pass) + modelHelper.session.value?.orNull()?.rpcHandler?.changePassword( + 0L, + user.text.toString(), + oldPassword.text.toString(), + pass + ) } return view diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/certificate/CertificateInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/certificate/CertificateInfoFragment.kt index 02179e48b..00cc2ec83 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/certificate/CertificateInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/certificate/CertificateInfoFragment.kt @@ -37,10 +37,12 @@ import de.kuschku.quasseldroid.util.helper.sha256Fingerprint import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.helper.visibleIf import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import org.threeten.bp.Instant import org.threeten.bp.ZoneId import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.FormatStyle +import javax.inject.Inject class CertificateInfoFragment : ServiceBoundSettingsFragment() { @@ -95,6 +97,8 @@ class CertificateInfoFragment : ServiceBoundSettingsFragment() { @BindView(R.id.fingerprint_sha1) lateinit var fingerprintSha1: TextView + @Inject + lateinit var modelHelper: EditorViewModelHelper override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -103,7 +107,7 @@ class CertificateInfoFragment : ServiceBoundSettingsFragment() { val dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) - viewModel.peerCertificateChain.toLiveData().observe(this, Observer { + modelHelper.peerCertificateChain.toLiveData().observe(this, Observer { val leafCertificate = it.firstOrNull() if (leafCertificate != null) { content.visibility = View.VISIBLE diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/channel/ChannelInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/channel/ChannelInfoFragment.kt index 9126ee12c..49b933ff3 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/channel/ChannelInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/channel/ChannelInfoFragment.kt @@ -37,13 +37,14 @@ import de.kuschku.libquassel.quassel.syncables.IrcChannel import de.kuschku.libquassel.util.helpers.value import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.MessageSettings -import de.kuschku.quasseldroid.ui.info.topic.TopicActivity +import de.kuschku.quasseldroid.ui.chat.topic.TopicActivity import de.kuschku.quasseldroid.util.ShortcutCreationHelper import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.irc.format.ContentFormatter import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.BetterLinkMovementMethod import de.kuschku.quasseldroid.util.ui.LinkLongClickMenuHelper +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import javax.inject.Inject class ChannelInfoFragment : ServiceBoundFragment() { @@ -75,6 +76,9 @@ class ChannelInfoFragment : ServiceBoundFragment() { @Inject lateinit var messageSettings: MessageSettings + @Inject + lateinit var modelHelper: EditorViewModelHelper + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.info_channel, container, false) @@ -86,7 +90,7 @@ class ChannelInfoFragment : ServiceBoundFragment() { var currentBufferInfo: BufferInfo? - combineLatest(viewModel.session, viewModel.networks).map { (sessionOptional, networks) -> + combineLatest(modelHelper.session, modelHelper.networks).map { (sessionOptional, networks) -> if (openBuffer == true) { val session = sessionOptional?.orNull() val bufferSyncer = session?.bufferSyncer @@ -123,7 +127,7 @@ class ChannelInfoFragment : ServiceBoundFragment() { } actionPart.setOnClickListener { - viewModel.session.value?.orNull()?.let { session -> + modelHelper.session.value?.orNull()?.let { session -> session.bufferSyncer.find( networkId = channel.network().networkId(), type = Buffer_Type.of(Buffer_Type.StatusBuffer) @@ -135,7 +139,7 @@ class ChannelInfoFragment : ServiceBoundFragment() { } actionWho.setOnClickListener { - viewModel.session.value?.orNull()?.let { session -> + modelHelper.session.value?.orNull()?.let { session -> session.bufferSyncer.find( networkId = channel.network().networkId(), type = Buffer_Type.of(Buffer_Type.StatusBuffer) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/channellist/ChannelListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/channellist/ChannelListFragment.kt index 7b37af701..e5fbcc0c3 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/channellist/ChannelListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/channellist/ChannelListFragment.kt @@ -43,6 +43,7 @@ import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.ui.settings.fragment.ServiceBoundSettingsFragment import de.kuschku.quasseldroid.util.ui.view.MaterialContentLoadingProgressBar import de.kuschku.quasseldroid.util.ui.view.WarningBarView +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import io.reactivex.subjects.BehaviorSubject import javax.inject.Inject @@ -65,6 +66,9 @@ class ChannelListFragment : ServiceBoundSettingsFragment() { @Inject lateinit var adapter: ChannelListAdapter + @Inject + lateinit var modelHelper: EditorViewModelHelper + private var query: Query? = null private var state: State = State() @@ -146,7 +150,7 @@ class ChannelListFragment : ServiceBoundSettingsFragment() { }) }) - viewModel.ircListHelper + modelHelper.ircListHelper .mapSwitchMap(IrcListHelper::observable) .filter(Optional<IrcListHelper.Event>::isPresent) .map(Optional<IrcListHelper.Event>::get) @@ -169,7 +173,7 @@ class ChannelListFragment : ServiceBoundSettingsFragment() { }) searchButton.setOnClickListener { - viewModel.ircListHelper.value?.orNull()?.let { ircListHelper -> + modelHelper.ircListHelper.value?.orNull()?.let { ircListHelper -> val query = Query( networkId, listOf(searchInput.text.toString()) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/core/CoreInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/core/CoreInfoFragment.kt index b9d1d6c24..94f3185b2 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/core/CoreInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/core/CoreInfoFragment.kt @@ -45,10 +45,12 @@ import de.kuschku.quasseldroid.util.missingfeatures.RequiredFeatures import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.BetterLinkMovementMethod import de.kuschku.quasseldroid.util.ui.LinkLongClickMenuHelper +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import org.threeten.bp.Instant import org.threeten.bp.ZoneId import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.FormatStyle +import javax.inject.Inject class CoreInfoFragment : ServiceBoundFragment() { @@ -88,6 +90,9 @@ class CoreInfoFragment : ServiceBoundFragment() { @BindView(R.id.clients) lateinit var clients: RecyclerView + @Inject + lateinit var modelHelper: EditorViewModelHelper + private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) private val dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) @@ -105,18 +110,21 @@ class CoreInfoFragment : ServiceBoundFragment() { ButterKnife.bind(this, view) var missingFeatureList: List<MissingFeature> = emptyList() - viewModel.coreInfo.toLiveData().observe(this, Observer { - it?.orNull().let { data -> - version.text = data?.quasselVersion?.let(Html::fromHtml) + combineLatest(modelHelper.coreInfo, modelHelper.coreFeatures).toLiveData() + .observe(this, Observer { + val data = it?.first?.orNull() + val connected = it?.second?.first + ?: false + val features = it?.second?.second + ?: QuasselFeatures.empty() + + version.text = data?.quasselVersion.let(Html::fromHtml) val versionTime = data?.quasselBuildDate?.toLongOrNull() val formattedVersionTime = if (versionTime != null) dateFormatter.format(Instant.ofEpochSecond(versionTime).atZone(ZoneId.systemDefault())) else data?.quasselBuildDate?.let(Html::fromHtml) versionDate.text = formattedVersionTime - - val (connected, features) = viewModel.coreFeatures.value ?: Pair(false, - QuasselFeatures.empty()) missingFeatureList = RequiredFeatures.features.filter { it.feature !in features.enabledFeatures } @@ -126,7 +134,6 @@ class CoreInfoFragment : ServiceBoundFragment() { uptime.text = requireContext().getString(R.string.label_core_online_since, startTime.toString()) uptimeContainer.visibleIf(startTime != null) - } }) missingFeatures.setOnClickListener { MissingFeaturesDialog.Builder(requireActivity()) @@ -154,7 +161,7 @@ class CoreInfoFragment : ServiceBoundFragment() { CertificateInfoActivity.launch(it.context) } - viewModel.sslSession.toLiveData().observe(this, Observer { + modelHelper.sslSession.toLiveData().observe(this, Observer { val certificateChain = it?.orNull()?.peerCertificateChain?.map(X509Helper::convert).orEmpty() val leafCertificate = certificateChain.firstOrNull() if (leafCertificate != null) { @@ -198,13 +205,13 @@ class CoreInfoFragment : ServiceBoundFragment() { clients.layoutManager = LinearLayoutManager(requireContext()) val adapter = ClientAdapter() adapter.setDisconnectListener { - val sessionOptional = viewModel.session.value + val sessionOptional = modelHelper.session.value val session = sessionOptional?.orNull() val rpcHandler = session?.rpcHandler rpcHandler?.requestKickClient(it) } clients.adapter = adapter - viewModel.coreInfoClients.toLiveData().observe(this, Observer { + modelHelper.coreInfoClients.toLiveData().observe(this, Observer { clientsTitle.visibleIf(it?.isNotEmpty() == true) adapter.submitList(it) }) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/UserInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/UserInfoFragment.kt index 08c0a7ad0..91a7a4070 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/UserInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/info/user/UserInfoFragment.kt @@ -70,6 +70,7 @@ import de.kuschku.quasseldroid.viewmodel.data.Avatar import de.kuschku.quasseldroid.viewmodel.data.BufferHiddenState import de.kuschku.quasseldroid.viewmodel.data.BufferProps import de.kuschku.quasseldroid.viewmodel.data.BufferStatus +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import io.reactivex.Observable import javax.inject.Inject @@ -140,6 +141,9 @@ class UserInfoFragment : ServiceBoundFragment() { @Inject lateinit var matrixApi: MatrixApi + @Inject + lateinit var modelHelper: EditorViewModelHelper + private var actualUrl: String? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @@ -175,7 +179,7 @@ class UserInfoFragment : ServiceBoundFragment() { getColor(0, 0) } - combineLatest(viewModel.session, viewModel.networks).switchMap { (sessionOptional, networks) -> + combineLatest(modelHelper.session, modelHelper.networks).switchMap { (sessionOptional, networks) -> fun processUser(user: IrcUser, bufferSyncer: BufferSyncer? = null, info: BufferInfo? = null, ignoreItems: List<IgnoreListManager.IgnoreListItem>? = null): Observable<Optional<IrcUserInfo>> { actionShortcut.post(::updateShortcutVisibility) @@ -347,7 +351,7 @@ class UserInfoFragment : ServiceBoundFragment() { actionWhois.visibleIf(user.knownToCore) actionQuery.setOnClickListener { view -> - viewModel.session.value?.orNull()?.let { session -> + modelHelper.session.value?.orNull()?.let { session -> val info = session.bufferSyncer.find( bufferName = user.nick, networkId = user.networkId, @@ -357,7 +361,7 @@ class UserInfoFragment : ServiceBoundFragment() { if (info != null) { ChatActivity.launch(view.context, bufferId = info.bufferId) } else { - viewModel.allBuffers.map { + modelHelper.allBuffers.map { listOfNotNull(it.find { it.networkId == user.networkId && it.bufferName == user.nick }) @@ -410,7 +414,7 @@ class UserInfoFragment : ServiceBoundFragment() { true } it.isCheckable -> { - viewModel.ignoreListManager.value?.orNull()?.requestToggleIgnoreRule(it.title.toString()) + modelHelper.ignoreListManager.value?.orNull()?.requestToggleIgnoreRule(it.title.toString()) true } else -> false @@ -428,7 +432,7 @@ class UserInfoFragment : ServiceBoundFragment() { } actionWhois.setOnClickListener { view -> - viewModel.session { + modelHelper.session { it.orNull()?.let { session -> session.bufferSyncer.find( networkId = user.networkId, 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 index 1c13bde36..6c6874e72 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSlideFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/ServiceBoundSlideFragment.kt @@ -30,9 +30,6 @@ 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 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 index b77644f34..b3a942f9e 100644 --- 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 @@ -31,8 +31,13 @@ import de.kuschku.libquassel.quassel.ExtendedFeature import de.kuschku.libquassel.util.helpers.value import de.kuschku.quasseldroid.persistence.models.Account import de.kuschku.quasseldroid.ui.setup.ServiceBoundSetupActivity +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject class CoreSetupActivity : ServiceBoundSetupActivity() { + @Inject + lateinit var modelHelper: EditorViewModelHelper + override val initData = Bundle() override fun onCreate(savedInstanceState: Bundle?) { @@ -51,7 +56,7 @@ class CoreSetupActivity : ServiceBoundSetupActivity() { val authenticatorBackend = data.getSerializable("authenticator") as? CoreSetupBackend val authenticatorBackendSetup = data.getSerializable("authenticatorSetup") as? HashMap<String, QVariant_> - viewModel.sessionManager.value?.orNull()?.setupCore(HandshakeMessage.CoreSetupData( + modelHelper.sessionManager.value?.orNull()?.setupCore(HandshakeMessage.CoreSetupData( adminUser = user, adminPassword = pass, backend = storageBackend?.backendId, 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 index 6fd30cd0a..e1ae92985 100644 --- 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 @@ -29,8 +29,13 @@ import de.kuschku.libquassel.protocol.NetworkId import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork import de.kuschku.libquassel.util.helpers.value import de.kuschku.quasseldroid.ui.setup.ServiceBoundSetupActivity +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject class NetworkSetupActivity : ServiceBoundSetupActivity() { + @Inject + lateinit var modelHelper: EditorViewModelHelper + private lateinit var arguments: Bundle override val initData: Bundle get() = arguments @@ -45,8 +50,8 @@ class NetworkSetupActivity : ServiceBoundSetupActivity() { val networkId = NetworkId(data.getInt("network_id", -1)) val identity = IdentityId(data.getInt("identity", -1)) if (networkId.isValidId() || (network != null && identity.isValidId())) { - viewModel.backend?.value?.ifPresent { backend -> - val session = viewModel.session.value?.orNull() + modelHelper.backend?.value?.ifPresent { backend -> + val session = modelHelper.session.value?.orNull() session?.apply { rpcHandler.apply { when { 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 index c87f2a25a..19a229770 100644 --- 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 @@ -49,6 +49,8 @@ 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.ui.AnimationHelper +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper +import javax.inject.Inject class NetworkSetupNetworkSlide : ServiceBoundSlideFragment() { @BindView(R.id.identity) @@ -81,6 +83,9 @@ class NetworkSetupNetworkSlide : ServiceBoundSlideFragment() { @BindView(R.id.ssl_enabled) lateinit var sslEnabled: SwitchCompat + @Inject + lateinit var modelHelper: EditorViewModelHelper + private val identityAdapter = IdentityAdapter() private val networkAdapter = NetworkAdapter(R.string.settings_chatlist_network_create) @@ -182,7 +187,7 @@ class NetworkSetupNetworkSlide : ServiceBoundSlideFragment() { identity.adapter = identityAdapter - viewModel.identities.switchMap { + modelHelper.identities.switchMap { combineLatest(it.values.map(Identity::liveUpdates)).map { it.sortedBy(Identity::identityName) } @@ -192,7 +197,7 @@ class NetworkSetupNetworkSlide : ServiceBoundSlideFragment() { } }) - viewModel.networks.switchMap { + modelHelper.networks.switchMap { combineLatest(it.values.map(Network::liveNetworkInfo)).map { it.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, INetwork.NetworkInfo::networkName)) } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/user/UserSetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/user/UserSetupActivity.kt index d50a8be89..292f05833 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/setup/user/UserSetupActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/setup/user/UserSetupActivity.kt @@ -33,9 +33,14 @@ import de.kuschku.quasseldroid.defaults.DefaultNetwork import de.kuschku.quasseldroid.defaults.Defaults import de.kuschku.quasseldroid.ui.setup.ServiceBoundSetupActivity import de.kuschku.quasseldroid.util.helper.toLiveData +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper import java.util.* +import javax.inject.Inject class UserSetupActivity : ServiceBoundSetupActivity() { + @Inject + lateinit var modelHelper: EditorViewModelHelper + override val initData get() = Bundle().apply { putString("nick", getString(R.string.default_identity_nick, Random().nextInt(16))) @@ -45,15 +50,15 @@ class UserSetupActivity : ServiceBoundSetupActivity() { override fun onDone(data: Bundle) { val network = data.getSerializable("network") as? DefaultNetwork if (network != null) { - viewModel.backend?.value?.ifPresent { backend -> - viewModel.session.value?.orNull()?.rpcHandler?.apply { + modelHelper.backend?.value?.ifPresent { backend -> + modelHelper.session.value?.orNull()?.rpcHandler?.apply { createIdentity(Defaults.identity(this@UserSetupActivity).apply { setIdentityName(this@UserSetupActivity.getString(R.string.default_identity_identity_name)) setNicks(listOf(data.getString("nick"))) setRealName(data.getString("realname")) }, emptyMap()) - viewModel.identities + modelHelper.identities .map(Map<IdentityId, Identity>::values) .filter(Collection<Identity>::isNotEmpty) .map(Collection<Identity>::first) diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ColorContext.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ColorContext.kt index 1d575c0a4..098682f52 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/ColorContext.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ColorContext.kt @@ -28,6 +28,7 @@ import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.ui.drawable.TextDrawable import de.kuschku.quasseldroid.viewmodel.EditorViewModel +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper.Companion.IGNORED_CHARS import javax.inject.Inject class ColorContext @Inject constructor( @@ -71,7 +72,7 @@ class ColorContext @Inject constructor( fun buildTextDrawable(nickName: String, self: Boolean): TextDrawable { val senderColorIndex = SenderColorUtil.senderColor(nickName) - val rawInitial = nickName.trimStart(*EditorViewModel.IGNORED_CHARS).firstOrNull() + val rawInitial = nickName.trimStart(*IGNORED_CHARS).firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() val senderColor = if (self) selfColor else senderColors[senderColorIndex] diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ShortcutCreationHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ShortcutCreationHelper.kt index 810f85bac..742e1d141 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/ShortcutCreationHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/ShortcutCreationHelper.kt @@ -42,6 +42,7 @@ import de.kuschku.quasseldroid.util.avatars.AvatarHelper import de.kuschku.quasseldroid.util.helper.loadWithFallbacks import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.viewmodel.EditorViewModel +import de.kuschku.quasseldroid.viewmodel.helper.EditorViewModelHelper.Companion.IGNORED_CHARS object ShortcutCreationHelper { fun create(context: Context, @@ -91,7 +92,7 @@ object ShortcutCreationHelper { if (info.type.hasFlag(Buffer_Type.QueryBuffer)) { val nickName = info.bufferName ?: "" val senderColorIndex = SenderColorUtil.senderColor(nickName) - val rawInitial = nickName.trimStart(*EditorViewModel.IGNORED_CHARS).firstOrNull() + val rawInitial = nickName.trimStart(*IGNORED_CHARS).firstOrNull() ?: nickName.firstOrNull() val initial = rawInitial?.toUpperCase().toString() val senderColor = senderColors[senderColorIndex] diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt index ed67800fa..ed62a7d1a 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt @@ -83,13 +83,13 @@ abstract class ServiceBoundActivity : lateinit var connectionSettings: ConnectionSettings @Inject - lateinit var viewModel: QuasselViewModel + lateinit var quasselViewModel: QuasselViewModel protected var accountId: Long = -1 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.backendWrapper.onNext(this.backend) + quasselViewModel.backendWrapper.onNext(this.backend) updateRecentsHeader() connection.context = this lifecycle.addObserver(connection) diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt index d7d848a7c..75cba1a03 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt @@ -31,7 +31,7 @@ import javax.inject.Inject abstract class ServiceBoundFragment : DaggerFragment() { @Inject - lateinit var viewModel: QuasselViewModel + lateinit var quasselViewModel: QuasselViewModel private val connection = BackendServiceConnection() protected val backend: BehaviorSubject<Optional<Backend>> diff --git a/viewmodel/build.gradle.kts b/viewmodel/build.gradle.kts index 01954e17b..219b61edf 100644 --- a/viewmodel/build.gradle.kts +++ b/viewmodel/build.gradle.kts @@ -58,6 +58,8 @@ dependencies { implementation("org.threeten", "threetenbp", "1.3.8", classifier = "no-tzdb") implementation("org.jetbrains", "annotations", "17.0.0") + implementation("javax.inject", "javax.inject", "1") + // Quassel implementation(project(":persistence")) implementation(project(":lib")) { diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/ChatViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/ChatViewModel.kt new file mode 100644 index 000000000..f48c096f3 --- /dev/null +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/ChatViewModel.kt @@ -0,0 +1,70 @@ +/* + * 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.viewmodel + +import de.kuschku.libquassel.protocol.BufferId +import de.kuschku.libquassel.protocol.MsgId +import de.kuschku.libquassel.protocol.NetworkId +import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage +import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.PublishSubject + +open class ChatViewModel : QuasselViewModel() { + val stateReset = BehaviorSubject.create<Unit>() + val selectedMessages = BehaviorSubject.createDefault(emptyMap<MsgId, FormattedMessage>()) + val bufferSearch = BehaviorSubject.createDefault("") + val expandedMessages = BehaviorSubject.createDefault(emptySet<MsgId>()) + val buffer = BehaviorSubject.createDefault(BufferId.MAX_VALUE) + val bufferOpened = PublishSubject.create<Unit>() + val bufferViewConfigId = BehaviorSubject.createDefault(-1) + val recentlySentMessages = BehaviorSubject.createDefault(emptyList<CharSequence>()) + val showHidden = BehaviorSubject.createDefault(false) + val bufferSearchTemporarilyVisible = BehaviorSubject.createDefault(false) + val expandedNetworks = BehaviorSubject.createDefault(emptyMap<NetworkId, Boolean>()) + val selectedBufferId = BehaviorSubject.createDefault(BufferId.MAX_VALUE) + + fun resetAccount() { + bufferViewConfigId.onNext(-1) + selectedMessages.onNext(emptyMap()) + expandedMessages.onNext(emptySet()) + recentlySentMessages.onNext(emptyList()) + stateReset.onNext(Unit) + } + + fun selectedMessagesToggle(key: MsgId, value: FormattedMessage): Int { + val set = selectedMessages.value.orEmpty() + val result = if (set.containsKey(key)) set - key else set + Pair(key, value) + selectedMessages.onNext(result) + return result.size + } + + fun addRecentlySentMessage(message: CharSequence) { + recentlySentMessages.onNext( + listOf(message) + recentlySentMessages.value + .orEmpty() + .filter { it != message } + .take(MAX_RECENT_MESSAGES - 1) + ) + } + + companion object { + const val MAX_RECENT_MESSAGES = 20 + } +} diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt index 80464f9bc..a340d2d65 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt @@ -20,154 +20,9 @@ package de.kuschku.quasseldroid.viewmodel import androidx.lifecycle.ViewModel -import de.kuschku.libquassel.protocol.BufferId -import de.kuschku.libquassel.protocol.Buffer_Type -import de.kuschku.libquassel.quassel.syncables.AliasManager -import de.kuschku.libquassel.quassel.syncables.IrcChannel -import de.kuschku.libquassel.quassel.syncables.IrcUser -import de.kuschku.libquassel.quassel.syncables.Network -import de.kuschku.libquassel.session.ISession -import de.kuschku.libquassel.util.Optional -import de.kuschku.libquassel.util.flag.hasFlag -import de.kuschku.libquassel.util.helpers.mapNullable -import de.kuschku.libquassel.util.helpers.nullIf -import de.kuschku.quasseldroid.util.helper.combineLatest -import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem -import de.kuschku.quasseldroid.viewmodel.data.BufferStatus import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject -import java.util.concurrent.TimeUnit class EditorViewModel : ViewModel() { - val quasselViewModel = BehaviorSubject.create<QuasselViewModel>() - - val session = quasselViewModel.switchMap(QuasselViewModel::session) - val buffer = quasselViewModel.switchMap(QuasselViewModel::buffer) - val lastWord = BehaviorSubject.create<Observable<Pair<String, IntRange>>>() - - val rawAutoCompleteData: Observable<Triple<Optional<ISession>, BufferId, Pair<String, IntRange>>> = - combineLatest(session, buffer, lastWord).switchMap { (sessionOptional, id, lastWordWrapper) -> - lastWordWrapper - .distinctUntilChanged() - .map { lastWord -> - Triple(sessionOptional, id, lastWord) - } - } - - val autoCompleteData: Observable<Pair<String, List<AutoCompleteItem>>> = rawAutoCompleteData - .distinctUntilChanged() - .debounce(300, TimeUnit.MILLISECONDS) - .switchMap { (sessionOptional, id, lastWord) -> - val session = sessionOptional.orNull() - val bufferSyncer = session?.bufferSyncer - val bufferInfo = bufferSyncer?.bufferInfo(id) - if (bufferSyncer != null) { - session.liveNetworks().switchMap { networks -> - bufferSyncer.liveBufferInfos().switchMap { infos -> - session.aliasManager.updates().map(AliasManager::aliasList).switchMap { aliases -> - val network = networks[bufferInfo?.networkId] ?: Network.NULL - val ircChannel = if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { - network.ircChannel(bufferInfo.bufferName) ?: IrcChannel.NULL - } else IrcChannel.NULL - ircChannel.liveIrcUsers().switchMap { users -> - fun processResults(results: List<Observable<out AutoCompleteItem>>) = - combineLatest<AutoCompleteItem>(results) - .map { list -> - val filtered = list.filter { - it.name.trimStart(*IGNORED_CHARS) - .startsWith( - lastWord.first.trimStart(*IGNORED_CHARS), - ignoreCase = true - ) - } - Pair( - lastWord.first, - filtered.sorted() - ) - } - - fun getAliases() = aliases.map { - Observable.just(AutoCompleteItem.AliasItem( - "/${it.name}", - it.expansion - )) - } - - fun getBuffers() = infos.values - .filter { - it.type.toInt() == Buffer_Type.ChannelBuffer.toInt() - }.mapNotNull { info -> - networks[info.networkId]?.let { info to it } - }.map { (info, network) -> - network.liveIrcChannel( - info.bufferName - ).switchMap { channel -> - channel.updates().mapNullable(IrcChannel.NULL) { - AutoCompleteItem.ChannelItem( - info = info, - network = network.networkInfo(), - bufferStatus = when (it) { - null -> BufferStatus.OFFLINE - else -> BufferStatus.ONLINE - }, - description = it?.topic() ?: "" - ) - } - } - } - - fun getUsers(): Set<IrcUser> = when { - bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true -> - users - bufferInfo?.type?.hasFlag(Buffer_Type.QueryBuffer) == true -> - network.ircUser(bufferInfo.bufferName).nullIf { it == IrcUser.NULL }?.let { - setOf(it) - } ?: emptySet() - else -> - emptySet() - } - - fun getNicks() = getUsers().map<IrcUser, Observable<AutoCompleteItem.UserItem>> { - it.updates().map { user -> - val userModes = ircChannel.userModes(user) - val prefixModes = network.prefixModes() - - val lowestMode = userModes.mapNotNull(prefixModes::indexOf).min() - ?: prefixModes.size - - AutoCompleteItem.UserItem( - user.nick(), - user.hostMask(), - network.modesToPrefixes(userModes), - lowestMode, - user.realName(), - user.isAway(), - user.network().isMyNick(user.nick()), - network.support("CASEMAPPING") - ) - } - } - - when (lastWord.first.firstOrNull()) { - '/' -> processResults(getAliases()) - '@' -> processResults(getNicks()) - '#' -> processResults(getBuffers()) - else -> processResults(getAliases() + getNicks() + getBuffers()) - } - } - } - } - } - } else { - Observable.just(Pair(lastWord.first, emptyList())) - } - } - - companion object { - val IGNORED_CHARS = charArrayOf( - '!', '"', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', - '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~' - ) - } } diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt index da0eac52f..b18a60589 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt @@ -20,562 +20,11 @@ package de.kuschku.quasseldroid.viewmodel import androidx.lifecycle.ViewModel -import de.kuschku.libquassel.connection.ConnectionState -import de.kuschku.libquassel.connection.Features -import de.kuschku.libquassel.protocol.* -import de.kuschku.libquassel.quassel.BufferInfo -import de.kuschku.libquassel.quassel.syncables.* -import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork import de.kuschku.libquassel.session.Backend -import de.kuschku.libquassel.session.ISession -import de.kuschku.libquassel.session.SessionManager -import de.kuschku.libquassel.ssl.X509Helper 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.helpers.* -import de.kuschku.libquassel.util.irc.IrcCaseMappers -import de.kuschku.quasseldroid.util.helper.combineLatest -import de.kuschku.quasseldroid.viewmodel.data.* import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject -import io.reactivex.subjects.PublishSubject -import java.util.concurrent.TimeUnit -import javax.net.ssl.SSLSession - -class QuasselViewModel : ViewModel() { - fun resetAccount() { - bufferViewConfigId.onNext(-1) - selectedMessages.onNext(emptyMap()) - expandedMessages.onNext(emptySet()) - recentlySentMessages.onNext(emptyList()) - stateReset.onNext(Unit) - } - - val stateReset = BehaviorSubject.create<Unit>() +open class QuasselViewModel : ViewModel() { val backendWrapper = BehaviorSubject.createDefault(Observable.empty<Optional<Backend>>()) - - val selectedMessages = BehaviorSubject.createDefault(emptyMap<MsgId, FormattedMessage>()) - fun selectedMessagesToggle(key: MsgId, value: FormattedMessage): Int { - val set = selectedMessages.value.orEmpty() - val result = if (set.containsKey(key)) set - key else set + Pair(key, value) - selectedMessages.onNext(result) - return result.size - } - - val bufferSearch = BehaviorSubject.createDefault("") - - val expandedMessages = BehaviorSubject.createDefault(emptySet<MsgId>()) - - val buffer = BehaviorSubject.createDefault(BufferId.MAX_VALUE) - val bufferOpened = PublishSubject.create<Unit>() - - val bufferViewConfigId = BehaviorSubject.createDefault(-1) - - val recentlySentMessages = BehaviorSubject.createDefault(emptyList<CharSequence>()) - fun addRecentlySentMessage(message: CharSequence) { - recentlySentMessages.onNext( - listOf(message) + recentlySentMessages.value - .orEmpty() - .filter { it != message } - .take(MAX_RECENT_MESSAGES - 1) - ) - } - - val backend = backendWrapper.switchMap { it } - val sessionManager = backend.mapMapNullable(Backend::sessionManager) - val session = sessionManager.mapSwitchMap(SessionManager::session) - val rpcHandler = session.mapMap(ISession::rpcHandler) - val ircListHelper = session.mapMap(ISession::ircListHelper) - val features = sessionManager.mapSwitchMap { manager -> - manager.state.switchMap { state -> - if (state != ConnectionState.CONNECTED) { - Observable.just(Pair(false, Features.empty())) - } else { - manager.session.map { - Pair(true, it.features) - } - } - } - }.mapOrElse(Pair(false, Features.empty())) - val clientFeatures = features.map { (connected, features) -> - Pair(connected, features.client) - } - val negotiatedFeatures = features.map { (connected, features) -> - Pair(connected, features.negotiated) - } - val coreFeatures = features.map { (connected, features) -> - Pair(connected, features.core) - } - - val connectionProgress = sessionManager.mapSwitchMap(SessionManager::connectionProgress) - .mapOrElse(Triple(ConnectionState.DISCONNECTED, 0, 0)) - - val bufferViewManager = session.mapMap(ISession::bufferViewManager) - - val bufferViewConfig = bufferViewManager.flatMapSwitchMap { manager -> - bufferViewConfigId.map { id -> - Optional.ofNullable(manager.bufferViewConfig(id)) - }.mapSwitchMap(BufferViewConfig::liveUpdates) - } - - val bufferSearchTemporarilyVisible = BehaviorSubject.createDefault(false) - - val errors = sessionManager.switchMap { - it.orNull()?.error ?: Observable.empty() - } - - val connectionErrors = sessionManager.switchMap { - it.orNull()?.connectionError ?: Observable.empty() - } - - val sslSession = session.flatMapSwitchMap(ISession::sslSession) - val peerCertificateChain = sslSession.mapMap(SSLSession::getPeerCertificateChain).mapMap { - it.mapNotNull(X509Helper::convert) - }.mapOrElse(emptyList()) - val leafCertificate = peerCertificateChain.map { Optional.ofNullable(it.firstOrNull()) } - - val coreInfo = session.mapMap(ISession::coreInfo).mapSwitchMap(CoreInfo::liveInfo) - val coreInfoClients = coreInfo.mapMap(CoreInfo.CoreData::sessionConnectedClientData) - .mapOrElse(emptyList()) - - val networkConfig = session.mapMap(ISession::networkConfig) - - val ignoreListManager = session.mapMap(ISession::ignoreListManager) - - val highlightRuleManager = session.mapMap(ISession::highlightRuleManager) - - val aliasManager = session.mapMap(ISession::aliasManager) - - val networks = session.switchMap { - it.map(ISession::liveNetworks).orElse(Observable.just(emptyMap())) - } - - val identities = session.switchMap { - it.map(ISession::liveIdentities).orElse(Observable.just(emptyMap())) - } - - val bufferSyncer = session.mapMap(ISession::bufferSyncer) - val allBuffers = bufferSyncer.mapSwitchMap { - it.liveBufferInfos().map(Map<BufferId, BufferInfo>::values) - }.mapOrElse(emptyList()) - - val network = combineLatest(bufferSyncer, networks, buffer).map { (syncer, networks, buffer) -> - Optional.ofNullable(syncer.orNull()?.bufferInfo(buffer)?.let { networks[it.networkId] }) - } - - /** - * An observable of the changes of the markerline, as pairs of `(old, new)` - */ - val markerLine = session.mapSwitchMap { currentSession -> - buffer.switchMap { currentBuffer -> - // Get a stream of the latest marker line - currentSession.bufferSyncer.liveMarkerLine(currentBuffer) - } - } - - val lag: Observable<Long> = session.mapSwitchMap(ISession::lag).mapOrElse(0) - - val bufferData = combineLatest(session, buffer).switchMap { (sessionOptional, id) -> - val session = sessionOptional.orNull() - val bufferSyncer = session?.bufferSyncer - if (bufferSyncer != null) { - session.liveNetworks().switchMap { networks -> - bufferSyncer.liveBufferInfos().switchMap { - val info = bufferSyncer.bufferInfo(id) - val network = networks[info?.networkId] - if (info == null || network == null) { - Observable.just(BufferData()) - } else { - when (info.type.toInt()) { - BufferInfo.Type.QueryBuffer.toInt() -> { - network.liveIrcUser(info.bufferName).switchMap { - it.updates().map { user -> - BufferData( - info = info, - network = network, - description = user.realName(), - ircUser = - if (user == IrcUser.NULL) null - else user - ) - } - } - } - BufferInfo.Type.ChannelBuffer.toInt() -> { - network.liveIrcChannel( - info.bufferName - ).switchMap { channel -> - channel.updates().map { - BufferData( - info = info, - network = network, - description = it.topic(), - userCount = it.userCount() - ) - } - } - } - BufferInfo.Type.StatusBuffer.toInt() -> { - network.liveConnectionState().map { - BufferData( - info = info, - network = network - ) - } - } - else -> Observable.just( - BufferData( - description = "type is unknown: ${info.type.toInt()}" - ) - ) - } - } - } - } - } else { - Observable.just(BufferData()) - } - } - val bufferDataThrottled = bufferData.distinctUntilChanged().throttleLast(100, - TimeUnit.MILLISECONDS) - - val nickData: Observable<List<IrcUserItem>> = combineLatest(session, buffer) - .switchMap { (sessionOptional, buffer) -> - val session = sessionOptional.orNull() - val bufferSyncer = session?.bufferSyncer - val bufferInfo = bufferSyncer?.bufferInfo(buffer) - if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { - session.liveNetworks().switchMap { networks -> - val network = networks[bufferInfo.networkId] - network?.liveIrcChannel(bufferInfo.bufferName)?.switchMapNullable(IrcChannel.NULL) { ircChannel -> - ircChannel?.liveIrcUsers()?.switchMap { users -> - combineLatest<IrcUserItem>( - users.map<IrcUser, Observable<IrcUserItem>?> { - it.updates().map { user -> - val userModes = ircChannel.userModes(user) - val prefixModes = network.prefixModes() - - val lowestMode = userModes.asSequence().mapNotNull { - prefixModes.indexOf(it) - }.min() ?: prefixModes.size - - IrcUserItem( - user.nick(), - network.modesToPrefixes(userModes), - lowestMode, - user.realName(), - user.hostMask(), - user.isAway(), - user.network().isMyNick(user.nick()), - network.support("CASEMAPPING") - ) - } - } - ) - } ?: Observable.just(emptyList()) - } - } - } else { - Observable.just(emptyList()) - } - } - val nickDataThrottled = nickData.distinctUntilChanged().throttleLast(100, TimeUnit.MILLISECONDS) - - val bufferViewConfigs = bufferViewManager.mapSwitchMap { manager -> - manager.liveBufferViewConfigs().map { ids -> - ids.mapNotNull { id -> - manager.bufferViewConfig(id) - }.sortedWith(BufferViewConfig.NameComparator) - } - }.mapOrElse(emptyList()) - - val bufferViewConfigMap = bufferViewManager.switchMap { - it.map { manager -> - manager.liveBufferViewConfigs().map { - it.mapNotNull(manager::bufferViewConfig).associateBy(BufferViewConfig::bufferViewId) - } - }.orElse(Observable.empty()) - } - - val showHidden = BehaviorSubject.createDefault(false) - val expandedNetworks = BehaviorSubject.createDefault(emptyMap<NetworkId, Boolean>()) - val selectedBufferId = BehaviorSubject.createDefault(BufferId.MAX_VALUE) - val selectedBuffer = combineLatest(session, selectedBufferId, bufferViewConfig) - .switchMap { (sessionOptional, buffer, bufferViewConfigOptional) -> - val session = sessionOptional.orNull() - val bufferSyncer = session?.bufferSyncer - val bufferViewConfig = bufferViewConfigOptional.orNull() - if (bufferSyncer != null && bufferViewConfig != null) { - session.liveNetworks().switchMap { networks -> - val hiddenState = when { - bufferViewConfig.removedBuffers().contains(buffer) -> - BufferHiddenState.HIDDEN_PERMANENT - bufferViewConfig.temporarilyRemovedBuffers().contains(buffer) -> - BufferHiddenState.HIDDEN_TEMPORARY - else -> - BufferHiddenState.VISIBLE - } - - val info = if (!buffer.isValidId()) networks[NetworkId(-buffer.id)]?.let { - BufferInfo( - bufferId = buffer, - networkId = it.networkId(), - groupId = 0, - bufferName = it.networkName(), - type = Buffer_Type.of(Buffer_Type.StatusBuffer) - ) - } else bufferSyncer.bufferInfo(buffer) - if (info != null) { - val network = networks[info.networkId] - when (info.type.enabledValues().firstOrNull()) { - Buffer_Type.StatusBuffer -> { - network?.liveConnectionState()?.map { - SelectedBufferItem( - info, - connectionState = it, - hiddenState = hiddenState - ) - } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState)) - } - Buffer_Type.ChannelBuffer -> { - network?.liveIrcChannel(info.bufferName)?.mapNullable(IrcChannel.NULL) { - SelectedBufferItem( - info, - joined = it != null, - hiddenState = hiddenState - ) - } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState)) - } - else -> - Observable.just(SelectedBufferItem(info, hiddenState = hiddenState)) - } - } else { - Observable.just(SelectedBufferItem()) - } - } - } else { - Observable.just(SelectedBufferItem()) - } - } - - val bufferList: Observable<Pair<BufferViewConfig?, List<BufferProps>>> = - combineLatest(session, bufferViewConfig, showHidden, bufferSearch) - .switchMap { (sessionOptional, configOptional, showHiddenRaw, bufferSearch) -> - val session = sessionOptional.orNull() - val bufferSyncer = session?.bufferSyncer - val showHidden = showHiddenRaw ?: false - val config = configOptional.orNull() - if (bufferSyncer != null && config != null) { - session.liveNetworks().switchMap { networks -> - config.liveUpdates() - .switchMap { currentConfig -> - combineLatest<Collection<BufferId>>( - listOf( - config.liveBuffers(), - config.liveTemporarilyRemovedBuffers(), - config.liveRemovedBuffers() - ) - ).switchMap { (ids, temp, perm) -> - fun transformIds(ids: Collection<BufferId>, state: BufferHiddenState) = - ids.asSequence().mapNotNull { id -> - bufferSyncer.bufferInfo(id) - }.filter { - bufferSearch.isBlank() || - it.type.hasFlag(Buffer_Type.StatusBuffer) || - it.bufferName?.contains(bufferSearch, ignoreCase = true) == true - }.filter { - !currentConfig.networkId().isValidId() || currentConfig.networkId() == it.networkId - }.filter { - (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() || - (it.type.hasFlag(Buffer_Type.StatusBuffer) && !currentConfig.networkId().isValidId()) - }.mapNotNull { - val network = networks[it.networkId] - if (network == null) { - null - } else { - it to network - } - }.map<Pair<BufferInfo, Network>, Observable<BufferProps>?> { (info, network) -> - bufferSyncer.liveActivity(info.bufferId).switchMap { activity -> - bufferSyncer.liveHighlightCount(info.bufferId).map { highlights -> - activity to highlights - } - }.switchMap { (activity, highlights) -> - val name = info.bufferName?.trim() ?: "" - val search = bufferSearch.trim() - val matchMode = when { - name.equals(search, ignoreCase = true) -> - BufferProps.BufferMatchMode.EXACT - name.startsWith(search, ignoreCase = true) -> - BufferProps.BufferMatchMode.START - else -> - BufferProps.BufferMatchMode.CONTAINS - } - when (info.type.toInt()) { - BufferInfo.Type.QueryBuffer.toInt() -> { - network.liveNetworkInfo().switchMap { networkInfo -> - network.liveConnectionState().switchMap { connectionState -> - network.liveIrcUser(info.bufferName).switchMap { - it.updates().mapNullable(IrcUser.NULL) { user -> - BufferProps( - info = info, - network = networkInfo, - networkConnectionState = connectionState, - bufferStatus = when { - user == null -> BufferStatus.OFFLINE - user.isAway() -> BufferStatus.AWAY - else -> BufferStatus.ONLINE - }, - description = user?.realName() ?: "", - activity = activity, - highlights = highlights, - hiddenState = state, - ircUser = user, - matchMode = matchMode - ) - } - } - } - } - } - BufferInfo.Type.ChannelBuffer.toInt() -> { - network.liveNetworkInfo().switchMap { networkInfo -> - network.liveConnectionState().switchMap { connectionState -> - network.liveIrcChannel(info.bufferName).switchMap { channel -> - channel.updates().mapNullable(IrcChannel.NULL) { - BufferProps( - info = info, - network = networkInfo, - networkConnectionState = connectionState, - bufferStatus = when (it) { - null -> BufferStatus.OFFLINE - else -> BufferStatus.ONLINE - }, - description = it?.topic() ?: "", - activity = activity, - highlights = highlights, - hiddenState = state, - matchMode = matchMode - ) - } - } - } - } - } - BufferInfo.Type.StatusBuffer.toInt() -> { - network.liveNetworkInfo().switchMap { networkInfo -> - network.liveConnectionState().map { connectionState -> - BufferProps( - info = info, - network = networkInfo, - networkConnectionState = connectionState, - bufferStatus = BufferStatus.OFFLINE, - description = "", - activity = activity, - highlights = highlights, - hiddenState = state, - matchMode = matchMode - ) - } - } - } - else -> Observable.empty() - } - } - } - - fun missingStatusBuffers( - list: Collection<BufferId>): Sequence<Observable<BufferProps>?> { - val totalNetworks = networks.keys - val wantedNetworks = if (!currentConfig.networkId().isValidId()) totalNetworks - else listOf(currentConfig.networkId()) - - val availableNetworks = list.asSequence().mapNotNull { id -> - bufferSyncer.bufferInfo(id) - }.filter { - it.type.hasFlag(Buffer_Type.StatusBuffer) - }.map { - it.networkId - } - - val missingNetworks = wantedNetworks - availableNetworks - - return missingNetworks.asSequence().filter { - !currentConfig.networkId().isValidId() || currentConfig.networkId() == it - }.filter { - currentConfig.allowedBufferTypes().hasFlag(Buffer_Type.StatusBuffer) - }.mapNotNull { - networks[it] - }.filter { - !config.hideInactiveNetworks() || it.isConnected() - }.map<Network, Observable<BufferProps>?> { network -> - network.liveNetworkInfo().switchMap { networkInfo -> - network.liveConnectionState().map { connectionState -> - BufferProps( - info = BufferInfo( - bufferId = BufferId(-networkInfo.networkId.id), - networkId = networkInfo.networkId, - groupId = 0, - bufferName = networkInfo.networkName, - type = Buffer_Type.of(Buffer_Type.StatusBuffer) - ), - network = networkInfo, - networkConnectionState = connectionState, - bufferStatus = BufferStatus.OFFLINE, - description = "", - activity = Message_Type.of(), - highlights = 0, - hiddenState = BufferHiddenState.VISIBLE - ) - } - } - } - } - - bufferSyncer.liveBufferInfos().switchMap { - val buffers = if (showHidden || bufferSearch.isNotBlank()) { - transformIds(ids, BufferHiddenState.VISIBLE) + - transformIds(temp - ids, BufferHiddenState.HIDDEN_TEMPORARY) + - transformIds(perm - temp - ids, BufferHiddenState.HIDDEN_PERMANENT) + - missingStatusBuffers(ids + temp + perm) - } else { - transformIds(ids, BufferHiddenState.VISIBLE) + - missingStatusBuffers(ids) - } - - combineLatest<BufferProps>(buffers.toList()).map { list -> - Pair<BufferViewConfig?, List<BufferProps>>( - config, - list.asSequence().filter { - !config.hideInactiveNetworks() || - it.networkConnectionState == INetwork.ConnectionState.Initialized - }.filter { - (!config.hideInactiveBuffers()) || - it.bufferStatus != BufferStatus.OFFLINE || - it.info.type.hasFlag(Buffer_Type.StatusBuffer) - }.let { - if (config.sortAlphabetically()) - it.sortedBy { IrcCaseMappers.unicode.toLowerCaseNullable(it.info.bufferName) } - .sortedBy { it.matchMode.priority } - .sortedByDescending { it.hiddenState == BufferHiddenState.VISIBLE } - else it - }.distinctBy { - it.info.bufferId - }.toList() - ) - } - } - } - } - } - } else { - Observable.just(Pair<BufferViewConfig?, List<BufferProps>>(null, emptyList())) - } - } - - companion object { - const val MAX_RECENT_MESSAGES = 20 - } } diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt new file mode 100644 index 000000000..720b65592 --- /dev/null +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt @@ -0,0 +1,434 @@ +package de.kuschku.quasseldroid.viewmodel.helper + +import de.kuschku.libquassel.protocol.BufferId +import de.kuschku.libquassel.protocol.Buffer_Type +import de.kuschku.libquassel.protocol.Message_Type +import de.kuschku.libquassel.protocol.NetworkId +import de.kuschku.libquassel.quassel.BufferInfo +import de.kuschku.libquassel.quassel.syncables.BufferViewConfig +import de.kuschku.libquassel.quassel.syncables.IrcChannel +import de.kuschku.libquassel.quassel.syncables.IrcUser +import de.kuschku.libquassel.quassel.syncables.Network +import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork +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.helpers.flatMapSwitchMap +import de.kuschku.libquassel.util.helpers.mapNullable +import de.kuschku.libquassel.util.helpers.mapSwitchMap +import de.kuschku.libquassel.util.helpers.switchMapNullable +import de.kuschku.libquassel.util.irc.IrcCaseMappers +import de.kuschku.quasseldroid.util.helper.combineLatest +import de.kuschku.quasseldroid.viewmodel.ChatViewModel +import de.kuschku.quasseldroid.viewmodel.QuasselViewModel +import de.kuschku.quasseldroid.viewmodel.data.* +import io.reactivex.Observable +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +open class ChatViewModelHelper @Inject constructor( + val chat: ChatViewModel, + quassel: QuasselViewModel +) : QuasselViewModelHelper(quassel) { + val bufferViewConfig = bufferViewManager.flatMapSwitchMap { manager -> + chat.bufferViewConfigId.map { id -> + Optional.ofNullable(manager.bufferViewConfig(id)) + }.mapSwitchMap(BufferViewConfig::liveUpdates) + } + + val network = combineLatest(bufferSyncer, networks, chat.buffer) + .map { (syncer, networks, buffer) -> + Optional.ofNullable(syncer.orNull()?.bufferInfo(buffer)?.let { networks[it.networkId] }) + } + + /** + * An observable of the changes of the markerline, as pairs of `(old, new)` + */ + val markerLine = session.mapSwitchMap { currentSession -> + chat.buffer.switchMap { currentBuffer -> + // Get a stream of the latest marker line + currentSession.bufferSyncer.liveMarkerLine(currentBuffer) + } + } + + val bufferData = combineLatest(session, chat.buffer) + .switchMap { (sessionOptional, id) -> + val session = sessionOptional.orNull() + val bufferSyncer = session?.bufferSyncer + if (bufferSyncer != null) { + session.liveNetworks().switchMap { networks -> + bufferSyncer.liveBufferInfos().switchMap { + val info = bufferSyncer.bufferInfo(id) + val network = networks[info?.networkId] + if (info == null || network == null) { + Observable.just(BufferData()) + } else { + when (info.type.toInt()) { + BufferInfo.Type.QueryBuffer.toInt() -> { + network.liveIrcUser(info.bufferName).switchMap { + it.updates().map { user -> + BufferData( + info = info, + network = network, + description = user.realName(), + ircUser = + if (user == IrcUser.NULL) null + else user + ) + } + } + } + BufferInfo.Type.ChannelBuffer.toInt() -> { + network.liveIrcChannel( + info.bufferName + ).switchMap { channel -> + channel.updates().map { + BufferData( + info = info, + network = network, + description = it.topic(), + userCount = it.userCount() + ) + } + } + } + BufferInfo.Type.StatusBuffer.toInt() -> { + network.liveConnectionState().map { + BufferData( + info = info, + network = network + ) + } + } + else -> Observable.just( + BufferData( + description = "type is unknown: ${info.type.toInt()}" + ) + ) + } + } + } + } + } else { + Observable.just(BufferData()) + } + } + val bufferDataThrottled = + bufferData.distinctUntilChanged().throttleLast(100, TimeUnit.MILLISECONDS) + + val nickData: Observable<List<IrcUserItem>> = combineLatest(session, chat.buffer) + .switchMap { (sessionOptional, buffer) -> + val session = sessionOptional.orNull() + val bufferSyncer = session?.bufferSyncer + val bufferInfo = bufferSyncer?.bufferInfo(buffer) + if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { + session.liveNetworks().switchMap { networks -> + val network = networks[bufferInfo.networkId] + network?.liveIrcChannel(bufferInfo.bufferName)?.switchMapNullable(IrcChannel.NULL) { ircChannel -> + ircChannel?.liveIrcUsers()?.switchMap { users -> + combineLatest<IrcUserItem>( + users.map<IrcUser, Observable<IrcUserItem>?> { + it.updates().map { user -> + val userModes = ircChannel.userModes(user) + val prefixModes = network.prefixModes() + + val lowestMode = userModes.asSequence().mapNotNull { + prefixModes.indexOf(it) + }.min() ?: prefixModes.size + + IrcUserItem( + user.nick(), + network.modesToPrefixes(userModes), + lowestMode, + user.realName(), + user.hostMask(), + user.isAway(), + user.network().isMyNick(user.nick()), + network.support("CASEMAPPING") + ) + } + } + ) + } ?: Observable.just(emptyList()) + } + } + } else { + Observable.just(emptyList()) + } + } + val nickDataThrottled = + nickData.distinctUntilChanged().throttleLast(100, TimeUnit.MILLISECONDS) + + val selectedBuffer = combineLatest(session, chat.selectedBufferId, bufferViewConfig) + .switchMap { (sessionOptional, buffer, bufferViewConfigOptional) -> + val session = sessionOptional.orNull() + val bufferSyncer = session?.bufferSyncer + val bufferViewConfig = bufferViewConfigOptional.orNull() + if (bufferSyncer != null && bufferViewConfig != null) { + session.liveNetworks().switchMap { networks -> + val hiddenState = when { + bufferViewConfig.removedBuffers().contains(buffer) -> + BufferHiddenState.HIDDEN_PERMANENT + bufferViewConfig.temporarilyRemovedBuffers().contains(buffer) -> + BufferHiddenState.HIDDEN_TEMPORARY + else -> + BufferHiddenState.VISIBLE + } + + val info = if (!buffer.isValidId()) networks[NetworkId(-buffer.id)]?.let { + BufferInfo( + bufferId = buffer, + networkId = it.networkId(), + groupId = 0, + bufferName = it.networkName(), + type = Buffer_Type.of(Buffer_Type.StatusBuffer) + ) + } else bufferSyncer.bufferInfo(buffer) + if (info != null) { + val network = networks[info.networkId] + when (info.type.enabledValues().firstOrNull()) { + Buffer_Type.StatusBuffer -> { + network?.liveConnectionState()?.map { + SelectedBufferItem( + info, + connectionState = it, + hiddenState = hiddenState + ) + } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState)) + } + Buffer_Type.ChannelBuffer -> { + network?.liveIrcChannel(info.bufferName)?.mapNullable(IrcChannel.NULL) { + SelectedBufferItem( + info, + joined = it != null, + hiddenState = hiddenState + ) + } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState)) + } + else -> + Observable.just(SelectedBufferItem(info, hiddenState = hiddenState)) + } + } else { + Observable.just(SelectedBufferItem()) + } + } + } else { + Observable.just(SelectedBufferItem()) + } + } + + val bufferList: Observable<Pair<BufferViewConfig?, List<BufferProps>>> = + combineLatest(session, bufferViewConfig, chat.showHidden, chat.bufferSearch) + .switchMap { (sessionOptional, configOptional, showHiddenRaw, bufferSearch) -> + val session = sessionOptional.orNull() + val bufferSyncer = session?.bufferSyncer + val showHidden = showHiddenRaw ?: false + val config = configOptional.orNull() + if (bufferSyncer != null && config != null) { + session.liveNetworks().switchMap { networks -> + config.liveUpdates() + .switchMap { currentConfig -> + combineLatest<Collection<BufferId>>( + listOf( + config.liveBuffers(), + config.liveTemporarilyRemovedBuffers(), + config.liveRemovedBuffers() + ) + ).switchMap { (ids, temp, perm) -> + fun transformIds(ids: Collection<BufferId>, state: BufferHiddenState) = + ids.asSequence().mapNotNull { id -> + bufferSyncer.bufferInfo(id) + }.filter { + bufferSearch.isBlank() || + it.type.hasFlag(Buffer_Type.StatusBuffer) || + it.bufferName?.contains(bufferSearch, ignoreCase = true) == true + }.filter { + !currentConfig.networkId().isValidId() || currentConfig.networkId() == it.networkId + }.filter { + (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() || + (it.type.hasFlag(Buffer_Type.StatusBuffer) && !currentConfig.networkId().isValidId()) + }.mapNotNull { + val network = networks[it.networkId] + if (network == null) { + null + } else { + it to network + } + }.map<Pair<BufferInfo, Network>, Observable<BufferProps>?> { (info, network) -> + bufferSyncer.liveActivity(info.bufferId).switchMap { activity -> + bufferSyncer.liveHighlightCount(info.bufferId).map { highlights -> + activity to highlights + } + }.switchMap { (activity, highlights) -> + val name = info.bufferName?.trim() ?: "" + val search = bufferSearch.trim() + val matchMode = when { + name.equals(search, ignoreCase = true) -> + BufferProps.BufferMatchMode.EXACT + name.startsWith(search, ignoreCase = true) -> + BufferProps.BufferMatchMode.START + else -> + BufferProps.BufferMatchMode.CONTAINS + } + when (info.type.toInt()) { + BufferInfo.Type.QueryBuffer.toInt() -> { + network.liveNetworkInfo().switchMap { networkInfo -> + network.liveConnectionState().switchMap { connectionState -> + network.liveIrcUser(info.bufferName).switchMap { + it.updates().mapNullable(IrcUser.NULL) { user -> + BufferProps( + info = info, + network = networkInfo, + networkConnectionState = connectionState, + bufferStatus = when { + user == null -> BufferStatus.OFFLINE + user.isAway() -> BufferStatus.AWAY + else -> BufferStatus.ONLINE + }, + description = user?.realName() ?: "", + activity = activity, + highlights = highlights, + hiddenState = state, + ircUser = user, + matchMode = matchMode + ) + } + } + } + } + } + BufferInfo.Type.ChannelBuffer.toInt() -> { + network.liveNetworkInfo().switchMap { networkInfo -> + network.liveConnectionState().switchMap { connectionState -> + network.liveIrcChannel(info.bufferName).switchMap { channel -> + channel.updates().mapNullable(IrcChannel.NULL) { + BufferProps( + info = info, + network = networkInfo, + networkConnectionState = connectionState, + bufferStatus = when (it) { + null -> BufferStatus.OFFLINE + else -> BufferStatus.ONLINE + }, + description = it?.topic() ?: "", + activity = activity, + highlights = highlights, + hiddenState = state, + matchMode = matchMode + ) + } + } + } + } + } + BufferInfo.Type.StatusBuffer.toInt() -> { + network.liveNetworkInfo().switchMap { networkInfo -> + network.liveConnectionState().map { connectionState -> + BufferProps( + info = info, + network = networkInfo, + networkConnectionState = connectionState, + bufferStatus = BufferStatus.OFFLINE, + description = "", + activity = activity, + highlights = highlights, + hiddenState = state, + matchMode = matchMode + ) + } + } + } + else -> Observable.empty() + } + } + } + + fun missingStatusBuffers( + list: Collection<BufferId>): Sequence<Observable<BufferProps>?> { + val totalNetworks = networks.keys + val wantedNetworks = if (!currentConfig.networkId().isValidId()) totalNetworks + else listOf(currentConfig.networkId()) + + val availableNetworks = list.asSequence().mapNotNull { id -> + bufferSyncer.bufferInfo(id) + }.filter { + it.type.hasFlag(Buffer_Type.StatusBuffer) + }.map { + it.networkId + } + + val missingNetworks = wantedNetworks - availableNetworks + + return missingNetworks.asSequence().filter { + !currentConfig.networkId().isValidId() || currentConfig.networkId() == it + }.filter { + currentConfig.allowedBufferTypes().hasFlag(Buffer_Type.StatusBuffer) + }.mapNotNull { + networks[it] + }.filter { + !config.hideInactiveNetworks() || it.isConnected() + }.map<Network, Observable<BufferProps>?> { network -> + network.liveNetworkInfo().switchMap { networkInfo -> + network.liveConnectionState().map { connectionState -> + BufferProps( + info = BufferInfo( + bufferId = BufferId(-networkInfo.networkId.id), + networkId = networkInfo.networkId, + groupId = 0, + bufferName = networkInfo.networkName, + type = Buffer_Type.of(Buffer_Type.StatusBuffer) + ), + network = networkInfo, + networkConnectionState = connectionState, + bufferStatus = BufferStatus.OFFLINE, + description = "", + activity = Message_Type.of(), + highlights = 0, + hiddenState = BufferHiddenState.VISIBLE + ) + } + } + } + } + + bufferSyncer.liveBufferInfos().switchMap { + val buffers = if (showHidden || bufferSearch.isNotBlank()) { + transformIds(ids, BufferHiddenState.VISIBLE) + + transformIds(temp - ids, BufferHiddenState.HIDDEN_TEMPORARY) + + transformIds(perm - temp - ids, BufferHiddenState.HIDDEN_PERMANENT) + + missingStatusBuffers(ids + temp + perm) + } else { + transformIds(ids, BufferHiddenState.VISIBLE) + + missingStatusBuffers(ids) + } + + combineLatest<BufferProps>(buffers.toList()).map { list -> + Pair<BufferViewConfig?, List<BufferProps>>( + config, + list.asSequence().filter { + !config.hideInactiveNetworks() || + it.networkConnectionState == INetwork.ConnectionState.Initialized + }.filter { + (!config.hideInactiveBuffers()) || + it.bufferStatus != BufferStatus.OFFLINE || + it.info.type.hasFlag(Buffer_Type.StatusBuffer) + }.let { + if (config.sortAlphabetically()) + it.sortedBy { IrcCaseMappers.unicode.toLowerCaseNullable(it.info.bufferName) } + .sortedBy { it.matchMode.priority } + .sortedByDescending { it.hiddenState == BufferHiddenState.VISIBLE } + else it + }.distinctBy { + it.info.bufferId + }.toList() + ) + } + } + } + } + } + } else { + Observable.just(Pair<BufferViewConfig?, List<BufferProps>>(null, emptyList())) + } + } +} diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/EditorViewModelHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/EditorViewModelHelper.kt new file mode 100644 index 000000000..89f1d1011 --- /dev/null +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/EditorViewModelHelper.kt @@ -0,0 +1,155 @@ +package de.kuschku.quasseldroid.viewmodel.helper + +import de.kuschku.libquassel.protocol.BufferId +import de.kuschku.libquassel.protocol.Buffer_Type +import de.kuschku.libquassel.quassel.syncables.AliasManager +import de.kuschku.libquassel.quassel.syncables.IrcChannel +import de.kuschku.libquassel.quassel.syncables.IrcUser +import de.kuschku.libquassel.quassel.syncables.Network +import de.kuschku.libquassel.session.ISession +import de.kuschku.libquassel.util.Optional +import de.kuschku.libquassel.util.flag.hasFlag +import de.kuschku.libquassel.util.helpers.mapNullable +import de.kuschku.libquassel.util.helpers.nullIf +import de.kuschku.quasseldroid.util.helper.combineLatest +import de.kuschku.quasseldroid.viewmodel.ChatViewModel +import de.kuschku.quasseldroid.viewmodel.EditorViewModel +import de.kuschku.quasseldroid.viewmodel.QuasselViewModel +import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem +import de.kuschku.quasseldroid.viewmodel.data.BufferStatus +import io.reactivex.Observable +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +open class EditorViewModelHelper @Inject constructor( + val editor: EditorViewModel, + chat: ChatViewModel, + quassel: QuasselViewModel +) : ChatViewModelHelper(chat, quassel) { + val rawAutoCompleteData: Observable<Triple<Optional<ISession>, BufferId, Pair<String, IntRange>>> = + combineLatest(session, + chat.buffer, + editor.lastWord).switchMap { (sessionOptional, id, lastWordWrapper) -> + lastWordWrapper + .distinctUntilChanged() + .map { lastWord -> + Triple(sessionOptional, id, lastWord) + } + } + + val autoCompleteData: Observable<Pair<String, List<AutoCompleteItem>>> = rawAutoCompleteData + .distinctUntilChanged() + .debounce(300, TimeUnit.MILLISECONDS) + .switchMap { (sessionOptional, id, lastWord) -> + val session = sessionOptional.orNull() + val bufferSyncer = session?.bufferSyncer + val bufferInfo = bufferSyncer?.bufferInfo(id) + if (bufferSyncer != null) { + session.liveNetworks().switchMap { networks -> + bufferSyncer.liveBufferInfos().switchMap { infos -> + session.aliasManager.updates().map(AliasManager::aliasList).switchMap { aliases -> + val network = networks[bufferInfo?.networkId] ?: Network.NULL + val ircChannel = if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { + network.ircChannel(bufferInfo.bufferName) ?: IrcChannel.NULL + } else IrcChannel.NULL + ircChannel.liveIrcUsers().switchMap { users -> + fun processResults(results: List<Observable<out AutoCompleteItem>>) = + combineLatest<AutoCompleteItem>(results) + .map { list -> + val filtered = list.filter { + it.name.trimStart(*IGNORED_CHARS) + .startsWith( + lastWord.first.trimStart(*IGNORED_CHARS), + ignoreCase = true + ) + } + Pair( + lastWord.first, + filtered.sorted() + ) + } + + fun getAliases() = aliases.map { + Observable.just(AutoCompleteItem.AliasItem( + "/${it.name}", + it.expansion + )) + } + + fun getBuffers() = infos.values + .filter { + it.type.toInt() == Buffer_Type.ChannelBuffer.toInt() + }.mapNotNull { info -> + networks[info.networkId]?.let { info to it } + }.map { (info, network) -> + network.liveIrcChannel( + info.bufferName + ).switchMap { channel -> + channel.updates().mapNullable(IrcChannel.NULL) { + AutoCompleteItem.ChannelItem( + info = info, + network = network.networkInfo(), + bufferStatus = when (it) { + null -> BufferStatus.OFFLINE + else -> BufferStatus.ONLINE + }, + description = it?.topic() ?: "" + ) + } + } + } + + fun getUsers(): Set<IrcUser> = when { + bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true -> + users + bufferInfo?.type?.hasFlag(Buffer_Type.QueryBuffer) == true -> + network.ircUser(bufferInfo.bufferName).nullIf { it == IrcUser.NULL }?.let { + setOf(it) + } ?: emptySet() + else -> + emptySet() + } + + fun getNicks() = getUsers().map<IrcUser, Observable<AutoCompleteItem.UserItem>> { + it.updates().map { user -> + val userModes = ircChannel.userModes(user) + val prefixModes = network.prefixModes() + + val lowestMode = userModes.mapNotNull(prefixModes::indexOf).min() + ?: prefixModes.size + + AutoCompleteItem.UserItem( + user.nick(), + user.hostMask(), + network.modesToPrefixes(userModes), + lowestMode, + user.realName(), + user.isAway(), + user.network().isMyNick(user.nick()), + network.support("CASEMAPPING") + ) + } + } + + when (lastWord.first.firstOrNull()) { + '/' -> processResults(getAliases()) + '@' -> processResults(getNicks()) + '#' -> processResults(getBuffers()) + else -> processResults(getAliases() + getNicks() + getBuffers()) + } + } + } + } + } + } else { + Observable.just(Pair(lastWord.first, emptyList())) + } + } + + companion object { + val IGNORED_CHARS = charArrayOf( + '!', '"', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', + '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~' + ) + } +} diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/QuasselViewModelHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/QuasselViewModelHelper.kt new file mode 100644 index 000000000..7c839ebf0 --- /dev/null +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/QuasselViewModelHelper.kt @@ -0,0 +1,110 @@ +package de.kuschku.quasseldroid.viewmodel.helper + +import de.kuschku.libquassel.connection.ConnectionState +import de.kuschku.libquassel.connection.Features +import de.kuschku.libquassel.protocol.BufferId +import de.kuschku.libquassel.quassel.BufferInfo +import de.kuschku.libquassel.quassel.syncables.BufferViewConfig +import de.kuschku.libquassel.quassel.syncables.CoreInfo +import de.kuschku.libquassel.session.Backend +import de.kuschku.libquassel.session.ISession +import de.kuschku.libquassel.session.SessionManager +import de.kuschku.libquassel.ssl.X509Helper +import de.kuschku.libquassel.util.Optional +import de.kuschku.libquassel.util.helpers.* +import de.kuschku.quasseldroid.viewmodel.QuasselViewModel +import io.reactivex.Observable +import javax.inject.Inject +import javax.net.ssl.SSLSession + +open class QuasselViewModelHelper @Inject constructor( + val quassel: QuasselViewModel +) { + val backend = quassel.backendWrapper.switchMap { it } + val sessionManager = backend.mapMapNullable(Backend::sessionManager) + val session = sessionManager.mapSwitchMap(SessionManager::session) + val rpcHandler = session.mapMap(ISession::rpcHandler) + val ircListHelper = session.mapMap(ISession::ircListHelper) + val features = sessionManager.mapSwitchMap { manager -> + manager.state.switchMap { state -> + if (state != ConnectionState.CONNECTED) { + Observable.just(Pair(false, Features.empty())) + } else { + manager.session.map { + Pair(true, it.features) + } + } + } + }.mapOrElse(Pair(false, Features.empty())) + val clientFeatures = features.map { (connected, features) -> + Pair(connected, features.client) + } + val negotiatedFeatures = features.map { (connected, features) -> + Pair(connected, features.negotiated) + } + val coreFeatures = features.map { (connected, features) -> + Pair(connected, features.core) + } + + val connectionProgress = sessionManager.mapSwitchMap(SessionManager::connectionProgress) + .mapOrElse(Triple(ConnectionState.DISCONNECTED, 0, 0)) + + val bufferViewManager = session.mapMap(ISession::bufferViewManager) + + val errors = sessionManager.switchMap { + it.orNull()?.error ?: Observable.empty() + } + + val connectionErrors = sessionManager.switchMap { + it.orNull()?.connectionError ?: Observable.empty() + } + + val sslSession = session.flatMapSwitchMap(ISession::sslSession) + val peerCertificateChain = sslSession.mapMap(SSLSession::getPeerCertificateChain).mapMap { + it.mapNotNull(X509Helper::convert) + }.mapOrElse(emptyList()) + val leafCertificate = peerCertificateChain.map { Optional.ofNullable(it.firstOrNull()) } + + val coreInfo = session.mapMap(ISession::coreInfo).mapSwitchMap(CoreInfo::liveInfo) + val coreInfoClients = coreInfo.mapMap(CoreInfo.CoreData::sessionConnectedClientData) + .mapOrElse(emptyList()) + + val networkConfig = session.mapMap(ISession::networkConfig) + + val ignoreListManager = session.mapMap(ISession::ignoreListManager) + + val highlightRuleManager = session.mapMap(ISession::highlightRuleManager) + + val aliasManager = session.mapMap(ISession::aliasManager) + + val networks = session.switchMap { + it.map(ISession::liveNetworks).orElse(Observable.just(emptyMap())) + } + + val identities = session.switchMap { + it.map(ISession::liveIdentities).orElse(Observable.just(emptyMap())) + } + + val bufferSyncer = session.mapMap(ISession::bufferSyncer) + val allBuffers = bufferSyncer.mapSwitchMap { + it.liveBufferInfos().map(Map<BufferId, BufferInfo>::values) + }.mapOrElse(emptyList()) + + val lag: Observable<Long> = session.mapSwitchMap(ISession::lag).mapOrElse(0) + + val bufferViewConfigs = bufferViewManager.mapSwitchMap { manager -> + manager.liveBufferViewConfigs().map { ids -> + ids.mapNotNull { id -> + manager.bufferViewConfig(id) + }.sortedWith(BufferViewConfig.NameComparator) + } + }.mapOrElse(emptyList()) + + val bufferViewConfigMap = bufferViewManager.switchMap { + it.map { manager -> + manager.liveBufferViewConfigs().map { + it.mapNotNull(manager::bufferViewConfig).associateBy(BufferViewConfig::bufferViewId) + } + }.orElse(Observable.empty()) + } +} -- GitLab