From b01cba18ff2fc7bc7a580a8f8d102522c1eab24d Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Wed, 21 Feb 2018 14:45:36 +0100 Subject: [PATCH] Code cleanup --- .../ui/chat/BufferViewConfigFragment.kt | 206 ++--------- .../quasseldroid_ng/ui/chat/ChatActivity.kt | 62 +--- .../quasseldroid_ng/ui/chat/MessageAdapter.kt | 17 +- .../ui/chat/MessageListFragment.kt | 75 ++-- .../ui/chat/NickListFragment.kt | 88 +---- .../ui/chat/QuasselMessageRenderer.kt | 1 + .../ui/chat/ToolbarFragment.kt | 126 ++----- .../ui/viewmodel/QuasselViewModel.kt | 325 ++++++++++++++++++ .../util/helper/LiveDataHelper.kt | 33 +- .../quassel/syncables/BufferViewManager.kt | 5 +- .../libquassel/quassel/syncables/IrcUser.kt | 2 +- 11 files changed, 501 insertions(+), 439 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt index f0de1e7bd..6c5b6b382 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt @@ -1,7 +1,6 @@ package de.kuschku.quasseldroid_ng.ui.chat -import android.arch.lifecycle.LiveData -import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.ViewModelProviders import android.os.Bundle import android.support.v7.widget.* import android.view.LayoutInflater @@ -10,25 +9,14 @@ import android.view.ViewGroup import android.widget.AdapterView import butterknife.BindView import butterknife.ButterKnife -import de.kuschku.libquassel.protocol.* -import de.kuschku.libquassel.quassel.BufferInfo -import de.kuschku.libquassel.quassel.syncables.* -import de.kuschku.libquassel.session.Backend -import de.kuschku.libquassel.session.ISession -import de.kuschku.libquassel.session.SessionManager -import de.kuschku.libquassel.util.and -import de.kuschku.libquassel.util.hasFlag +import de.kuschku.libquassel.protocol.BufferId import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.ui.settings.data.RenderingSettings +import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread import de.kuschku.quasseldroid_ng.util.helper.map -import de.kuschku.quasseldroid_ng.util.helper.or -import de.kuschku.quasseldroid_ng.util.helper.switchMap -import de.kuschku.quasseldroid_ng.util.helper.switchMapRx import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment -import io.reactivex.Observable -import java.util.concurrent.TimeUnit class BufferViewConfigFragment : ServiceBoundFragment() { private val handlerThread = AndroidHandlerThread("ChatList") @@ -42,37 +30,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { @BindView(R.id.chatList) lateinit var chatList: RecyclerView - val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData() - - private val sessionManager: LiveData<SessionManager?> - = backend.map(Backend::sessionManager) - private val bufferViewManager: LiveData<BufferViewManager?> - = sessionManager.switchMapRx(SessionManager::session).map(ISession::bufferViewManager) - private val networks: LiveData<Map<NetworkId, Network>?> - = sessionManager.switchMapRx(SessionManager::session).map(ISession::networks) - private val bufferViewConfigs = bufferViewManager.switchMapRx { manager -> - manager.live_bufferViewConfigs.map { ids -> - ids.mapNotNull { id -> - manager.bufferViewConfig(id) - }.sortedWith( - Comparator { a, b -> - (a?.bufferViewName() ?: "").compareTo((b?.bufferViewName() ?: ""), true) - } - ) - } - }.or(emptyList()) - - private val selectedBufferViewConfig = MutableLiveData<BufferViewConfig>() - - private val itemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onNothingSelected(p0: AdapterView<*>?) { - selectedBufferViewConfig.value = null - } - - override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) { - selectedBufferViewConfig.value = adapter.getItem(p2) - } - } + private lateinit var viewModel: QuasselViewModel private var ircFormatDeserializer: IrcFormatDeserializer? = null private val renderingSettings = RenderingSettings( @@ -82,135 +40,12 @@ class BufferViewConfigFragment : ServiceBoundFragment() { timeFormat = "" ) - private val adapter = BufferViewConfigAdapter(this, bufferViewConfigs) - - private val bufferIdList = selectedBufferViewConfig.switchMapRx(BufferViewConfig::live_buffers) - - private val bufferList: LiveData<List<BufferListAdapter.BufferProps>?> = sessionManager.switchMap { manager -> - selectedBufferViewConfig.switchMapRx { config -> - config.live_config.debounce(16, TimeUnit.MILLISECONDS).switchMap { currentConfig -> - config.live_buffers.switchMap { ids -> - val bufferSyncer = manager.bufferSyncer - if (bufferSyncer != null) { - bufferSyncer.liveBufferInfos().switchMap { - Observable.combineLatest( - ids.mapNotNull { id -> - bufferSyncer.bufferInfo(id) - }.filter { - currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId - }.filter { - (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() || - it.type.hasFlag(Buffer_Type.StatusBuffer) - }.mapNotNull { - val network = manager.networks[it.networkId] - if (network == null) { - null - } else { - it to network - } - }.map { (info, network) -> - bufferSyncer.liveActivity(info.bufferId).switchMap { activity -> - bufferSyncer.liveHighlightCount(info.bufferId).map { highlights -> - when { - highlights > 0 -> Buffer_Activity.Highlight - activity.hasFlag(Message.MessageType.Plain) || - activity.hasFlag(Message.MessageType.Notice) || - activity.hasFlag(Message.MessageType.Action) -> Buffer_Activity.NewMessage - activity.isNotEmpty() -> Buffer_Activity.OtherActivity - else -> Buffer_Activity.NoActivity - } - } - }.switchMap { activity -> - when (info.type.toInt()) { - BufferInfo.Type.QueryBuffer.toInt() -> { - network.liveIrcUser(info.bufferName).switchMap { user -> - user.live_away.switchMap { away -> - user.live_realName.map { realName -> - BufferListAdapter.BufferProps( - info = info, - network = network.networkInfo(), - bufferStatus = when { - user == IrcUser.NULL -> BufferListAdapter.BufferStatus.OFFLINE - away -> BufferListAdapter.BufferStatus.AWAY - else -> BufferListAdapter.BufferStatus.ONLINE - }, - description = ircFormatDeserializer?.formatString( - realName, renderingSettings.colorizeMirc - ) ?: realName, - activity = activity - ) - } - } - } - } - BufferInfo.Type.ChannelBuffer.toInt() -> { - network.liveIrcChannel( - info.bufferName - ).switchMap { channel -> - channel.live_topic.map { topic -> - BufferListAdapter.BufferProps( - info = info, - network = network.networkInfo(), - bufferStatus = when (channel) { - IrcChannel.NULL -> BufferListAdapter.BufferStatus.OFFLINE - else -> BufferListAdapter.BufferStatus.ONLINE - }, - description = ircFormatDeserializer?.formatString( - topic, renderingSettings.colorizeMirc - ) ?: topic, - activity = activity - ) - } - } - } - BufferInfo.Type.StatusBuffer.toInt() -> { - network.liveConnectionState.map { - BufferListAdapter.BufferProps( - info = info, - network = network.networkInfo(), - bufferStatus = BufferListAdapter.BufferStatus.OFFLINE, - description = "", - activity = activity - ) - } - } - else -> Observable.just( - BufferListAdapter.BufferProps( - info = info, - network = network.networkInfo(), - bufferStatus = BufferListAdapter.BufferStatus.OFFLINE, - description = "", - activity = activity - ) - ) - } - } - }, { array: Array<Any> -> - array.toList() as List<BufferListAdapter.BufferProps> - } - ).map { list -> - list.filter { - config.minimumActivity().value <= it.activity.bit || - it.info.type.hasFlag(Buffer_Type.StatusBuffer) - }.filter { - (!config.hideInactiveBuffers()) || - it.bufferStatus != BufferListAdapter.BufferStatus.OFFLINE || - it.info.type.hasFlag(Buffer_Type.StatusBuffer) - } - } - } - } else { - Observable.empty() - } - } - } - } - } - override fun onCreate(savedInstanceState: Bundle?) { handlerThread.onCreate() super.onCreate(savedInstanceState) + viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java] + if (ircFormatDeserializer == null) { ircFormatDeserializer = IrcFormatDeserializer(context!!) } @@ -220,12 +55,31 @@ class BufferViewConfigFragment : ServiceBoundFragment() { savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_chat_list, container, false) ButterKnife.bind(this, view) + + val adapter = BufferViewConfigAdapter(this, viewModel.bufferViewConfigs) + chatListSpinner.adapter = adapter - chatListSpinner.onItemSelectedListener = itemSelectedListener + chatListSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(p0: AdapterView<*>?) { + viewModel.setBufferViewConfig(null) + } + + override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) { + viewModel.setBufferViewConfig(adapter.getItem(p2)) + } + } chatList.adapter = BufferListAdapter( this, - bufferList, + viewModel.bufferList.map { + it.map { + it.copy( + description = ircFormatDeserializer?.formatString( + it.description.toString(), renderingSettings.colorizeMirc + ) ?: it.description + ) + } + }, handlerThread::post, activity!!::runOnUiThread, clickListener @@ -241,11 +95,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { super.onDestroy() } - val clickListeners = mutableListOf<(BufferId) -> Unit>() - private val clickListener: ((BufferId) -> Unit)? = { - for (clickListener in clickListeners) { - clickListener.invoke(it) - } + viewModel.setBuffer(it) } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt index d1f3c74c0..c62795a30 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt @@ -1,9 +1,8 @@ package de.kuschku.quasseldroid_ng.ui.chat import android.app.Activity -import android.arch.lifecycle.LiveData -import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders import android.content.Context import android.os.Bundle import android.support.design.widget.Snackbar @@ -17,26 +16,19 @@ import android.widget.Button import android.widget.EditText import butterknife.BindView import butterknife.ButterKnife -import de.kuschku.libquassel.protocol.BufferId -import de.kuschku.libquassel.session.Backend import de.kuschku.libquassel.session.ConnectionState -import de.kuschku.libquassel.session.SessionManager import de.kuschku.libquassel.session.SocketAddress import de.kuschku.quasseldroid_ng.Keys import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.AccountDatabase import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase +import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread import de.kuschku.quasseldroid_ng.util.helper.* import de.kuschku.quasseldroid_ng.util.service.ServiceBoundActivity import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar class ChatActivity : ServiceBoundActivity() { - private var contentMessages: MessageListFragment? = null - private var chatListFragment: BufferViewConfigFragment? = null - private var nickListFragment: NickListFragment? = null - private var toolbarFragment: ToolbarFragment? = null - @BindView(R.id.drawerLayout) lateinit var drawerLayout: DrawerLayout @@ -56,14 +48,10 @@ class ChatActivity : ServiceBoundActivity() { private val handler = AndroidHandlerThread("Chat") - private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager) - private val state = sessionManager.switchMapRx(SessionManager::state) - private val initStatus = sessionManager.switchMapRx(SessionManager::initStatus) + private lateinit var viewModel: QuasselViewModel private var snackbar: Snackbar? = null - private val currentBuffer = MutableLiveData<BufferId>() - private lateinit var database: QuasselDatabase override fun onCreate(savedInstanceState: Bundle?) { @@ -72,34 +60,14 @@ class ChatActivity : ServiceBoundActivity() { setContentView(R.layout.activity_main) ButterKnife.bind(this) - database = QuasselDatabase.Creator.init(application) + viewModel = ViewModelProviders.of(this)[QuasselViewModel::class.java] + viewModel.setBackend(this.backend) - contentMessages = supportFragmentManager.findFragmentById( - R.id.contentMessages - ) as? MessageListFragment - chatListFragment = supportFragmentManager.findFragmentById( - R.id.chatListFragment - ) as? BufferViewConfigFragment - nickListFragment = supportFragmentManager.findFragmentById( - R.id.nickListFragment - ) as? NickListFragment - toolbarFragment = supportFragmentManager.findFragmentById( - R.id.toolbarFragment - ) as? ToolbarFragment + database = QuasselDatabase.Creator.init(application) setSupportActionBar(toolbar) - chatListFragment?.currentBuffer?.value = currentBuffer - nickListFragment?.currentBuffer?.value = currentBuffer - contentMessages?.currentBuffer?.value = currentBuffer - toolbarFragment?.currentBuffer?.value = currentBuffer - - chatListFragment?.clickListeners?.add { - currentBuffer.value = it - println("Changed buffer to $it") - } - - currentBuffer.observe( + viewModel.getBuffer().observe( this, Observer { if (it != null) { drawerLayout.closeDrawer(Gravity.START, true) @@ -145,17 +113,17 @@ class ChatActivity : ServiceBoundActivity() { ) buttonSend.setOnClickListener { - sessionManager { sessionManager -> - currentBuffer { bufferId -> - sessionManager.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo -> - sessionManager.rpcHandler?.sendInput(bufferInfo, input.text.toString()) + viewModel.session { session -> + viewModel.getBuffer().let { bufferId -> + session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo -> + session.rpcHandler?.sendInput(bufferInfo, input.text.toString()) } } } input.text.clear() } - state.observe( + viewModel.connectionState.observe( this, Observer { val status = it ?: ConnectionState.DISCONNECTED @@ -180,7 +148,7 @@ class ChatActivity : ServiceBoundActivity() { } ) - initStatus.observe( + viewModel.initState.observe( this, Observer { val (progress, max) = it ?: 0 to 0 @@ -192,12 +160,12 @@ class ChatActivity : ServiceBoundActivity() { override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) - outState?.putInt("OPEN_BUFFER", currentBuffer.value ?: -1) + outState?.putInt("OPEN_BUFFER", viewModel.getBuffer().value ?: -1) } override fun onRestoreInstanceState(savedInstanceState: Bundle?) { super.onRestoreInstanceState(savedInstanceState) - currentBuffer.value = savedInstanceState?.getInt("OPEN_BUFFER", -1) + viewModel.setBuffer(savedInstanceState?.getInt("OPEN_BUFFER", -1) ?: -1) } override fun onCreateOptionsMenu(menu: Menu?): Boolean { diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt index 02e301411..ab634fac3 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt @@ -16,7 +16,8 @@ import de.kuschku.quasseldroid_ng.util.helper.getOrPut class MessageAdapter( context: Context, - private val markerLine: LiveData<Pair<MsgId, MsgId>?> + private val markerLine: LiveData<Pair<MsgId, MsgId>?>, + var markerLinePosition: Pair<MsgId, MsgId>? = null ) : PagedListAdapter<QuasselDatabase.DatabaseMessage, QuasselMessageViewHolder>( object : DiffCallback<QuasselDatabase.DatabaseMessage>() { override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage, @@ -26,8 +27,8 @@ class MessageAdapter( override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage) = DatabaseMessage.MessageDiffCallback.areContentsTheSame(oldItem, newItem) && - oldItem.messageId != markerLine.value?.first && - oldItem.messageId != markerLine.value?.second + oldItem.messageId != markerLinePosition?.first && + oldItem.messageId != markerLinePosition?.second } ) { private val messageRenderer: MessageRenderer = QuasselMessageRenderer( @@ -42,6 +43,10 @@ class MessageAdapter( private val messageCache = LruCache<Int, FormattedMessage>(512) + fun clearCache() { + messageCache.evictAll() + } + override fun onBindViewHolder(holder: QuasselMessageViewHolder, position: Int) { getItem(position)?.let { messageRenderer.bind( @@ -100,8 +105,10 @@ class MessageAdapter( return viewHolder } - operator fun get(position: Int): QuasselDatabase.DatabaseMessage? { - return getItem(position) + operator fun get(position: Int) = if (position in 0 until itemCount) { + getItem(position) + } else { + null } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt index 6ed8b5ce5..9177811e4 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt @@ -1,8 +1,8 @@ package de.kuschku.quasseldroid_ng.ui.chat import android.arch.lifecycle.LiveData -import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders import android.arch.paging.LivePagedListBuilder import android.arch.paging.PagedList import android.os.Bundle @@ -15,40 +15,31 @@ import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife import de.kuschku.libquassel.protocol.BufferId -import de.kuschku.libquassel.protocol.MsgId import de.kuschku.libquassel.session.Backend import de.kuschku.libquassel.session.SessionManager +import de.kuschku.libquassel.util.compatibility.LoggingHandler +import de.kuschku.libquassel.util.compatibility.log import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase +import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread import de.kuschku.quasseldroid_ng.util.helper.* import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment -import io.reactivex.Observable -import io.reactivex.functions.BiFunction class MessageListFragment : ServiceBoundFragment() { - val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData() - private val buffer = currentBuffer.switchMap { it } + private lateinit var viewModel: QuasselViewModel private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager) - val markerLine = buffer.switchMap { buffer -> - sessionManager.switchMapRx { manager -> - manager.session.switchMap { session -> - val raw = session.bufferSyncer?.liveMarkerLine(buffer) - Observable.zip( - Observable.just(-1).concatWith(raw), - raw, - BiFunction<MsgId, MsgId, Pair<MsgId, MsgId>> { a, b -> a to b } - ) - } - } - } + private var lastBuffer: BufferId? = null private val handler = AndroidHandlerThread("Chat") private lateinit var database: QuasselDatabase + private lateinit var linearLayoutManager: LinearLayoutManager + private lateinit var adapter: MessageAdapter + @BindView(R.id.messages) lateinit var messageList: RecyclerView @@ -58,6 +49,8 @@ class MessageListFragment : ServiceBoundFragment() { override fun onCreate(savedInstanceState: Bundle?) { handler.onCreate() super.onCreate(savedInstanceState) + + viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java] setHasOptionsMenu(true) } @@ -76,10 +69,9 @@ class MessageListFragment : ServiceBoundFragment() { val view = inflater.inflate(R.layout.fragment_messages, container, false) ButterKnife.bind(this, view) - val adapter = MessageAdapter(context!!, markerLine) - + adapter = MessageAdapter(context!!, viewModel.markerLine) messageList.adapter = adapter - val linearLayoutManager = LinearLayoutManager(context) + linearLayoutManager = LinearLayoutManager(context) linearLayoutManager.reverseLayout = true messageList.layoutManager = linearLayoutManager messageList.itemAnimator = null @@ -98,7 +90,7 @@ class MessageListFragment : ServiceBoundFragment() { ) database = QuasselDatabase.Creator.init(context!!.applicationContext) - val data = buffer.switchMap { + val data = viewModel.getBuffer().switchMapNotNull { LivePagedListBuilder( database.message().findByBufferIdPaged(it), PagedList.Config.Builder() @@ -114,7 +106,7 @@ class MessageListFragment : ServiceBoundFragment() { handler.post { val database = QuasselDatabase.Creator.init(this.context!!) - sessionManager.zip(buffer).zip(data).observe( + sessionManager.zip(viewModel.getBuffer()).zip(data).observe( this, Observer { handler.post { val session = it?.first?.first @@ -134,13 +126,19 @@ class MessageListFragment : ServiceBoundFragment() { ) } - markerLine.observe(this, Observer { adapter.notifyDataSetChanged() }) + viewModel.markerLine.observe( + this, Observer { + log(LoggingHandler.LogLevel.ERROR, "DEBUG", "$it") + adapter.markerLinePosition = it + adapter.notifyDataSetChanged() + } + ) data.observe( this, Observer { list -> - val findFirstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition() + val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition() adapter.setList(list) - if (findFirstVisibleItemPosition < 2) { + if (firstVisibleItemPosition < 2) { activity?.runOnUiThread { messageList.smoothScrollToPosition(0) } @@ -155,9 +153,17 @@ class MessageListFragment : ServiceBoundFragment() { } ) - buffer.observe( + viewModel.getBuffer().observe( this, Observer { + val previous = lastBuffer + val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition() + val messageId = adapter[firstVisibleItemPosition]?.messageId + handler.post { + val bufferSyncer = sessionManager.value?.bufferSyncer + if (previous != null && messageId != null) + bufferSyncer?.requestSetMarkerLine(previous, messageId) + // Try loading messages when switching to isEmpty buffer if (it != null) { if (database.message().bufferSize(it) == 0) { @@ -166,6 +172,8 @@ class MessageListFragment : ServiceBoundFragment() { activity?.runOnUiThread { messageList.scrollToPosition(0) } + + lastBuffer = it } } } @@ -179,9 +187,20 @@ class MessageListFragment : ServiceBoundFragment() { return view } + override fun onPause() { + val previous = lastBuffer + val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition() + val messageId = adapter[firstVisibleItemPosition]?.messageId + val bufferSyncer = sessionManager.value?.bufferSyncer + if (previous != null && messageId != null) + bufferSyncer?.requestSetMarkerLine(previous, messageId) + + super.onPause() + } + private fun loadMore() { handler.post { - buffer { bufferId -> + viewModel.getBuffer().let { bufferId -> backend()?.sessionManager()?.backlogManager?.requestBacklog( bufferId = bufferId, last = database.message().findFirstByBufferId(bufferId)?.messageId ?: -1, diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt index be9bd31ff..f532c3802 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt @@ -1,7 +1,6 @@ package de.kuschku.quasseldroid_ng.ui.chat -import android.arch.lifecycle.LiveData -import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.ViewModelProviders import android.os.Bundle import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.LinearLayoutManager @@ -11,24 +10,17 @@ import android.view.View import android.view.ViewGroup import butterknife.BindView import butterknife.ButterKnife -import de.kuschku.libquassel.protocol.BufferId -import de.kuschku.libquassel.protocol.Buffer_Type -import de.kuschku.libquassel.session.Backend -import de.kuschku.libquassel.session.SessionManager -import de.kuschku.libquassel.util.hasFlag import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.ui.settings.data.RenderingSettings +import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread import de.kuschku.quasseldroid_ng.util.helper.map -import de.kuschku.quasseldroid_ng.util.helper.switchMap -import de.kuschku.quasseldroid_ng.util.helper.switchMapRx import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment -import io.reactivex.Observable -import io.reactivex.Observable.zip -import io.reactivex.functions.BiFunction class NickListFragment : ServiceBoundFragment() { + private lateinit var viewModel: QuasselViewModel + private val handlerThread = AndroidHandlerThread("NickList") @BindView(R.id.nickList) @@ -42,64 +34,12 @@ class NickListFragment : ServiceBoundFragment() { timeFormat = "" ) - val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData() - val buffer = currentBuffer.switchMap { it } - - private val sessionManager: LiveData<SessionManager?> - = backend.map(Backend::sessionManager) - - private val ircChannel: LiveData<List<NickListAdapter.IrcUserItem>?> - = sessionManager.switchMapRx(SessionManager::session).switchMap { session -> - buffer.switchMapRx { - val bufferSyncer = session.bufferSyncer - val bufferInfo = bufferSyncer?.bufferInfo(it) - if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { - val network = session.networks[bufferInfo.networkId] - val ircChannel = network?.ircChannel(bufferInfo.bufferName) - if (ircChannel != null) { - Observable.combineLatest( - ircChannel.ircUsers().map { user -> - zip( - user.live_realName, user.live_away, - BiFunction<String, Boolean, Pair<String, Boolean>> { a, b -> Pair(a, b) } - ).map { (realName, away) -> - val userModes = ircChannel.userModes(user) - val prefixModes = network.prefixModes() - - val lowestMode = userModes.mapNotNull { - prefixModes.indexOf(it) - }.min() ?: prefixModes.size - - NickListAdapter.IrcUserItem( - user.nick(), - network.modesToPrefixes(userModes), - lowestMode, - ircFormatDeserializer?.formatString( - realName, renderingSettings.colorizeMirc - ) ?: realName, - away, - network.support("CASEMAPPING") - ) - } - }, { array: Array<Any> -> - array.toList() as List<NickListAdapter.IrcUserItem> - } - ) - } else { - Observable.just(emptyList()) - } - } else { - Observable.just(emptyList()) - } - } - } - - private val nicks: LiveData<List<NickListAdapter.IrcUserItem>?> = ircChannel - override fun onCreate(savedInstanceState: Bundle?) { handlerThread.onCreate() super.onCreate(savedInstanceState) + viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java] + if (ircFormatDeserializer == null) { ircFormatDeserializer = IrcFormatDeserializer(context!!) } @@ -112,7 +52,15 @@ class NickListFragment : ServiceBoundFragment() { nickList.adapter = NickListAdapter( this, - nicks, + viewModel.nickData.map { + it.map { + it.copy( + realname = ircFormatDeserializer?.formatString( + it.realname.toString(), renderingSettings.colorizeMirc + ) ?: it.realname + ) + } + }, handlerThread::post, activity!!::runOnUiThread, clickListener @@ -129,11 +77,7 @@ class NickListFragment : ServiceBoundFragment() { super.onDestroy() } - val clickListeners = mutableListOf<(String) -> Unit>() - private val clickListener: ((String) -> Unit)? = { - for (clickListener in clickListeners) { - clickListener.invoke(it) - } + // TODO } } \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt index 48d1f87ec..6b237e590 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt @@ -285,6 +285,7 @@ class QuasselMessageRenderer( text.setSpan(URLSpan(result.value), result.range.start, result.range.endInclusive, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } */ + return text } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt index 0e3176638..bd82faddf 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt @@ -1,8 +1,7 @@ package de.kuschku.quasseldroid_ng.ui.chat -import android.arch.lifecycle.LiveData -import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -10,21 +9,19 @@ import android.view.ViewGroup import android.widget.TextView import butterknife.BindView import butterknife.ButterKnife -import de.kuschku.libquassel.protocol.BufferId import de.kuschku.libquassel.protocol.Buffer_Type import de.kuschku.libquassel.quassel.BufferInfo import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork -import de.kuschku.libquassel.session.Backend -import de.kuschku.libquassel.session.SessionManager import de.kuschku.libquassel.util.hasFlag import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.ui.settings.data.DisplaySettings import de.kuschku.quasseldroid_ng.ui.settings.data.RenderingSettings -import de.kuschku.quasseldroid_ng.util.helper.* +import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel +import de.kuschku.quasseldroid_ng.util.helper.visibleIf +import de.kuschku.quasseldroid_ng.util.helper.zip import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment import de.kuschku.quasseldroid_ng.util.ui.SpanFormatter -import io.reactivex.Observable class ToolbarFragment : ServiceBoundFragment() { @BindView(R.id.toolbar_title) @@ -33,14 +30,7 @@ class ToolbarFragment : ServiceBoundFragment() { @BindView(R.id.toolbar_subtitle) lateinit var toolbarSubtitle: TextView - val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData() - val buffer = currentBuffer.switchMap { it } - - private val sessionManager: LiveData<SessionManager?> - = backend.map(Backend::sessionManager) - - private val lag: LiveData<Long?> - = sessionManager.switchMapRx { it.session.switchMap { it.lag } } + private lateinit var viewModel: QuasselViewModel private val displaySettings = DisplaySettings( showLag = true @@ -54,91 +44,6 @@ class ToolbarFragment : ServiceBoundFragment() { timeFormat = "" ) - private val bufferData: LiveData<BufferData?> = sessionManager.switchMap { manager -> - buffer.switchMapRx { id -> - manager.session.switchMap { - val bufferSyncer = it.bufferSyncer - if (bufferSyncer != null) { - bufferSyncer.liveBufferInfos().switchMap { - val info = bufferSyncer.bufferInfo(id) - val network = manager.networks[info?.networkId] - if (info == null) { - Observable.just( - BufferData( - description = "Info was null" - ) - ) - } else if (network == null) { - Observable.just( - BufferData( - description = "Network was null" - ) - ) - } else { - when (info.type.toInt()) { - BufferInfo.Type.QueryBuffer.toInt() -> { - network.liveIrcUser(info.bufferName).switchMap { user -> - user.live_realName.map { realName -> - BufferData( - info = info, - network = network.networkInfo(), - description = ircFormatDeserializer?.formatString( - realName, renderingSettings.colorizeMirc - ) ?: realName - ) - } - } - } - BufferInfo.Type.ChannelBuffer.toInt() -> { - network.liveIrcChannel( - info.bufferName - ).switchMap { channel -> - channel.live_topic.map { topic -> - BufferData( - info = info, - network = network.networkInfo(), - description = ircFormatDeserializer?.formatString( - topic, renderingSettings.colorizeMirc - ) ?: topic - ) - } - } - } - BufferInfo.Type.StatusBuffer.toInt() -> { - network.liveConnectionState.map { - BufferData( - info = info, - network = network.networkInfo() - ) - } - } - else -> Observable.just( - BufferData( - description = "type is unknown: ${info.type.toInt()}" - ) - ) - } - } - } - } else { - Observable.just( - BufferData( - description = "buffersyncer was null" - ) - ) - } - } - } - } - - private val isSecure: LiveData<Boolean?> = sessionManager.switchMapRx( - SessionManager::session - ).switchMapRx { session -> - session.state.map { state -> - session.sslSession != null - } - } - var title: CharSequence? get() = toolbarTitle.text set(value) { @@ -157,6 +62,9 @@ class ToolbarFragment : ServiceBoundFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java] + if (ircFormatDeserializer == null) { ircFormatDeserializer = IrcFormatDeserializer(context!!) } @@ -168,7 +76,7 @@ class ToolbarFragment : ServiceBoundFragment() { val view = inflater.inflate(R.layout.fragment_toolbar, container, false) ButterKnife.bind(this, view) - bufferData.zip(isSecure, lag).observe( + viewModel.bufferData.zip(viewModel.isSecure, viewModel.lag).observe( this, Observer { if (it != null) { val (data, isSecure, lag) = it @@ -179,13 +87,17 @@ class ToolbarFragment : ServiceBoundFragment() { } if (lag == 0L || !displaySettings.showLag) { - this.subtitle = data?.description + this.subtitle = colorizeDescription(data?.description) } else { - val description = data?.description + val description = colorizeDescription(data?.description) if (description.isNullOrBlank()) { this.subtitle = "Lag: ${lag}ms" } else { - this.subtitle = SpanFormatter.format("Lag: %dms | %s", lag, description) + this.subtitle = SpanFormatter.format( + "Lag: %dms | %s", + lag, + colorizeDescription(data?.description) + ) } } } @@ -195,10 +107,14 @@ class ToolbarFragment : ServiceBoundFragment() { return view } + private fun colorizeDescription(description: String?) + = ircFormatDeserializer?.formatString(description, renderingSettings.colorizeMirc) + ?: description + data class BufferData( val info: BufferInfo? = null, val network: INetwork.NetworkInfo? = null, - val description: CharSequence? = null + val description: String? = null ) } \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt new file mode 100644 index 000000000..e22fc8877 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt @@ -0,0 +1,325 @@ +package de.kuschku.quasseldroid_ng.ui.viewmodel + +import android.arch.lifecycle.LiveData +import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.ViewModel +import de.kuschku.libquassel.protocol.BufferId +import de.kuschku.libquassel.protocol.Buffer_Activity +import de.kuschku.libquassel.protocol.Buffer_Type +import de.kuschku.libquassel.protocol.Message +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.session.Backend +import de.kuschku.libquassel.session.ISession +import de.kuschku.libquassel.session.SessionManager +import de.kuschku.libquassel.util.and +import de.kuschku.libquassel.util.hasFlag +import de.kuschku.quasseldroid_ng.ui.chat.BufferListAdapter +import de.kuschku.quasseldroid_ng.ui.chat.NickListAdapter +import de.kuschku.quasseldroid_ng.ui.chat.ToolbarFragment +import de.kuschku.quasseldroid_ng.util.helper.* +import io.reactivex.Observable +import io.reactivex.functions.BiFunction +import java.util.concurrent.TimeUnit + +class QuasselViewModel : ViewModel() { + private val backendWrapper = MutableLiveData<LiveData<Backend?>>() + fun setBackend(backendWrapper: LiveData<Backend?>) { + this.backendWrapper.value = backendWrapper + } + + private val buffer = MutableLiveData<BufferId>() + fun getBuffer(): LiveData<BufferId> = buffer + fun setBuffer(buffer: BufferId) { + this.buffer.value = buffer + } + + private val bufferViewConfig = MutableLiveData<BufferViewConfig?>() + fun getBufferViewConfig(): LiveData<BufferViewConfig?> = bufferViewConfig + fun setBufferViewConfig(bufferViewConfig: BufferViewConfig?) { + this.bufferViewConfig.value = bufferViewConfig + } + + val backend = backendWrapper.switchMap { it } + val sessionManager = backend.map { it.sessionManager() } + val session = sessionManager.switchMapRx { it.session } + + val connectionState = sessionManager.switchMapRx { it.state } + val initState = sessionManager.switchMapRx { it.initStatus } + + private val bufferViewManager = session.map(ISession::bufferViewManager) + + private var lastMarkerLine = -1 + /** + * An observable of the changes of the markerline, as pairs of `(old, new)` + */ + val markerLine = session.switchMap { currentSession -> + buffer.switchMapRx { currentBuffer -> + // Get a stream of the latest marker line + val raw = currentSession.bufferSyncer?.liveMarkerLine(currentBuffer) + + // Turn it into a pair of changes + val changes = raw?.map { + val previous = lastMarkerLine + if (it != lastMarkerLine) + lastMarkerLine = it + previous to it + } + + // Only return when there was an actual change + val distinct = changes?.filter { + it.first != it.second + } + + distinct + } + } + + val lag: LiveData<Long?> = sessionManager.switchMapRx { it.session.switchMap { it.lag } } + + val isSecure: LiveData<Boolean?> = sessionManager.switchMapRx(SessionManager::session) + .switchMapRx { session -> + session.state.map { _ -> + session.sslSession != null + } + } + + val bufferData = session.zip(buffer).switchMapRx { (session, id) -> + val bufferSyncer = session?.bufferSyncer + if (bufferSyncer != null) { + bufferSyncer.liveBufferInfos().switchMap { + val info = bufferSyncer.bufferInfo(id) + val network = session.networks[info?.networkId] + if (info == null) { + Observable.just( + ToolbarFragment.BufferData( + description = "Info was null" + ) + ) + } else if (network == null) { + Observable.just( + ToolbarFragment.BufferData( + description = "Network was null" + ) + ) + } else { + when (info.type.toInt()) { + BufferInfo.Type.QueryBuffer.toInt() -> { + network.liveIrcUser(info.bufferName).switchMap { user -> + user.live_realName.map { realName -> + ToolbarFragment.BufferData( + info = info, + network = network.networkInfo(), + description = realName + ) + } + } + } + BufferInfo.Type.ChannelBuffer.toInt() -> { + network.liveIrcChannel( + info.bufferName + ).switchMap { channel -> + channel.live_topic.map { topic -> + ToolbarFragment.BufferData( + info = info, + network = network.networkInfo(), + description = topic + ) + } + } + } + BufferInfo.Type.StatusBuffer.toInt() -> { + network.liveConnectionState.map { + ToolbarFragment.BufferData( + info = info, + network = network.networkInfo() + ) + } + } + else -> Observable.just( + ToolbarFragment.BufferData( + description = "type is unknown: ${info.type.toInt()}" + ) + ) + } + } + } + } else { + Observable.just( + ToolbarFragment.BufferData( + description = "buffersyncer was null" + ) + ) + } + } + + val nickData = session.zip(buffer).switchMapRx { (session, buffer) -> + val bufferSyncer = session?.bufferSyncer + val bufferInfo = bufferSyncer?.bufferInfo(buffer) + if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { + val network = session.networks[bufferInfo.networkId] + val ircChannel = network?.ircChannel(bufferInfo.bufferName) + if (ircChannel != null) { + Observable.combineLatest( + ircChannel.ircUsers().map { user -> + Observable.zip( + user.live_realName, user.live_away, + BiFunction<String, Boolean, Pair<String, Boolean>> { a, b -> Pair(a, b) } + ).map { (realName, away) -> + val userModes = ircChannel.userModes(user) + val prefixModes = network.prefixModes() + + val lowestMode = userModes.mapNotNull { + prefixModes.indexOf(it) + }.min() ?: prefixModes.size + + NickListAdapter.IrcUserItem( + user.nick(), + network.modesToPrefixes(userModes), + lowestMode, + realName, + away, + network.support("CASEMAPPING") + ) + } + }, { array: Array<Any> -> + array.toList() as List<NickListAdapter.IrcUserItem> + } + ) + } else { + Observable.just(emptyList()) + } + } else { + Observable.just(emptyList()) + } + } + + val bufferViewConfigs = bufferViewManager.switchMapRx { manager -> + manager.liveBufferViewConfigs().map { ids -> + ids.mapNotNull { id -> + manager.bufferViewConfig(id) + }.sortedWith( + Comparator { a, b -> + (a?.bufferViewName() ?: "").compareTo((b?.bufferViewName() ?: ""), true) + } + ) + } + }.or(emptyList()) + + val bufferList = session.zip(bufferViewConfig).switchMapRx { (session, config) -> + val bufferSyncer = session?.bufferSyncer + if (bufferSyncer != null && config != null) { + config.live_config.debounce(16, TimeUnit.MILLISECONDS).switchMap { currentConfig -> + config.live_buffers.switchMap { ids -> + bufferSyncer.liveBufferInfos().switchMap { + Observable.combineLatest( + ids.mapNotNull { id -> + bufferSyncer.bufferInfo(id) + }.filter { + currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId + }.filter { + (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() || + it.type.hasFlag(Buffer_Type.StatusBuffer) + }.mapNotNull { + val network = session.networks[it.networkId] + if (network == null) { + null + } else { + it to network + } + }.map { (info, network) -> + bufferSyncer.liveActivity(info.bufferId).switchMap { activity -> + bufferSyncer.liveHighlightCount(info.bufferId).map { highlights -> + when { + highlights > 0 -> Buffer_Activity.Highlight + activity.hasFlag(Message.MessageType.Plain) || + activity.hasFlag(Message.MessageType.Notice) || + activity.hasFlag(Message.MessageType.Action) -> Buffer_Activity.NewMessage + activity.isNotEmpty() -> Buffer_Activity.OtherActivity + else -> Buffer_Activity.NoActivity + } + } + }.switchMap { activity -> + when (info.type.toInt()) { + BufferInfo.Type.QueryBuffer.toInt() -> { + network.liveIrcUser(info.bufferName).switchMap { user -> + user.live_away.switchMap { away -> + user.live_realName.map { realName -> + BufferListAdapter.BufferProps( + info = info, + network = network.networkInfo(), + bufferStatus = when { + user == IrcUser.NULL -> BufferListAdapter.BufferStatus.OFFLINE + away -> BufferListAdapter.BufferStatus.AWAY + else -> BufferListAdapter.BufferStatus.ONLINE + }, + description = realName, + activity = activity + ) + } + } + } + } + BufferInfo.Type.ChannelBuffer.toInt() -> { + network.liveIrcChannel( + info.bufferName + ).switchMap { channel -> + channel.live_topic.map { topic -> + BufferListAdapter.BufferProps( + info = info, + network = network.networkInfo(), + bufferStatus = when (channel) { + IrcChannel.NULL -> BufferListAdapter.BufferStatus.OFFLINE + else -> BufferListAdapter.BufferStatus.ONLINE + }, + description = topic, + activity = activity + ) + } + } + } + BufferInfo.Type.StatusBuffer.toInt() -> { + network.liveConnectionState.map { + BufferListAdapter.BufferProps( + info = info, + network = network.networkInfo(), + bufferStatus = BufferListAdapter.BufferStatus.OFFLINE, + description = "", + activity = activity + ) + } + } + else -> Observable.just( + BufferListAdapter.BufferProps( + info = info, + network = network.networkInfo(), + bufferStatus = BufferListAdapter.BufferStatus.OFFLINE, + description = "", + activity = activity + ) + ) + } + } + }, { array: Array<Any> -> + array.toList() as List<BufferListAdapter.BufferProps> + } + ).map { list -> + list.filter { + config.minimumActivity().value <= it.activity.bit || + it.info.type.hasFlag(Buffer_Type.StatusBuffer) + }.filter { + (!config.hideInactiveBuffers()) || + it.bufferStatus != BufferListAdapter.BufferStatus.OFFLINE || + it.info.type.hasFlag(Buffer_Type.StatusBuffer) + } + } + } + } + } + } else { + Observable.just(emptyList()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt index a72e74e0e..5c81c9898 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt @@ -32,6 +32,33 @@ inline fun <X, Y> LiveData<X?>.switchMap( return result } +@MainThread +inline fun <X, Y> LiveData<X>.switchMapNotNull( + crossinline func: (X) -> LiveData<Y>? +): LiveData<Y> { + val result = MediatorLiveData<Y>() + result.addSource( + this, object : Observer<X> { + internal var mSource: LiveData<Y>? = null + + override fun onChanged(x: X?) { + val newLiveData = if (x == null) null else func(x) + if (mSource === newLiveData) { + return + } + mSource?.let(result::removeSource) + mSource = newLiveData + if (newLiveData != null) { + result.addSource(newLiveData) { y -> result.value = y } + } else { + result.value = null + } + } + } + ) + return result +} + @MainThread inline fun <X, Y> LiveData<X?>.switchMapRx( strategy: BackpressureStrategy, @@ -66,7 +93,7 @@ inline fun <X, Y> LiveData<out X?>.switchMapRx( ): LiveData<Y?> = switchMapRx(BackpressureStrategy.LATEST, func) @MainThread -inline fun <X, Y> LiveData<X?>.map( +inline fun <X, Y> LiveData<out X?>.map( crossinline func: (X) -> Y? ): LiveData<Y?> { val result = MediatorLiveData<Y?>() @@ -118,4 +145,6 @@ inline fun <T> LiveData<T>.toObservable(lifecycleOwner: LifecycleOwner): Observa inline operator fun <T> LiveData<T>.invoke() = value -inline operator fun <T, U> LiveData<T?>.invoke(f: (T) -> U?) = value?.let(f) \ No newline at end of file +inline operator fun <T, U> LiveData<T?>.invoke(f: (T) -> U?) = value?.let(f) + +inline fun <T, U> LiveData<T>.let(f: (T) -> U?) = value?.let(f) \ No newline at end of file diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewManager.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewManager.kt index 0de820eb5..49f16ebbb 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewManager.kt @@ -3,6 +3,7 @@ package de.kuschku.libquassel.quassel.syncables import de.kuschku.libquassel.protocol.* import de.kuschku.libquassel.quassel.syncables.interfaces.IBufferViewManager import de.kuschku.libquassel.session.SignalProxy +import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject class BufferViewManager constructor( @@ -24,6 +25,8 @@ class BufferViewManager constructor( fun bufferViewConfigs() = _bufferViewConfigs.values + fun liveBufferViewConfigs(): Observable<Set<Int>> = live_bufferViewConfigs + override fun initSetBufferViewIds(bufferViewIds: QVariantList) { bufferViewIds .mapNotNull { it.value<Int>() } @@ -57,6 +60,6 @@ class BufferViewManager constructor( private val _bufferViewConfigs: MutableMap<Int, BufferViewConfig> = mutableMapOf() - val live_bufferViewConfigs: BehaviorSubject<Set<Int>> + private val live_bufferViewConfigs: BehaviorSubject<Set<Int>> = BehaviorSubject.createDefault<Set<Int>>(emptySet()) } diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt index 7069bc149..c50dd6400 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt @@ -250,7 +250,7 @@ class IrcUser( } override fun quit() { - for (channel in _channels) { + for (channel in _channels.toList()) { channel.part(this) } _channels.clear() -- GitLab