Room summary paged initial commit
This commit is contained in:
parent
c0913711d6
commit
cf868f885f
@ -33,3 +33,4 @@ sealed class QueryStringValue {
|
||||
INSENSITIVE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.query
|
||||
|
||||
data class RoomTagQueryFilter(
|
||||
val isFavorite: Boolean?,
|
||||
val isLowPriority: Boolean?,
|
||||
val isServerNotice: Boolean?
|
||||
)
|
@ -17,6 +17,7 @@
|
||||
package org.matrix.android.sdk.api.session.room
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
@ -26,7 +27,9 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult
|
||||
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
|
||||
/**
|
||||
* This interface defines methods to get rooms. It's implemented at the session level.
|
||||
@ -178,4 +181,9 @@ interface RoomService {
|
||||
* This call will try to gather some information on this room, but it could fail and get nothing more
|
||||
*/
|
||||
fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>)
|
||||
|
||||
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<PagedList<RoomSummary>>
|
||||
|
||||
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount
|
||||
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult
|
||||
}
|
||||
|
@ -17,12 +17,19 @@
|
||||
package org.matrix.android.sdk.api.session.room
|
||||
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
|
||||
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
|
||||
return RoomSummaryQueryParams.Builder().apply(init).build()
|
||||
}
|
||||
|
||||
enum class RoomCategoryFilter {
|
||||
ONLY_DM,
|
||||
ONLY_ROOMS,
|
||||
ALL
|
||||
}
|
||||
|
||||
/**
|
||||
* This class can be used to filter room summaries to use with:
|
||||
* [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
|
||||
@ -31,7 +38,9 @@ data class RoomSummaryQueryParams(
|
||||
val roomId: QueryStringValue,
|
||||
val displayName: QueryStringValue,
|
||||
val canonicalAlias: QueryStringValue,
|
||||
val memberships: List<Membership>
|
||||
val memberships: List<Membership>,
|
||||
val roomCategoryFilter: RoomCategoryFilter?,
|
||||
val roomTagQueryFilter: RoomTagQueryFilter?
|
||||
) {
|
||||
|
||||
class Builder {
|
||||
@ -40,12 +49,16 @@ data class RoomSummaryQueryParams(
|
||||
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
|
||||
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
|
||||
var memberships: List<Membership> = Membership.all()
|
||||
var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
|
||||
var roomTagQueryFilter: RoomTagQueryFilter? = null
|
||||
|
||||
fun build() = RoomSummaryQueryParams(
|
||||
roomId = roomId,
|
||||
displayName = displayName,
|
||||
canonicalAlias = canonicalAlias,
|
||||
memberships = memberships
|
||||
memberships = memberships,
|
||||
roomCategoryFilter = roomCategoryFilter,
|
||||
roomTagQueryFilter = roomTagQueryFilter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.room.summary
|
||||
|
||||
data class RoomAggregateNotificationCount(
|
||||
val notificationCount: Int,
|
||||
val highlightCount: Int
|
||||
) {
|
||||
fun totalCount() = notificationCount + highlightCount
|
||||
|
||||
fun isHighlight() = highlightCount > 0
|
||||
}
|
@ -17,22 +17,27 @@
|
||||
package org.matrix.android.sdk.internal.database
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import io.realm.FieldAttribute
|
||||
import io.realm.RealmMigration
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 8L
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 9L
|
||||
}
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
@ -46,6 +51,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
if (oldVersion <= 5) migrateTo6(realm)
|
||||
if (oldVersion <= 6) migrateTo7(realm)
|
||||
if (oldVersion <= 7) migrateTo8(realm)
|
||||
if (oldVersion <= 8) migrateTo9(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
@ -149,4 +155,43 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
?.removeField("sourceLocalEchoEvents")
|
||||
?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
|
||||
}
|
||||
|
||||
fun migrateTo9(realm: DynamicRealm) {
|
||||
Timber.d("Step 8 -> 9")
|
||||
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED)
|
||||
?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true)
|
||||
?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_DIRECT)
|
||||
?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR)
|
||||
|
||||
?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE)
|
||||
?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY)
|
||||
?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java)
|
||||
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
|
||||
|
||||
?.transform { obj ->
|
||||
|
||||
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
|
||||
}
|
||||
obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite)
|
||||
|
||||
val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
|
||||
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY
|
||||
}
|
||||
|
||||
obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority)
|
||||
|
||||
// XXX migrate last message origin server ts
|
||||
obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`)
|
||||
?.getObject(TimelineEventEntityFields.ROOT.`$`)
|
||||
?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
|
||||
obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(SessionRealmModule())
|
||||
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
|
||||
// .deleteRealmIfMigrationNeeded()
|
||||
.migration(migration)
|
||||
.build()
|
||||
|
||||
|
@ -26,7 +26,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
||||
private val typingUsersTracker: DefaultTypingUsersTracker) {
|
||||
|
||||
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||
val tags = roomSummaryEntity.tags.map {
|
||||
val tags = roomSummaryEntity.tags().map {
|
||||
RoomTag(it.tagName, it.tagOrder)
|
||||
}
|
||||
|
||||
|
@ -16,62 +16,222 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.database.model
|
||||
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.Index
|
||||
import io.realm.annotations.PrimaryKey
|
||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import timber.log.Timber
|
||||
|
||||
internal open class RoomSummaryEntity(
|
||||
@PrimaryKey var roomId: String = "",
|
||||
var displayName: String? = "",
|
||||
var avatarUrl: String? = "",
|
||||
var name: String? = "",
|
||||
var topic: String? = "",
|
||||
var latestPreviewableEvent: TimelineEventEntity? = null,
|
||||
var heroes: RealmList<String> = RealmList(),
|
||||
var joinedMembersCount: Int? = 0,
|
||||
var invitedMembersCount: Int? = 0,
|
||||
var isDirect: Boolean = false,
|
||||
var directUserId: String? = null,
|
||||
var otherMemberIds: RealmList<String> = RealmList(),
|
||||
var notificationCount: Int = 0,
|
||||
var highlightCount: Int = 0,
|
||||
var readMarkerId: String? = null,
|
||||
var hasUnreadMessages: Boolean = false,
|
||||
var tags: RealmList<RoomTagEntity> = RealmList(),
|
||||
var userDrafts: UserDraftsEntity? = null,
|
||||
var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS,
|
||||
var canonicalAlias: String? = null,
|
||||
var aliases: RealmList<String> = RealmList(),
|
||||
// this is required for querying
|
||||
var flatAliases: String = "",
|
||||
var isEncrypted: Boolean = false,
|
||||
var encryptionEventTs: Long? = 0,
|
||||
var roomEncryptionTrustLevelStr: String? = null,
|
||||
var inviterId: String? = null,
|
||||
var hasFailedSending: Boolean = false
|
||||
@PrimaryKey var roomId: String = ""
|
||||
) : RealmObject() {
|
||||
|
||||
var displayName: String? = ""
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
var avatarUrl: String? = ""
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
var name: String? = ""
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
var topic: String? = ""
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var latestPreviewableEvent: TimelineEventEntity? = null
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
@Index
|
||||
var lastActivityTime: Long? = null
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var heroes: RealmList<String> = RealmList()
|
||||
|
||||
var joinedMembersCount: Int? = 0
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var invitedMembersCount: Int? = 0
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
@Index
|
||||
var isDirect: Boolean = false
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var directUserId: String? = null
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var otherMemberIds: RealmList<String> = RealmList()
|
||||
|
||||
var notificationCount: Int = 0
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var highlightCount: Int = 0
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var readMarkerId: String? = null
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var hasUnreadMessages: Boolean = false
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
private var tags: RealmList<RoomTagEntity> = RealmList()
|
||||
|
||||
fun tags(): RealmList<RoomTagEntity> = tags
|
||||
|
||||
fun updateTags(newTags: List<Pair<String, Double?>>) {
|
||||
val toDelete = mutableListOf<RoomTagEntity>()
|
||||
tags.forEach { existingTag ->
|
||||
val updatedTag = newTags.firstOrNull { it.first == existingTag.tagName }
|
||||
if (updatedTag == null) {
|
||||
toDelete.add(existingTag)
|
||||
} else {
|
||||
existingTag.tagOrder = updatedTag.second
|
||||
}
|
||||
}
|
||||
toDelete.onEach { it.deleteFromRealm() }
|
||||
newTags.forEach { newTag ->
|
||||
if (tags.indexOfFirst { it.tagName == newTag.first } == -1) {
|
||||
// we must add it
|
||||
tags.add(
|
||||
RoomTagEntity(newTag.first, newTag.second)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
isFavourite = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_FAVOURITE } != -1
|
||||
isLowPriority = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY } != -1
|
||||
isServerNotice = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE } != -1
|
||||
}
|
||||
|
||||
@Index
|
||||
var isFavourite: Boolean = false
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
@Index
|
||||
var isLowPriority: Boolean = false
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
@Index
|
||||
var isServerNotice: Boolean = false
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var userDrafts: UserDraftsEntity? = null
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var canonicalAlias: String? = null
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var aliases: RealmList<String> = RealmList()
|
||||
|
||||
fun updateAliases(newAliases: List<String>) {
|
||||
// only update underlying field if there is a diff
|
||||
if (newAliases.toSet() != aliases.toSet()) {
|
||||
Timber.w("VAL: aliases updated")
|
||||
aliases.clear()
|
||||
aliases.addAll(newAliases)
|
||||
}
|
||||
}
|
||||
|
||||
// this is required for querying
|
||||
var flatAliases: String = ""
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var isEncrypted: Boolean = false
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var encryptionEventTs: Long? = 0
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var roomEncryptionTrustLevelStr: String? = null
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var inviterId: String? = null
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
var hasFailedSending: Boolean = false
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
}
|
||||
|
||||
@Index
|
||||
private var membershipStr: String = Membership.NONE.name
|
||||
|
||||
var membership: Membership
|
||||
get() {
|
||||
return Membership.valueOf(membershipStr)
|
||||
}
|
||||
set(value) {
|
||||
if (value.name != membershipStr) {
|
||||
membershipStr = value.name
|
||||
}
|
||||
}
|
||||
|
||||
@Index
|
||||
private var versioningStateStr: String = VersioningState.NONE.name
|
||||
var versioningState: VersioningState
|
||||
get() {
|
||||
return VersioningState.valueOf(versioningStateStr)
|
||||
}
|
||||
set(value) {
|
||||
if (value.name != versioningStateStr) {
|
||||
versioningStateStr = value.name
|
||||
}
|
||||
}
|
||||
|
||||
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
|
||||
get() {
|
||||
@ -84,8 +244,10 @@ internal open class RoomSummaryEntity(
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
if (value?.name != roomEncryptionTrustLevelStr) {
|
||||
roomEncryptionTrustLevelStr = value?.name
|
||||
}
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
@ -45,6 +46,7 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomT
|
||||
import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
|
||||
import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
|
||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
@ -96,6 +98,18 @@ internal class DefaultRoomService @Inject constructor(
|
||||
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
|
||||
}
|
||||
|
||||
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : LiveData<PagedList<RoomSummary>> {
|
||||
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams)
|
||||
}
|
||||
|
||||
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : UpdatableFilterLivePageResult {
|
||||
return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams)
|
||||
}
|
||||
|
||||
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||
return roomSummaryDataSource.getNotificationCountForRooms(queryParams)
|
||||
}
|
||||
|
||||
override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
||||
return roomSummaryDataSource.getBreadcrumbs(queryParams)
|
||||
}
|
||||
@ -178,3 +192,8 @@ internal class DefaultRoomService @Inject constructor(
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdatableFilterLivePageResult {
|
||||
val livePagedList: LiveData<PagedList<RoomSummary>>
|
||||
fun updateQuery(queryParams: RoomSummaryQueryParams)
|
||||
}
|
||||
|
@ -18,10 +18,17 @@ package org.matrix.android.sdk.internal.session.room.summary
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.Sort
|
||||
import org.matrix.android.sdk.api.session.room.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
|
||||
@ -31,9 +38,8 @@ import org.matrix.android.sdk.internal.database.query.findByAlias
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult
|
||||
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
@ -98,6 +104,71 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
||||
}
|
||||
|
||||
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<PagedList<RoomSummary>> {
|
||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||
roomSummariesQuery(realm, queryParams)
|
||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||
}
|
||||
val dataSourceFactory = realmDataSourceFactory.map {
|
||||
roomSummaryMapper.map(it)
|
||||
}
|
||||
return monarchy.findAllPagedWithChanges(realmDataSourceFactory,
|
||||
LivePagedListBuilder(dataSourceFactory,
|
||||
PagedList.Config.Builder()
|
||||
.setPageSize(10)
|
||||
.setInitialLoadSizeHint(20)
|
||||
.setEnablePlaceholders(false)
|
||||
.setPrefetchDistance(10)
|
||||
.build())
|
||||
)
|
||||
}
|
||||
|
||||
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult {
|
||||
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
|
||||
roomSummariesQuery(realm, queryParams)
|
||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||
}
|
||||
val dataSourceFactory = realmDataSourceFactory.map {
|
||||
roomSummaryMapper.map(it)
|
||||
}
|
||||
|
||||
val mapped = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
|
||||
LivePagedListBuilder(dataSourceFactory,
|
||||
PagedList.Config.Builder()
|
||||
.setPageSize(10)
|
||||
.setInitialLoadSizeHint(20)
|
||||
.setEnablePlaceholders(false)
|
||||
.setPrefetchDistance(10)
|
||||
.build())
|
||||
)
|
||||
|
||||
return object : UpdatableFilterLivePageResult {
|
||||
override val livePagedList: LiveData<PagedList<RoomSummary>>
|
||||
get() = mapped
|
||||
|
||||
override fun updateQuery(queryParams: RoomSummaryQueryParams) {
|
||||
realmDataSourceFactory.updateQuery {
|
||||
roomSummariesQuery(it, queryParams)
|
||||
.sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||
var notificationCount: RoomAggregateNotificationCount? = null
|
||||
monarchy.doWithRealm { realm ->
|
||||
val roomSummariesQuery = roomSummariesQuery(realm, queryParams)
|
||||
val notifCount = roomSummariesQuery.sum(RoomSummaryEntityFields.NOTIFICATION_COUNT).toInt()
|
||||
val highlightCount = roomSummariesQuery.sum(RoomSummaryEntityFields.HIGHLIGHT_COUNT).toInt()
|
||||
notificationCount = RoomAggregateNotificationCount(
|
||||
notifCount,
|
||||
highlightCount
|
||||
)
|
||||
}
|
||||
return notificationCount!!
|
||||
}
|
||||
|
||||
private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
|
||||
val query = RoomSummaryEntity.where(realm)
|
||||
query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
|
||||
@ -105,6 +176,27 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||
query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
|
||||
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||
query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name)
|
||||
|
||||
queryParams.roomCategoryFilter?.let {
|
||||
when (it) {
|
||||
RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||
RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
|
||||
RoomCategoryFilter.ALL -> {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
queryParams.roomTagQueryFilter?.let {
|
||||
it.isFavorite?.let { fav ->
|
||||
query.equalTo(RoomSummaryEntityFields.IS_FAVOURITE, fav)
|
||||
}
|
||||
it.isLowPriority?.let { lp ->
|
||||
query.equalTo(RoomSummaryEntityFields.IS_LOW_PRIORITY, lp)
|
||||
}
|
||||
it.isServerNotice?.let { lp ->
|
||||
query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, lp)
|
||||
}
|
||||
}
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +98,11 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||
|
||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||
|
||||
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
|
||||
if (lastActivityFromEvent != null) {
|
||||
roomSummaryEntity.lastActivityTime = lastActivityFromEvent
|
||||
}
|
||||
|
||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
||||
// avoid this call if we are sure there are unread events
|
||||
|| !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId)
|
||||
@ -112,8 +117,9 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||
|
||||
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases
|
||||
.orEmpty()
|
||||
roomSummaryEntity.aliases.clear()
|
||||
roomSummaryEntity.aliases.addAll(roomAliases)
|
||||
// roomSummaryEntity.aliases.clear()
|
||||
// roomSummaryEntity.aliases.addAll(roomAliases)
|
||||
roomSummaryEntity.updateAliases(roomAliases)
|
||||
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
|
||||
roomSummaryEntity.isEncrypted = encryptionEvent != null
|
||||
roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs
|
||||
|
@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.sync
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomTagEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomTagHandler @Inject constructor() {
|
||||
@ -31,12 +31,8 @@ internal class RoomTagHandler @Inject constructor() {
|
||||
}
|
||||
val tags = content.tags.entries.map { (tagName, params) ->
|
||||
RoomTagEntity(tagName, params["order"] as? Double)
|
||||
Pair(tagName, params["order"] as? Double)
|
||||
}
|
||||
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
|
||||
?: RoomSummaryEntity(roomId)
|
||||
|
||||
roomSummaryEntity.tags.clear()
|
||||
roomSummaryEntity.tags.addAll(tags)
|
||||
realm.insertOrUpdate(roomSummaryEntity)
|
||||
RoomSummaryEntity.getOrCreate(realm, roomId).updateTags(tags)
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class AppStateHandler @Inject constructor(
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun entersForeground() {
|
||||
observeRoomsAndGroup()
|
||||
// observeRoomsAndGroup()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
|
@ -127,6 +127,11 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||
Timber.i("onResume Fragment ${javaClass.simpleName}")
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
Timber.i("onPause Fragment ${javaClass.simpleName}")
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@ -150,6 +155,7 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Timber.i("onDestroy Fragment ${javaClass.simpleName}")
|
||||
uiDisposables.dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat
|
||||
|
||||
sealed class RoomListAction : VectorViewModelAction {
|
||||
data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction()
|
||||
data class ToggleCategory(val category: RoomCategory) : RoomListAction()
|
||||
data class ToggleSection(val section: RoomListViewModel.RoomsSection) : RoomListAction()
|
||||
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
||||
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
||||
data class FilterWith(val filter: String) : RoomListAction()
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.helpFooterItem
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.resources.UserPreferencesProvider
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomListFooterController @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val userPreferencesProvider: UserPreferencesProvider
|
||||
) : TypedEpoxyController<RoomListViewState>() {
|
||||
|
||||
var listener: RoomListListener? = null
|
||||
|
||||
override fun buildModels(data: RoomListViewState?) {
|
||||
when (data?.displayMode) {
|
||||
RoomListDisplayMode.FILTERED -> {
|
||||
filteredRoomFooterItem {
|
||||
id("filter_footer")
|
||||
listener(listener)
|
||||
currentFilter(data.roomFilter)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (userPreferencesProvider.shouldShowLongClickOnRoomHelp()) {
|
||||
helpFooterItem {
|
||||
id("long_click_help")
|
||||
text(stringProvider.getString(R.string.help_long_click_on_room_for_more_options))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -27,12 +27,10 @@ import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.OnModelBuildFinishedListener
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Incomplete
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
@ -44,6 +42,7 @@ import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.OnBackPressed
|
||||
import im.vector.app.core.platform.StateView
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.resources.UserPreferencesProvider
|
||||
import im.vector.app.databinding.FragmentRoomListBinding
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
|
||||
@ -53,8 +52,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
|
||||
import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
|
||||
import im.vector.app.features.notifications.NotificationDrawerManager
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.extensions.orTrue
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||
@ -66,12 +64,13 @@ data class RoomListParams(
|
||||
) : Parcelable
|
||||
|
||||
class RoomListFragment @Inject constructor(
|
||||
private val roomController: RoomSummaryController,
|
||||
private val pagedControllerFactory: RoomSummaryPagedControllerFactory,
|
||||
val roomListViewModelFactory: RoomListViewModel.Factory,
|
||||
private val notificationDrawerManager: NotificationDrawerManager,
|
||||
private val sharedViewPool: RecyclerView.RecycledViewPool
|
||||
private val footerController: RoomListFooterController,
|
||||
private val userPreferencesProvider: UserPreferencesProvider
|
||||
) : VectorBaseFragment<FragmentRoomListBinding>(),
|
||||
RoomSummaryController.Listener,
|
||||
RoomListListener,
|
||||
OnBackPressed,
|
||||
NotifsFabMenuView.Listener {
|
||||
|
||||
@ -87,6 +86,20 @@ class RoomListFragment @Inject constructor(
|
||||
|
||||
private var hasUnreadRooms = false
|
||||
|
||||
data class SectionKey(
|
||||
val name: String,
|
||||
val isExpanded: Boolean
|
||||
)
|
||||
|
||||
data class SectionAdapterInfo(
|
||||
var section: SectionKey,
|
||||
val headerHeaderAdapter: SectionHeaderAdapter,
|
||||
val contentAdapter: RoomSummaryPagedController
|
||||
)
|
||||
|
||||
private val adapterInfosList = mutableListOf<SectionAdapterInfo>()
|
||||
private var concatAdapter: ConcatAdapter? = null
|
||||
|
||||
override fun getMenuRes() = R.menu.room_list
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
@ -127,15 +140,49 @@ class RoomListFragment @Inject constructor(
|
||||
.disposeOnDestroyView()
|
||||
}
|
||||
|
||||
private fun refreshCollapseStates() {
|
||||
var contentInsertIndex = 1
|
||||
roomListViewModel.sections.forEachIndexed { index, roomsSection ->
|
||||
val actualBlock = adapterInfosList[index]
|
||||
val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue()
|
||||
if (actualBlock.section.isExpanded && !isRoomSectionExpanded) {
|
||||
// we have to remove the content adapter
|
||||
concatAdapter?.removeAdapter(actualBlock.contentAdapter.adapter)
|
||||
} else if (!actualBlock.section.isExpanded && isRoomSectionExpanded) {
|
||||
// we must add it back!
|
||||
concatAdapter?.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter)
|
||||
}
|
||||
contentInsertIndex = if (isRoomSectionExpanded) {
|
||||
contentInsertIndex + 2
|
||||
} else {
|
||||
contentInsertIndex + 1
|
||||
}
|
||||
actualBlock.section = actualBlock.section.copy(
|
||||
isExpanded = isRoomSectionExpanded
|
||||
)
|
||||
actualBlock.headerHeaderAdapter.updateSection(
|
||||
actualBlock.headerHeaderAdapter.section.copy(isExpanded = isRoomSectionExpanded)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showFailure(throwable: Throwable) {
|
||||
showErrorInSnackbar(throwable)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
roomController.removeModelBuildListener(modelBuildListener)
|
||||
adapterInfosList.onEach { it.contentAdapter.removeModelBuildListener(modelBuildListener) }
|
||||
adapterInfosList.clear()
|
||||
// roomController.removeModelBuildListener(modelBuildListener)
|
||||
modelBuildListener = null
|
||||
views.roomListView.cleanup()
|
||||
roomController.listener = null
|
||||
// controllers.onEach {
|
||||
// it.listener = null
|
||||
// // concatAdapter.removeAdapter(it.adapter)
|
||||
// }
|
||||
// controllers.clear()
|
||||
// roomController.listener = null
|
||||
// favRoomController.listener = null
|
||||
stateRestorer.clear()
|
||||
views.createChatFabMenu.listener = null
|
||||
super.onDestroyView()
|
||||
@ -204,13 +251,60 @@ class RoomListFragment @Inject constructor(
|
||||
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||
views.roomListView.layoutManager = layoutManager
|
||||
views.roomListView.itemAnimator = RoomListAnimator()
|
||||
views.roomListView.setRecycledViewPool(sharedViewPool)
|
||||
// views.roomListView.setRecycledViewPool(sharedViewPool)
|
||||
layoutManager.recycleChildrenOnDetach = true
|
||||
roomController.listener = this
|
||||
|
||||
modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) }
|
||||
roomController.addModelBuildListener(modelBuildListener)
|
||||
views.roomListView.adapter = roomController.adapter
|
||||
views.stateView.contentView = views.roomListView
|
||||
|
||||
val concatAdapter = ConcatAdapter()
|
||||
val hasOnlyOneSection = roomListViewModel.sections.size == 1
|
||||
roomListViewModel.sections.forEach { section ->
|
||||
|
||||
val sectionAdapter = SectionHeaderAdapter {
|
||||
roomListViewModel.handle(RoomListAction.ToggleSection(section))
|
||||
}.also {
|
||||
it.updateSection(SectionHeaderAdapter.SectionViewModel(
|
||||
section.sectionName
|
||||
))
|
||||
}
|
||||
|
||||
val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController().also {
|
||||
section.livePages.observe(viewLifecycleOwner) { pl ->
|
||||
it.submitList(pl)
|
||||
sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty() || hasOnlyOneSection))
|
||||
checkEmptyState()
|
||||
}
|
||||
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
||||
sectionAdapter.updateSection(sectionAdapter.section.copy(
|
||||
notificationCount = counts.totalCount(),
|
||||
isHighlighted = counts.isHighlight()
|
||||
))
|
||||
}
|
||||
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
||||
refreshCollapseStates()
|
||||
}
|
||||
it.listener = this
|
||||
}
|
||||
adapterInfosList.add(
|
||||
SectionAdapterInfo(
|
||||
SectionKey(
|
||||
name = section.sectionName,
|
||||
isExpanded = section.isExpanded.value.orTrue()
|
||||
),
|
||||
sectionAdapter,
|
||||
contentAdapter
|
||||
)
|
||||
)
|
||||
concatAdapter.addAdapter(sectionAdapter)
|
||||
concatAdapter.addAdapter(contentAdapter.adapter)
|
||||
}
|
||||
|
||||
// Add the footer controller
|
||||
this.footerController.listener = this
|
||||
concatAdapter.addAdapter(footerController.adapter)
|
||||
|
||||
this.concatAdapter = concatAdapter
|
||||
views.roomListView.adapter = concatAdapter
|
||||
}
|
||||
|
||||
private val showFabRunnable = Runnable {
|
||||
@ -278,12 +372,7 @@ class RoomListFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun invalidate() = withState(roomListViewModel) { state ->
|
||||
when (state.asyncFilteredRooms) {
|
||||
is Incomplete -> renderLoading()
|
||||
is Success -> renderSuccess(state)
|
||||
is Fail -> renderFailure(state.asyncFilteredRooms.error)
|
||||
}
|
||||
roomController.update(state)
|
||||
footerController.setData(state)
|
||||
// Mark all as read menu
|
||||
when (roomListParams.displayMode) {
|
||||
RoomListDisplayMode.NOTIFICATIONS,
|
||||
@ -299,37 +388,16 @@ class RoomListFragment @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSuccess(state: RoomListViewState) {
|
||||
val allRooms = state.asyncRooms()
|
||||
val filteredRooms = state.asyncFilteredRooms()
|
||||
if (filteredRooms.isNullOrEmpty()) {
|
||||
renderEmptyState(allRooms)
|
||||
} else {
|
||||
views.stateView.state = StateView.State.Content
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderEmptyState(allRooms: List<RoomSummary>?) {
|
||||
val hasNoRoom = allRooms
|
||||
?.filter {
|
||||
it.membership == Membership.JOIN || it.membership == Membership.INVITE
|
||||
}
|
||||
.isNullOrEmpty()
|
||||
private fun checkEmptyState() {
|
||||
val hasNoRoom = adapterInfosList.all { it.headerHeaderAdapter.section.isHidden }
|
||||
if (hasNoRoom) {
|
||||
val emptyState = when (roomListParams.displayMode) {
|
||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||
if (hasNoRoom) {
|
||||
StateView.State.Empty(
|
||||
title = getString(R.string.room_list_catchup_welcome_title),
|
||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_catchup),
|
||||
message = getString(R.string.room_list_catchup_welcome_body)
|
||||
)
|
||||
} else {
|
||||
StateView.State.Empty(
|
||||
title = getString(R.string.room_list_catchup_empty_title),
|
||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper),
|
||||
message = getString(R.string.room_list_catchup_empty_body))
|
||||
}
|
||||
}
|
||||
RoomListDisplayMode.PEOPLE ->
|
||||
StateView.State.Empty(
|
||||
title = getString(R.string.room_list_people_empty_title),
|
||||
@ -349,18 +417,9 @@ class RoomListFragment @Inject constructor(
|
||||
StateView.State.Content
|
||||
}
|
||||
views.stateView.state = emptyState
|
||||
} else {
|
||||
views.stateView.state = StateView.State.Content
|
||||
}
|
||||
|
||||
private fun renderLoading() {
|
||||
views.stateView.state = StateView.State.Loading
|
||||
}
|
||||
|
||||
private fun renderFailure(error: Throwable) {
|
||||
val message = when (error) {
|
||||
is Failure.NetworkConnection -> getString(R.string.network_error_please_check_and_retry)
|
||||
else -> getString(R.string.unknown_error)
|
||||
}
|
||||
views.stateView.state = StateView.State.Error(message)
|
||||
}
|
||||
|
||||
override fun onBackPressed(toolbarButton: Boolean): Boolean {
|
||||
@ -377,7 +436,11 @@ class RoomListFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onRoomLongClicked(room: RoomSummary): Boolean {
|
||||
roomController.onRoomLongClicked()
|
||||
userPreferencesProvider.neverShowLongClickOnRoomHelpAgain()
|
||||
withState(roomListViewModel) {
|
||||
// refresh footer
|
||||
footerController.setData(it)
|
||||
}
|
||||
RoomListQuickActionsBottomSheet
|
||||
.newInstance(room.roomId, RoomListActionsArgs.Mode.FULL)
|
||||
.show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS")
|
||||
@ -394,10 +457,6 @@ class RoomListFragment @Inject constructor(
|
||||
roomListViewModel.handle(RoomListAction.RejectInvitation(room))
|
||||
}
|
||||
|
||||
override fun onToggleRoomCategory(roomCategory: RoomCategory) {
|
||||
roomListViewModel.handle(RoomListAction.ToggleCategory(roomCategory))
|
||||
}
|
||||
|
||||
override fun createRoom(initialName: String) {
|
||||
navigator.openCreateRoom(requireActivity(), initialName)
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
||||
interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
|
||||
fun onRoomClicked(room: RoomSummary)
|
||||
fun onRoomLongClicked(room: RoomSummary): Boolean
|
||||
fun onRejectRoomInvitation(room: RoomSummary)
|
||||
fun onAcceptRoomInvitation(room: RoomSummary)
|
||||
}
|
@ -16,37 +16,50 @@
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagedList
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.utils.DataSource
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.state.isPublic
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult
|
||||
import org.matrix.android.sdk.rx.asObservable
|
||||
import timber.log.Timber
|
||||
import java.lang.Exception
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
private val session: Session,
|
||||
private val roomSummariesSource: DataSource<List<RoomSummary>>)
|
||||
private val stringProvider: StringProvider)
|
||||
: VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
|
||||
|
||||
interface Factory {
|
||||
fun create(initialState: RoomListViewState): RoomListViewModel
|
||||
}
|
||||
|
||||
private var updatableQuery: UpdatableFilterLivePageResult? = null
|
||||
|
||||
companion object : MvRxViewModelFactory<RoomListViewModel, RoomListViewState> {
|
||||
|
||||
@JvmStatic
|
||||
@ -56,18 +69,79 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
}
|
||||
}
|
||||
|
||||
private val displayMode = initialState.displayMode
|
||||
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
|
||||
data class RoomsSection(
|
||||
val sectionName: String,
|
||||
val livePages: LiveData<PagedList<RoomSummary>>,
|
||||
val isExpanded: MutableLiveData<Boolean> = MutableLiveData(true),
|
||||
val notificationCount: MutableLiveData<RoomAggregateNotificationCount> =
|
||||
MutableLiveData(RoomAggregateNotificationCount(0, 0))
|
||||
)
|
||||
|
||||
val sections: List<RoomsSection> by lazy {
|
||||
val sections = mutableListOf<RoomsSection>()
|
||||
if (initialState.displayMode == RoomListDisplayMode.PEOPLE) {
|
||||
|
||||
addSection(sections, R.string.invitations_header) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
}
|
||||
|
||||
addSection(sections, R.string.bottom_action_people_x) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
}
|
||||
} else if (initialState.displayMode == RoomListDisplayMode.ROOMS) {
|
||||
|
||||
addSection(sections, R.string.invitations_header) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
}
|
||||
|
||||
addSection(sections, R.string.bottom_action_favourites) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
|
||||
}
|
||||
|
||||
addSection(sections, R.string.bottom_action_rooms) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
it.roomTagQueryFilter = RoomTagQueryFilter(false, false, false)
|
||||
}
|
||||
|
||||
addSection(sections, R.string.low_priority_header) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
it.roomTagQueryFilter = RoomTagQueryFilter(null, true, null)
|
||||
}
|
||||
|
||||
addSection(sections, R.string.system_alerts_header) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true)
|
||||
}
|
||||
} else if (initialState.displayMode == RoomListDisplayMode.FILTERED) {
|
||||
withQueryParams({
|
||||
it.memberships = Membership.activeMemberships()
|
||||
// it.displayName = QueryStringValue.Contains("")
|
||||
}) { qpm ->
|
||||
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
||||
session.getFilteredPagedRoomSummariesLive(qpm)
|
||||
.let { livePagedList ->
|
||||
updatableQuery = livePagedList
|
||||
sections.add(RoomsSection(name, livePagedList.livePagedList))
|
||||
}
|
||||
}
|
||||
}
|
||||
sections
|
||||
}
|
||||
|
||||
init {
|
||||
observeRoomSummaries()
|
||||
observeMembershipChanges()
|
||||
}
|
||||
|
||||
override fun handle(action: RoomListAction) {
|
||||
when (action) {
|
||||
is RoomListAction.SelectRoom -> handleSelectRoom(action)
|
||||
is RoomListAction.ToggleCategory -> handleToggleCategory(action)
|
||||
is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action)
|
||||
is RoomListAction.RejectInvitation -> handleRejectInvitation(action)
|
||||
is RoomListAction.FilterWith -> handleFilter(action)
|
||||
@ -75,9 +149,41 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
is RoomListAction.LeaveRoom -> handleLeaveRoom(action)
|
||||
is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action)
|
||||
is RoomListAction.ToggleTag -> handleToggleTag(action)
|
||||
is RoomListAction.ToggleSection -> handleToggleSection(action.section)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun addSection(sections: MutableList<RoomsSection>, @StringRes nameRes: Int, query: (RoomSummaryQueryParams.Builder) -> Unit) {
|
||||
withQueryParams({
|
||||
query.invoke(it)
|
||||
}) { roomQueryParams ->
|
||||
|
||||
val name = stringProvider.getString(nameRes)
|
||||
session.getPagedRoomSummariesLive(roomQueryParams)
|
||||
.let { livePagedList ->
|
||||
|
||||
// use it also as a source to update count
|
||||
livePagedList.asObservable()
|
||||
.observeOn(Schedulers.computation())
|
||||
.subscribe {
|
||||
sections.find { it.sectionName == name }
|
||||
?.notificationCount
|
||||
?.postValue(session.getNotificationCountForRooms(roomQueryParams))
|
||||
}.disposeOnClear()
|
||||
|
||||
sections.add(RoomsSection(name, livePagedList))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) {
|
||||
RoomSummaryQueryParams.Builder().apply {
|
||||
builder.invoke(this)
|
||||
}.build().let {
|
||||
block(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun isPublicRoom(roomId: String): Boolean {
|
||||
return session.getRoom(roomId)?.isPublic().orFalse()
|
||||
}
|
||||
@ -88,8 +194,11 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
_viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary))
|
||||
}
|
||||
|
||||
private fun handleToggleCategory(action: RoomListAction.ToggleCategory) = setState {
|
||||
this.toggle(action.category)
|
||||
private fun handleToggleSection(section: RoomsSection) {
|
||||
sections.find { it.sectionName == section.sectionName }
|
||||
?.let { section ->
|
||||
section.isExpanded.postValue(!section.isExpanded.value.orFalse())
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFilter(action: RoomListAction.FilterWith) {
|
||||
@ -98,23 +207,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
roomFilter = action.filter
|
||||
)
|
||||
}
|
||||
updatableQuery?.updateQuery(
|
||||
roomSummaryQueryParams {
|
||||
this.memberships = Membership.activeMemberships()
|
||||
this.displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
|
||||
}
|
||||
|
||||
private fun observeRoomSummaries() {
|
||||
roomSummariesSource
|
||||
.observe()
|
||||
.observeOn(Schedulers.computation())
|
||||
.execute { asyncRooms ->
|
||||
copy(asyncRooms = asyncRooms)
|
||||
}
|
||||
|
||||
roomSummariesSource
|
||||
.observe()
|
||||
.observeOn(Schedulers.computation())
|
||||
.map { buildRoomSummaries(it) }
|
||||
.execute { async ->
|
||||
copy(asyncFilteredRooms = async)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state ->
|
||||
@ -164,12 +262,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
}
|
||||
|
||||
private fun handleMarkAllRoomsRead() = withState { state ->
|
||||
state.asyncFilteredRooms.invoke()
|
||||
?.flatMap { it.value }
|
||||
?.filter { it.membership == Membership.JOIN }
|
||||
?.map { it.roomId }
|
||||
?.toList()
|
||||
?.let { session.markAllAsRead(it, NoOpMatrixCallback()) }
|
||||
// state.asyncFilteredRooms.invoke()
|
||||
// ?.flatMap { it.value }
|
||||
// ?.filter { it.membership == Membership.JOIN }
|
||||
// ?.map { it.roomId }
|
||||
// ?.toList()
|
||||
// ?.let { session.markAllAsRead(it, NoOpMatrixCallback()) }
|
||||
}
|
||||
|
||||
private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) {
|
||||
@ -226,46 +324,4 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
_viewEvents.post(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeMembershipChanges() {
|
||||
session.rx()
|
||||
.liveRoomChangeMembershipState()
|
||||
.subscribe {
|
||||
Timber.v("ChangeMembership states: $it")
|
||||
setState { copy(roomMembershipChanges = it) }
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun buildRoomSummaries(rooms: List<RoomSummary>): RoomSummaries {
|
||||
// Set up init size on directChats and groupRooms as they are the biggest ones
|
||||
val invites = ArrayList<RoomSummary>()
|
||||
val favourites = ArrayList<RoomSummary>()
|
||||
val directChats = ArrayList<RoomSummary>(rooms.size)
|
||||
val groupRooms = ArrayList<RoomSummary>(rooms.size)
|
||||
val lowPriorities = ArrayList<RoomSummary>()
|
||||
val serverNotices = ArrayList<RoomSummary>()
|
||||
|
||||
rooms
|
||||
.filter { roomListDisplayModeFilter.test(it) }
|
||||
.forEach { room ->
|
||||
val tags = room.tags.map { it.name }
|
||||
when {
|
||||
room.membership == Membership.INVITE -> invites.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room)
|
||||
tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room)
|
||||
room.isDirect -> directChats.add(room)
|
||||
else -> groupRooms.add(room)
|
||||
}
|
||||
}
|
||||
return RoomSummaries().apply {
|
||||
put(RoomCategory.INVITE, invites)
|
||||
put(RoomCategory.FAVOURITE, favourites)
|
||||
put(RoomCategory.DIRECT, directChats)
|
||||
put(RoomCategory.GROUP, groupRooms)
|
||||
put(RoomCategory.LOW_PRIORITY, lowPriorities)
|
||||
put(RoomCategory.SERVER_NOTICE, serverNotices)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,20 +16,20 @@
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import im.vector.app.features.home.HomeRoomListDataSource
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class RoomListViewModelFactory @Inject constructor(private val session: Provider<Session>,
|
||||
private val homeRoomListDataSource: Provider<HomeRoomListDataSource>)
|
||||
private val stringProvider: StringProvider)
|
||||
: RoomListViewModel.Factory {
|
||||
|
||||
override fun create(initialState: RoomListViewState): RoomListViewModel {
|
||||
return RoomListViewModel(
|
||||
initialState,
|
||||
session.get(),
|
||||
homeRoomListDataSource.get()
|
||||
stringProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -17,59 +17,26 @@
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
|
||||
data class RoomListViewState(
|
||||
val displayMode: RoomListDisplayMode,
|
||||
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
|
||||
val roomFilter: String = "",
|
||||
val asyncFilteredRooms: Async<RoomSummaries> = Uninitialized,
|
||||
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
|
||||
val isInviteExpanded: Boolean = true,
|
||||
val isFavouriteRoomsExpanded: Boolean = true,
|
||||
val isDirectRoomsExpanded: Boolean = true,
|
||||
val isGroupRoomsExpanded: Boolean = true,
|
||||
val isLowPriorityRoomsExpanded: Boolean = true,
|
||||
val isServerNoticeRoomsExpanded: Boolean = true
|
||||
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap()
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: RoomListParams) : this(displayMode = args.displayMode)
|
||||
|
||||
fun isCategoryExpanded(roomCategory: RoomCategory): Boolean {
|
||||
return when (roomCategory) {
|
||||
RoomCategory.INVITE -> isInviteExpanded
|
||||
RoomCategory.FAVOURITE -> isFavouriteRoomsExpanded
|
||||
RoomCategory.DIRECT -> isDirectRoomsExpanded
|
||||
RoomCategory.GROUP -> isGroupRoomsExpanded
|
||||
RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded
|
||||
RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle(roomCategory: RoomCategory): RoomListViewState {
|
||||
return when (roomCategory) {
|
||||
RoomCategory.INVITE -> copy(isInviteExpanded = !isInviteExpanded)
|
||||
RoomCategory.FAVOURITE -> copy(isFavouriteRoomsExpanded = !isFavouriteRoomsExpanded)
|
||||
RoomCategory.DIRECT -> copy(isDirectRoomsExpanded = !isDirectRoomsExpanded)
|
||||
RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded)
|
||||
RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded)
|
||||
RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
val hasUnread: Boolean
|
||||
get() = asyncFilteredRooms.invoke()
|
||||
?.flatMap { it.value }
|
||||
?.filter { it.membership == Membership.JOIN }
|
||||
?.any { it.hasUnreadMessages }
|
||||
?: false
|
||||
val hasUnread: Boolean = false
|
||||
// get() = asyncFilteredRooms.invoke()
|
||||
// ?.flatMap { it.value }
|
||||
// ?.filter { it.membership == Membership.JOIN }
|
||||
// ?.any { it.hasUnreadMessages }
|
||||
// ?: false
|
||||
}
|
||||
|
||||
typealias RoomSummaries = LinkedHashMap<RoomCategory, List<RoomSummary>>
|
||||
|
@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.helpFooterItem
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.resources.UserPreferencesProvider
|
||||
import im.vector.app.features.home.RoomListDisplayMode
|
||||
import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
|
||||
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
|
||||
private val roomSummaryItemFactory: RoomSummaryItemFactory,
|
||||
private val roomListNameFilter: RoomListNameFilter,
|
||||
private val userPreferencesProvider: UserPreferencesProvider
|
||||
) : EpoxyController() {
|
||||
|
||||
var listener: Listener? = null
|
||||
|
||||
private var viewState: RoomListViewState? = null
|
||||
|
||||
init {
|
||||
// We are requesting a model build directly as the first build of epoxy is on the main thread.
|
||||
// It avoids to build the whole list of rooms on the main thread.
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
fun update(viewState: RoomListViewState) {
|
||||
this.viewState = viewState
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
fun onRoomLongClicked() {
|
||||
userPreferencesProvider.neverShowLongClickOnRoomHelpAgain()
|
||||
requestModelBuild()
|
||||
}
|
||||
|
||||
override fun buildModels() {
|
||||
val nonNullViewState = viewState ?: return
|
||||
when (nonNullViewState.displayMode) {
|
||||
RoomListDisplayMode.FILTERED -> buildFilteredRooms(nonNullViewState)
|
||||
else -> buildRooms(nonNullViewState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildFilteredRooms(viewState: RoomListViewState) {
|
||||
val summaries = viewState.asyncRooms() ?: return
|
||||
|
||||
roomListNameFilter.filter = viewState.roomFilter
|
||||
|
||||
val filteredSummaries = summaries
|
||||
.filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) }
|
||||
|
||||
buildRoomModels(filteredSummaries,
|
||||
viewState.roomMembershipChanges,
|
||||
emptySet())
|
||||
|
||||
addFilterFooter(viewState)
|
||||
}
|
||||
|
||||
private fun buildRooms(viewState: RoomListViewState) {
|
||||
var showHelp = false
|
||||
val roomSummaries = viewState.asyncFilteredRooms()
|
||||
roomSummaries?.forEach { (category, summaries) ->
|
||||
if (summaries.isEmpty()) {
|
||||
return@forEach
|
||||
} else {
|
||||
val isExpanded = viewState.isCategoryExpanded(category)
|
||||
buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) {
|
||||
listener?.onToggleRoomCategory(category)
|
||||
}
|
||||
if (isExpanded) {
|
||||
buildRoomModels(summaries,
|
||||
viewState.roomMembershipChanges,
|
||||
emptySet())
|
||||
// Never set showHelp to true for invitation
|
||||
if (category != RoomCategory.INVITE) {
|
||||
showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showHelp) {
|
||||
buildLongClickHelp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildLongClickHelp() {
|
||||
helpFooterItem {
|
||||
id("long_click_help")
|
||||
text(stringProvider.getString(R.string.help_long_click_on_room_for_more_options))
|
||||
}
|
||||
}
|
||||
|
||||
private fun addFilterFooter(viewState: RoomListViewState) {
|
||||
filteredRoomFooterItem {
|
||||
id("filter_footer")
|
||||
listener(listener)
|
||||
currentFilter(viewState.roomFilter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRoomCategory(viewState: RoomListViewState,
|
||||
summaries: List<RoomSummary>,
|
||||
@StringRes titleRes: Int,
|
||||
isExpanded: Boolean,
|
||||
mutateExpandedState: () -> Unit) {
|
||||
// TODO should add some business logic later
|
||||
val unreadCount = if (summaries.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
summaries.map { it.notificationCount }.sumBy { i -> i }
|
||||
}
|
||||
val showHighlighted = summaries.any { it.highlightCount > 0 }
|
||||
roomCategoryItem {
|
||||
id(titleRes)
|
||||
title(stringProvider.getString(titleRes))
|
||||
expanded(isExpanded)
|
||||
unreadNotificationCount(unreadCount)
|
||||
showHighlighted(showHighlighted)
|
||||
listener {
|
||||
mutateExpandedState()
|
||||
update(viewState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildRoomModels(summaries: List<RoomSummary>,
|
||||
roomChangedMembershipStates: Map<String, ChangeMembershipState>,
|
||||
selectedRoomIds: Set<String>) {
|
||||
summaries.forEach { roomSummary ->
|
||||
roomSummaryItemFactory
|
||||
.create(roomSummary,
|
||||
roomChangedMembershipStates,
|
||||
selectedRoomIds,
|
||||
listener)
|
||||
.addTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
|
||||
fun onToggleRoomCategory(roomCategory: RoomCategory)
|
||||
fun onRoomClicked(room: RoomSummary)
|
||||
fun onRoomLongClicked(room: RoomSummary): Boolean
|
||||
fun onRejectRoomInvitation(room: RoomSummary)
|
||||
fun onAcceptRoomInvitation(room: RoomSummary)
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||
fun create(roomSummary: RoomSummary,
|
||||
roomChangeMembershipStates: Map<String, ChangeMembershipState>,
|
||||
selectedRoomIds: Set<String>,
|
||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
listener: RoomListListener?): VectorEpoxyModel<*> {
|
||||
return when (roomSummary.membership) {
|
||||
Membership.INVITE -> {
|
||||
val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown
|
||||
@ -52,7 +52,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
|
||||
|
||||
private fun createInvitationItem(roomSummary: RoomSummary,
|
||||
changeMembershipState: ChangeMembershipState,
|
||||
listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> {
|
||||
listener: RoomListListener?): VectorEpoxyModel<*> {
|
||||
val secondLine = if (roomSummary.isDirect) {
|
||||
roomSummary.inviterId
|
||||
} else {
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.resources.UserPreferencesProvider
|
||||
import im.vector.app.core.utils.createUIHandler
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomSummaryPagedControllerFactory @Inject constructor(private val stringProvider: StringProvider,
|
||||
private val userPreferencesProvider: UserPreferencesProvider,
|
||||
private val roomSummaryItemFactory: RoomSummaryItemFactory) {
|
||||
|
||||
fun createRoomSummaryPagedController(): RoomSummaryPagedController {
|
||||
return RoomSummaryPagedController(stringProvider, userPreferencesProvider, roomSummaryItemFactory)
|
||||
}
|
||||
}
|
||||
|
||||
class RoomSummaryPagedController constructor(private val stringProvider: StringProvider,
|
||||
private val userPreferencesProvider: UserPreferencesProvider,
|
||||
private val roomSummaryItemFactory: RoomSummaryItemFactory)
|
||||
: PagedListEpoxyController<RoomSummary>(
|
||||
// Important it must match the PageList builder notify Looper
|
||||
modelBuildingHandler = createUIHandler()
|
||||
) {
|
||||
|
||||
var listener: RoomListListener? = null
|
||||
|
||||
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
||||
val unwrappedItem = item
|
||||
// for place holder if enabled
|
||||
?: return roomSummaryItemFactory.createRoomItem(
|
||||
RoomSummary(
|
||||
roomId = "null_item_pos_$currentPosition",
|
||||
name = "",
|
||||
encryptionEventTs = null,
|
||||
isEncrypted = false,
|
||||
typingUsers = emptyList()
|
||||
), emptySet(), null, null)
|
||||
|
||||
// GenericItem_().apply { id("null_item_pos_$currentPosition") }
|
||||
|
||||
return roomSummaryItemFactory.create(unwrappedItem, emptyMap(), emptySet(), listener)
|
||||
}
|
||||
|
||||
// override fun onModelBound(holder: EpoxyViewHolder, boundModel: EpoxyModel<*>, position: Int, previouslyBoundModel: EpoxyModel<*>?) {
|
||||
// Timber.w("VAL: Will load around $position")
|
||||
// super.onModelBound(holder, boundModel, position, previouslyBoundModel)
|
||||
// }
|
||||
// fun onRoomLongClicked() {
|
||||
// userPreferencesProvider.neverShowLongClickOnRoomHelpAgain()
|
||||
// requestModelBuild()
|
||||
// }
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2021 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.utils.DebouncedClickListener
|
||||
import im.vector.app.databinding.ItemRoomCategoryBinding
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
class SectionHeaderAdapter constructor(
|
||||
private val onClickAction: (() -> Unit)
|
||||
) : RecyclerView.Adapter<SectionHeaderAdapter.VH>() {
|
||||
|
||||
data class SectionViewModel(
|
||||
val name: String,
|
||||
val isExpanded: Boolean = true,
|
||||
val notificationCount: Int = 0,
|
||||
val isHighlighted: Boolean = false,
|
||||
val isHidden: Boolean = true
|
||||
)
|
||||
|
||||
lateinit var section: SectionViewModel
|
||||
private set
|
||||
|
||||
fun updateSection(newSection: SectionViewModel) {
|
||||
if (!::section.isInitialized || newSection != section) {
|
||||
section = newSection
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int) = section.hashCode().toLong()
|
||||
|
||||
override fun getItemViewType(position: Int) = R.layout.item_room_category
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
|
||||
return VH.create(parent, this.onClickAction)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: VH, position: Int) {
|
||||
holder.bind(section)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1.takeIf { section.isHidden.not() } ?: 0
|
||||
|
||||
class VH constructor(
|
||||
private val binding: ItemRoomCategoryBinding,
|
||||
onClickAction: (() -> Unit)
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
init {
|
||||
binding.root.setOnClickListener(DebouncedClickListener({
|
||||
onClickAction.invoke()
|
||||
}))
|
||||
}
|
||||
|
||||
fun bind(section: SectionViewModel) {
|
||||
binding.roomCategoryTitleView.text = section.name
|
||||
val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.riotx_text_secondary)
|
||||
val expandedArrowDrawableRes = if (section.isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white
|
||||
val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also {
|
||||
DrawableCompat.setTint(it, tintColor)
|
||||
}
|
||||
binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(section.notificationCount, section.isHighlighted))
|
||||
binding.roomCategoryTitleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(parent: ViewGroup, onClickAction: () -> Unit): VH {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_room_category, parent, false)
|
||||
val binding = ItemRoomCategoryBinding.bind(view)
|
||||
return VH(binding, onClickAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user