Fixes archived chats screen, significantly improves its performance

parent 41eef1b3
......@@ -209,7 +209,7 @@ class QueryCreateFragment : ServiceBoundFragment() {
it.liveIrcUsers()
}.mapOrElse(emptyList()).safeSwitchMap {
combineLatest<IrcUserItem>(
it.map<IrcUser, Observable<IrcUserItem>?> {
it.mapNotNull<IrcUser, Observable<IrcUserItem>> {
it.updates().map { user ->
IrcUserItem(
user.network().networkId(),
......
......@@ -37,7 +37,6 @@ import de.kuschku.quasseldroid.persistence.db.QuasselDatabase
import de.kuschku.quasseldroid.persistence.models.Filtered
import de.kuschku.quasseldroid.settings.MessageSettings
import de.kuschku.quasseldroid.ui.chat.ChatActivity
import de.kuschku.quasseldroid.ui.chat.buffers.BufferListAdapter
import de.kuschku.quasseldroid.util.helper.toLiveData
import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
import de.kuschku.quasseldroid.util.ui.presenter.BufferContextPresenter
......@@ -47,11 +46,8 @@ import de.kuschku.quasseldroid.viewmodel.helper.ArchiveViewModelHelper
import javax.inject.Inject
class ArchiveFragment : ServiceBoundFragment() {
@BindView(R.id.list_temporary)
lateinit var listTemporary: RecyclerView
@BindView(R.id.list_permanently)
lateinit var listPermanently: RecyclerView
@BindView(R.id.list)
lateinit var list: RecyclerView
@Inject
lateinit var modelHelper: ArchiveViewModelHelper
......@@ -68,9 +64,7 @@ class ArchiveFragment : ServiceBoundFragment() {
@Inject
lateinit var messageSettings: MessageSettings
private lateinit var listTemporaryAdapter: BufferListAdapter
private lateinit var listPermanentlyAdapter: BufferListAdapter
private lateinit var listAdapter: ArchiveListAdapter
private var actionMode: ActionMode? = null
......@@ -112,8 +106,7 @@ class ArchiveFragment : ServiceBoundFragment() {
override fun onDestroyActionMode(mode: ActionMode?) {
actionMode = null
listTemporaryAdapter.unselectAll()
listPermanentlyAdapter.unselectAll()
listAdapter.unselectAll()
}
}
......@@ -125,27 +118,16 @@ class ArchiveFragment : ServiceBoundFragment() {
val chatlistId = arguments?.getInt("chatlist_id", -1) ?: -1
modelHelper.archive.bufferViewConfigId.onNext(chatlistId)
listTemporaryAdapter = BufferListAdapter(
listAdapter = ArchiveListAdapter(
messageSettings,
modelHelper.archive.selectedBufferId,
modelHelper.archive.temporarilyExpandedNetworks
)
listTemporaryAdapter.setOnClickListener(::clickListener)
listTemporaryAdapter.setOnLongClickListener(::longClickListener)
listTemporary.adapter = listTemporaryAdapter
listTemporary.layoutManager = LinearLayoutManager(listTemporary.context)
listTemporary.itemAnimator = DefaultItemAnimator()
listPermanentlyAdapter = BufferListAdapter(
messageSettings,
modelHelper.archive.selectedBufferId,
modelHelper.archive.permanentlyExpandedNetworks
)
listPermanentlyAdapter.setOnClickListener(::clickListener)
listPermanentlyAdapter.setOnLongClickListener(::longClickListener)
listPermanently.adapter = listPermanentlyAdapter
listPermanently.layoutManager = LinearLayoutManager(listPermanently.context)
listPermanently.itemAnimator = DefaultItemAnimator()
listAdapter.setOnClickListener(::clickListener)
listAdapter.setOnLongClickListener(::longClickListener)
list.adapter = listAdapter
list.layoutManager = LinearLayoutManager(list.context)
list.itemAnimator = DefaultItemAnimator()
val filtered = combineLatest(
database.filtered().listenRx(accountId).toObservable().map {
......@@ -154,20 +136,36 @@ class ArchiveFragment : ServiceBoundFragment() {
accountDatabase.accounts().listenDefaultFiltered(accountId, 0).toObservable()
)
fun processArchiveBufferList(bufferListType: BufferHiddenState, showHandle: Boolean) =
modelHelper.processArchiveBufferList(bufferListType, showHandle, filtered).map { buffers ->
bufferPresenter.render(buffers)
combineLatest(
modelHelper.processArchiveBufferList(BufferHiddenState.HIDDEN_TEMPORARY, false, filtered),
modelHelper.processArchiveBufferList(BufferHiddenState.HIDDEN_PERMANENT, false, filtered)
).map { (temporary, permanently) ->
listOf(ArchiveListItem.Header(
title = getString(R.string.label_temporarily_archived),
content = getString(R.string.label_temporarily_archived_long)
)) + temporary.map {
ArchiveListItem.Buffer(it.copy(
props = bufferPresenter.render(it.props)
))
}.ifEmpty {
listOf(ArchiveListItem.Placeholder(
content = getString(R.string.label_temporarily_archived_empty)
))
} + listOf(ArchiveListItem.Header(
title = getString(R.string.label_permanently_archived),
content = getString(R.string.label_permanently_archived_long)
)) + permanently.map {
ArchiveListItem.Buffer(it.copy(
props = bufferPresenter.render(it.props)
))
}.ifEmpty {
listOf(ArchiveListItem.Placeholder(
content = getString(R.string.label_permanently_archived_empty)
))
}
processArchiveBufferList(BufferHiddenState.HIDDEN_TEMPORARY, false)
.toLiveData().observe(this, Observer { processedList ->
listTemporaryAdapter.submitList(processedList)
})
processArchiveBufferList(BufferHiddenState.HIDDEN_PERMANENT, false)
.toLiveData().observe(this, Observer { processedList ->
listPermanentlyAdapter.submitList(processedList)
})
}.toLiveData().observe(this, Observer { processedList ->
listAdapter.submitList(processedList)
})
modelHelper.selectedBuffer.toLiveData().observe(this, Observer { buffer ->
actionMode?.let {
......
package de.kuschku.quasseldroid.ui.chat.archive
import de.kuschku.quasseldroid.viewmodel.data.BufferListItem
sealed class ArchiveListItem(val type: Type) {
data class Header(
val title: String,
val content: String
) : ArchiveListItem(Type.HEADER)
data class Placeholder(
val content: String
) : ArchiveListItem(Type.PLACEHOLDER)
data class Buffer(
val item: BufferListItem
) : ArchiveListItem(Type.BUFFER)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ArchiveListItem) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
enum class Type(val value: UByte) {
HEADER(0u),
PLACEHOLDER(1u),
BUFFER(2u);
companion object {
private val map = values().associateBy { it.value }
fun of(value: UByte) = map[value]
}
}
}
......@@ -18,71 +18,10 @@
-->
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<de.kuschku.quasseldroid.util.ui.fastscroll.views.FastScrollRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout style="@style/Widget.CoreSettings.PrimaryItemGroupHeader">
<androidx.appcompat.widget.AppCompatImageView
style="@style/Widget.CoreSettings.PrimaryItemIcon"
app:srcCompat="@drawable/ic_clock" />
<TextView
style="@style/Widget.CoreSettings.PrimaryItemSwitch"
android:text="@string/label_temporarily_archived" />
</LinearLayout>
<TextView
style="@style/Widget.CoreSettings.TextView"
android:layout_marginStart="72dp"
android:layout_marginLeft="72dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/label_temporarily_archived_long" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_temporary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="56dp"
android:layout_marginLeft="56dp"
tools:listitem="@layout/widget_buffer" />
<LinearLayout style="@style/Widget.CoreSettings.PrimaryItemGroupHeader">
<androidx.appcompat.widget.AppCompatImageView
style="@style/Widget.CoreSettings.PrimaryItemIcon"
app:srcCompat="@drawable/ic_eye_off" />
<TextView
style="@style/Widget.CoreSettings.PrimaryItemSwitch"
android:text="@string/label_permanently_archived" />
</LinearLayout>
<TextView
style="@style/Widget.CoreSettings.TextView"
android:layout_marginStart="72dp"
android:layout_marginLeft="72dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/label_permanently_archived_long" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_permanently"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="56dp"
android:layout_marginLeft="56dp"
tools:listitem="@layout/widget_buffer" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
android:id="@+id/list"
style="@style/Widget.FastScroller"
tools:listitem="@layout/widget_buffer" />
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content"
style="@style/Widget.CoreSettings.TextView"
android:paddingStart="72dp"
android:paddingLeft="72dp"
android:paddingEnd="16dp"
android:paddingRight="16dp"
android:textStyle="italic"
tools:text="@string/label_temporarily_archived_empty" />
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout style="@style/Widget.CoreSettings.PrimaryItemGroupHeader">
<androidx.appcompat.widget.AppCompatImageView
style="@style/Widget.CoreSettings.PrimaryItemIcon"
app:srcCompat="@drawable/ic_clock" />
<TextView
android:id="@+id/title"
style="@style/Widget.CoreSettings.PrimaryItemSwitch"
tools:text="@string/label_temporarily_archived" />
</LinearLayout>
<TextView
android:id="@+id/content"
style="@style/Widget.CoreSettings.TextView"
android:layout_marginStart="72dp"
android:layout_marginLeft="72dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
tools:text="@string/label_temporarily_archived_long" />
</LinearLayout>
......@@ -104,6 +104,7 @@
<string name="label_password_old">Old Password</string>
<string name="label_password_repeat">Repeat Password</string>
<string name="label_permanently_archived">Permanently Archived</string>
<string name="label_permanently_archived_empty">You have no permanently archived chats</string>
<string name="label_permanently_archived_long">Permanently archived chats stay hidden until you manually choose to un-archive them.</string>
<string name="label_placeholder_message">Write a message…</string>
<string name="label_placeholder_topic">Describe the channel topic…</string>
......@@ -134,6 +135,7 @@
<string name="label_sort">Sort</string>
<string name="label_source">Source</string>
<string name="label_temporarily_archived">Temporarily Archived</string>
<string name="label_temporarily_archived_empty">You have no temporarily archived chats</string>
<string name="label_temporarily_archived_long">Chats which are temporarily archived will be shown again once they get a new message.</string>
<string name="label_topic">Channel Topic</string>
<string name="label_translators">Translators</string>
......
......@@ -125,8 +125,11 @@ inline fun <reified A, B, C, D, E, F> combineLatest(
Tuple6(it[0], it[1], it[2], it[3], it[4], it[5]) as Tuple6<A, B, C, D, E, F>
}
inline fun <reified T> combineLatest(sources: Iterable<ObservableSource<out T>?>) =
Observable.combineLatest(sources) { t -> t.toList() as List<T> }
inline fun <reified T> combineLatest(
sources: Collection<ObservableSource<out T>>
): Observable<List<T>> =
if (sources.isEmpty()) Observable.just(emptyList())
else Observable.combineLatest(sources) { t -> t.toList() as List<T> }
inline operator fun <T, U> Observable<T>.invoke(f: (T) -> U?) =
blockingLatest().firstOrNull()?.let(f)
......
......@@ -19,13 +19,13 @@
package de.kuschku.quasseldroid.viewmodel.data
enum class BufferStatus(val value: Short) {
ONLINE(0),
AWAY(1),
OFFLINE(2);
enum class BufferStatus(val value: UByte) {
ONLINE(0u),
AWAY(1u),
OFFLINE(2u);
companion object {
private val map = values().associateBy { it.value }
fun of(value: Short) = map[value]
fun of(value: UByte) = map[value]
}
}
......@@ -45,7 +45,12 @@ open class ArchiveViewModelHelper @Inject constructor(
showHandle: Boolean,
filtered: Observable<Pair<Map<BufferId, Int>, Int>>
) = filterBufferList(
processRawBufferList(bufferViewConfig, filtered, bufferListType = bufferListType),
processRawBufferList(
bufferViewConfig,
filtered,
bufferListType = bufferListType,
showAllNetworks = false
),
when (bufferListType) {
BufferHiddenState.VISIBLE -> archive.visibleExpandedNetworks
BufferHiddenState.HIDDEN_TEMPORARY -> archive.temporarilyExpandedNetworks
......
......@@ -137,7 +137,7 @@ open class ChatViewModelHelper @Inject constructor(
network?.liveIrcChannel(bufferInfo.bufferName)?.switchMapNullable(IrcChannel.NULL) { ircChannel ->
ircChannel?.liveIrcUsers()?.safeSwitchMap { users ->
combineLatest<IrcUserItem>(
users.map<IrcUser, Observable<IrcUserItem>?> {
users.mapNotNull<IrcUser, Observable<IrcUserItem>> {
it.updates().map { user ->
val userModes = ircChannel.userModes(user)
val prefixModes = network.prefixModes()
......@@ -158,7 +158,7 @@ open class ChatViewModelHelper @Inject constructor(
network.support("CASEMAPPING")
)
}
}
}.toList()
)
} ?: Observable.just(emptyList())
} ?: Observable.just(emptyList())
......
......@@ -137,8 +137,7 @@ open class QuasselViewModelHelper @Inject constructor(
filtered: Map<BufferId, Int>,
defaultFiltered: Int,
bufferSearch: String = ""
) =
ids.asSequence().mapNotNull { id ->
): Sequence<Observable<BufferProps>> = ids.asSequence().mapNotNull { id ->
bufferSyncer.bufferInfo(id)
}.filter {
bufferSearch.isBlank() ||
......@@ -156,7 +155,7 @@ open class QuasselViewModelHelper @Inject constructor(
} else {
it to network
}
}.map<Pair<BufferInfo, Network>, Observable<BufferProps>?> { (info, network) ->
}.mapNotNull<Pair<BufferInfo, Network>, Observable<BufferProps>> { (info, network) ->
bufferSyncer.liveActivity(info.bufferId).safeSwitchMap { activity ->
bufferSyncer.liveHighlightCount(info.bufferId).map { highlights ->
Pair(activity, highlights)
......@@ -284,7 +283,8 @@ open class QuasselViewModelHelper @Inject constructor(
bufferViewConfig: Observable<Optional<BufferViewConfig>>,
filteredTypes: Observable<Pair<Map<BufferId, Int>, Int>>,
bufferSearch: Observable<String> = Observable.just(""),
bufferListType: BufferHiddenState = BufferHiddenState.VISIBLE
bufferListType: BufferHiddenState = BufferHiddenState.VISIBLE,
showAllNetworks: Boolean = true
): Observable<Pair<BufferViewConfig?, List<BufferProps>>> =
combineLatest(connectedSession, bufferViewConfig, filteredTypes, bufferSearch)
.safeSwitchMap { (sessionOptional, configOptional, rawFiltered, bufferSearch) ->
......@@ -316,18 +316,28 @@ open class QuasselViewModelHelper @Inject constructor(
)
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 ->
list: Collection<BufferId>
): Sequence<Observable<BufferProps>> {
val buffers = list.asSequence().mapNotNull { id ->
bufferSyncer.bufferInfo(id)
}.filter {
}
val totalNetworks =
if (showAllNetworks) networks.keys
else buffers.filter {
!it.type.hasFlag(Buffer_Type.StatusBuffer)
}.map {
it.networkId
}.toList()
val availableNetworks = buffers.filter {
it.type.hasFlag(Buffer_Type.StatusBuffer)
}.map {
it.networkId
}
}.toList()
val wantedNetworks = if (!currentConfig.networkId().isValidId()) totalNetworks
else listOf(currentConfig.networkId())
val missingNetworks = wantedNetworks - availableNetworks
......@@ -339,7 +349,7 @@ open class QuasselViewModelHelper @Inject constructor(
networks[it]
}.filter {
!config.hideInactiveNetworks() || it.isConnected()
}.map<Network, Observable<BufferProps>?> { network ->
}.mapNotNull<Network, Observable<BufferProps>> { network ->
network.liveNetworkInfo().safeSwitchMap { networkInfo ->
network.liveConnectionState().map { connectionState ->
BufferProps(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment