mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Merge pull request #3092 from vector-im/feature/bca/paged_room_list
Room List performance PR (use Live PagedList via Monarchy)
This commit is contained in:
commit
3109d111a4
@ -14,6 +14,7 @@ Improvements 🙌:
|
||||
- Update reactions to Unicode 13.1 (#2998)
|
||||
- Be more robust when parsing some enums
|
||||
- Improve timeline filtering (dissociate membership and profile events, display hidden events when highlighted, fix hidden item/read receipts behavior)
|
||||
- Room list improvements (paging)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix bad theme change for the MainActivity
|
||||
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) 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
|
||||
|
||||
enum class RoomCategoryFilter {
|
||||
ONLY_DM,
|
||||
ONLY_ROOMS,
|
||||
ONLY_WITH_NOTIFICATIONS,
|
||||
ALL
|
||||
}
|
@ -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
|
||||
@ -24,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
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.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||
@ -178,4 +180,29 @@ 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>)
|
||||
|
||||
/**
|
||||
* TODO Doc
|
||||
*/
|
||||
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData<PagedList<RoomSummary>>
|
||||
|
||||
/**
|
||||
* TODO Doc
|
||||
*/
|
||||
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult
|
||||
|
||||
/**
|
||||
* TODO Doc
|
||||
*/
|
||||
fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount
|
||||
|
||||
private val defaultPagedListConfig
|
||||
get() = PagedList.Config.Builder()
|
||||
.setPageSize(10)
|
||||
.setInitialLoadSizeHint(20)
|
||||
.setEnablePlaceholders(false)
|
||||
.setPrefetchDistance(10)
|
||||
.build()
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
package org.matrix.android.sdk.api.session.room
|
||||
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
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 {
|
||||
@ -31,7 +33,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 +44,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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* 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
|
||||
* 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,
|
||||
@ -14,12 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home
|
||||
package org.matrix.android.sdk.api.session.room
|
||||
|
||||
import im.vector.app.core.utils.BehaviorDataSource
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class HomeRoomListDataSource @Inject constructor() : BehaviorDataSource<List<RoomSummary>>()
|
||||
interface UpdatableFilterLivePageResult {
|
||||
val livePagedList: LiveData<PagedList<RoomSummary>>
|
||||
|
||||
fun updateQuery(queryParams: RoomSummaryQueryParams)
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.session.room.summary
|
||||
|
||||
data class RoomAggregateNotificationCount(
|
||||
val notificationCount: Int,
|
||||
val highlightCount: Int
|
||||
) {
|
||||
val totalCount = notificationCount + highlightCount
|
||||
val 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,61 +16,217 @@
|
||||
|
||||
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
|
||||
|
||||
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(): List<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.forEach { it.deleteFromRealm() }
|
||||
newTags.forEach { newTag ->
|
||||
if (tags.all { it.tagName != newTag.first }) {
|
||||
// we must add it
|
||||
tags.add(
|
||||
RoomTagEntity(newTag.first, newTag.second)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
isFavourite = newTags.any { it.first == RoomTag.ROOM_TAG_FAVOURITE }
|
||||
isLowPriority = newTags.any { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY }
|
||||
isServerNotice = newTags.any { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE }
|
||||
}
|
||||
|
||||
@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.distinct().sorted() != aliases.distinct().sorted()) {
|
||||
aliases.clear()
|
||||
aliases.addAll(newAliases)
|
||||
flatAliases = newAliases.joinToString(separator = "|", prefix = "|")
|
||||
}
|
||||
}
|
||||
|
||||
// this is required for querying
|
||||
var flatAliases: String = ""
|
||||
|
||||
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) {
|
||||
membershipStr = value.name
|
||||
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) {
|
||||
versioningStateStr = value.name
|
||||
if (value.name != versioningStateStr) {
|
||||
versioningStateStr = value.name
|
||||
}
|
||||
}
|
||||
|
||||
var roomEncryptionTrustLevel: RoomEncryptionTrustLevel?
|
||||
@ -84,7 +240,9 @@ internal open class RoomSummaryEntity(
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
roomEncryptionTrustLevelStr = value?.name
|
||||
if (value?.name != roomEncryptionTrustLevelStr) {
|
||||
roomEncryptionTrustLevelStr = value?.name
|
||||
}
|
||||
}
|
||||
|
||||
companion object
|
||||
|
@ -18,17 +18,20 @@ 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
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.RoomService
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
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.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
@ -96,6 +99,20 @@ internal class DefaultRoomService @Inject constructor(
|
||||
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
|
||||
}
|
||||
|
||||
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
|
||||
: LiveData<PagedList<RoomSummary>> {
|
||||
return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig)
|
||||
}
|
||||
|
||||
override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
|
||||
: UpdatableFilterLivePageResult {
|
||||
return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig)
|
||||
}
|
||||
|
||||
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount {
|
||||
return roomSummaryDataSource.getNotificationCountForRooms(queryParams)
|
||||
}
|
||||
|
||||
override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
||||
return roomSummaryDataSource.getBreadcrumbs(queryParams)
|
||||
}
|
||||
|
@ -17,18 +17,19 @@
|
||||
package org.matrix.android.sdk.internal.session.room.create
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
|
||||
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
|
||||
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
@ -96,12 +97,18 @@ internal class DefaultCreateRoomTask @Inject constructor(
|
||||
// Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
|
||||
try {
|
||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||
realm.where(RoomEntity::class.java)
|
||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||
realm.where(RoomSummaryEntity::class.java)
|
||||
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
|
||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
}
|
||||
} catch (exception: TimeoutCancellationException) {
|
||||
throw CreateRoomFailure.CreatedWithTimeout
|
||||
}
|
||||
|
||||
Realm.getInstance(realmConfiguration).executeTransactionAsync {
|
||||
RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
if (otherUserId != null) {
|
||||
handleDirectChatCreation(roomId, otherUserId)
|
||||
}
|
||||
|
@ -16,20 +16,23 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.session.room.membership.joining
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
|
||||
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.internal.database.awaitNotEmptyResult
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -68,12 +71,18 @@ internal class DefaultJoinRoomTask @Inject constructor(
|
||||
val roomId = joinRoomResponse.roomId
|
||||
try {
|
||||
awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
|
||||
realm.where(RoomEntity::class.java)
|
||||
.equalTo(RoomEntityFields.ROOM_ID, roomId)
|
||||
realm.where(RoomSummaryEntity::class.java)
|
||||
.equalTo(RoomSummaryEntityFields.ROOM_ID, roomId)
|
||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
}
|
||||
} catch (exception: TimeoutCancellationException) {
|
||||
throw JoinRoomFailure.JoinedWithTimeout
|
||||
}
|
||||
|
||||
Realm.getInstance(realmConfiguration).executeTransactionAsync {
|
||||
RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
setReadMarkers(roomId)
|
||||
}
|
||||
|
||||
|
@ -18,10 +18,18 @@ 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.query.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
|
||||
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
|
||||
@ -32,8 +40,6 @@ 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.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,62 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
|
||||
.sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX)
|
||||
}
|
||||
|
||||
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config): 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, pagedListConfig)
|
||||
)
|
||||
}
|
||||
|
||||
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
pagedListConfig: PagedList.Config): 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, pagedListConfig)
|
||||
)
|
||||
|
||||
return object : UpdatableFilterLivePageResult {
|
||||
override val livePagedList: LiveData<PagedList<RoomSummary>> = 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 +167,28 @@ 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.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
|
||||
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 { sn ->
|
||||
query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn)
|
||||
}
|
||||
}
|
||||
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,9 +117,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||
|
||||
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases
|
||||
.orEmpty()
|
||||
roomSummaryEntity.aliases.clear()
|
||||
roomSummaryEntity.aliases.addAll(roomAliases)
|
||||
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
|
||||
roomSummaryEntity.updateAliases(roomAliases)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1
|
||||
# android\.text\.TextUtils
|
||||
|
||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||
enum class===93
|
||||
enum class===94
|
||||
|
||||
### Do not import temporary legacy classes
|
||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||
|
@ -19,78 +19,26 @@ package im.vector.app
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import arrow.core.Option
|
||||
import im.vector.app.features.grouplist.ALL_COMMUNITIES_GROUP_ID
|
||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||
import im.vector.app.features.home.HomeRoomListDataSource
|
||||
import im.vector.app.features.home.room.list.ChronologicalRoomComparator
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.rxkotlin.addTo
|
||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* This class handles the global app state. At the moment, it only manages room list.
|
||||
* This class handles the global app state.
|
||||
* It requires to be added to ProcessLifecycleOwner.get().lifecycle
|
||||
*/
|
||||
// TODO Keep this class for now, will maybe be used fro Space
|
||||
@Singleton
|
||||
class AppStateHandler @Inject constructor(
|
||||
private val sessionDataSource: ActiveSessionDataSource,
|
||||
private val homeRoomListDataSource: HomeRoomListDataSource,
|
||||
private val selectedGroupDataSource: SelectedGroupDataSource,
|
||||
private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver {
|
||||
class AppStateHandler @Inject constructor() : LifecycleObserver {
|
||||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun entersForeground() {
|
||||
observeRoomsAndGroup()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
fun entersBackground() {
|
||||
compositeDisposable.clear()
|
||||
}
|
||||
|
||||
private fun observeRoomsAndGroup() {
|
||||
Observable
|
||||
.combineLatest<List<RoomSummary>, Option<GroupSummary>, List<RoomSummary>>(
|
||||
sessionDataSource.observe()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.switchMap {
|
||||
val query = roomSummaryQueryParams {}
|
||||
it.orNull()?.rx()?.liveRoomSummaries(query)
|
||||
?: Observable.just(emptyList())
|
||||
}
|
||||
.throttleLast(300, TimeUnit.MILLISECONDS),
|
||||
selectedGroupDataSource.observe(),
|
||||
BiFunction { rooms, selectedGroupOption ->
|
||||
val selectedGroup = selectedGroupOption.orNull()
|
||||
val filteredRooms = rooms.filter {
|
||||
if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) {
|
||||
true
|
||||
} else if (it.isDirect) {
|
||||
it.otherMemberIds
|
||||
.intersect(selectedGroup.userIds)
|
||||
.isNotEmpty()
|
||||
} else {
|
||||
selectedGroup.roomIds.contains(it.roomId)
|
||||
}
|
||||
}
|
||||
filteredRooms.sortedWith(chronologicalRoomComparator)
|
||||
}
|
||||
)
|
||||
.subscribe {
|
||||
homeRoomListDataSource.post(it)
|
||||
}
|
||||
.addTo(compositeDisposable)
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
||||
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
|
||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.HomeRoomListDataSource
|
||||
import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
||||
@ -113,8 +112,6 @@ interface VectorComponent {
|
||||
|
||||
fun errorFormatter(): ErrorFormatter
|
||||
|
||||
fun homeRoomListObservableStore(): HomeRoomListDataSource
|
||||
|
||||
fun selectedGroupStore(): SelectedGroupDataSource
|
||||
|
||||
fun roomDetailPendingActionStore(): RoomDetailPendingActionStore
|
||||
|
@ -127,6 +127,12 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||
Timber.i("onResume Fragment ${javaClass.simpleName}")
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
Timber.i("onPause Fragment ${javaClass.simpleName}")
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@ -149,7 +155,9 @@ abstract class VectorBaseFragment<VB: ViewBinding> : BaseMvRxFragment(), HasScre
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDestroy() {
|
||||
Timber.i("onDestroy Fragment ${javaClass.simpleName}")
|
||||
uiDisposables.dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class HomeDetailAction : VectorViewModelAction {
|
||||
data class SwitchDisplayMode(val displayMode: RoomListDisplayMode) : HomeDetailAction()
|
||||
object MarkAllRoomsRead : HomeDetailAction()
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ package im.vector.app.features.home
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -33,8 +35,8 @@ import im.vector.app.core.platform.ToolbarConfigurable
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.ui.views.CurrentCallsView
|
||||
import im.vector.app.core.ui.views.KnownCallsViewHolder
|
||||
import im.vector.app.core.ui.views.KeysBackupBanner
|
||||
import im.vector.app.core.ui.views.KnownCallsViewHolder
|
||||
import im.vector.app.databinding.FragmentHomeDetailBinding
|
||||
import im.vector.app.features.call.SharedKnownCallsViewModel
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
@ -49,7 +51,6 @@ import im.vector.app.features.themes.ThemeUtils
|
||||
import im.vector.app.features.workers.signout.BannerState
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewModel
|
||||
import im.vector.app.features.workers.signout.ServerBackupStatusViewState
|
||||
|
||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
@ -79,6 +80,32 @@ class HomeDetailFragment @Inject constructor(
|
||||
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
||||
private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel
|
||||
|
||||
private var hasUnreadRooms = false
|
||||
set(value) {
|
||||
if (value != field) {
|
||||
field = value
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMenuRes() = R.menu.room_list
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_home_mark_all_as_read -> {
|
||||
viewModel.handle(HomeDetailAction.MarkAllRoomsRead)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeDetailBinding {
|
||||
return FragmentHomeDetailBinding.inflate(inflater, container, false)
|
||||
}
|
||||
@ -314,6 +341,8 @@ class HomeDetailFragment @Inject constructor(
|
||||
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
|
||||
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
|
||||
views.syncStateView.render(it.syncState)
|
||||
|
||||
hasUnreadRooms = it.hasUnreadMessages
|
||||
}
|
||||
|
||||
private fun BadgeDrawable.render(count: Int, highlight: Boolean) {
|
||||
|
@ -16,22 +16,30 @@
|
||||
|
||||
package im.vector.app.features.home
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.di.HasScreenInjector
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.features.grouplist.SelectedGroupDataSource
|
||||
import im.vector.app.features.ui.UiStateRepository
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.query.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||
import org.matrix.android.sdk.rx.asObservable
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* View model used to update the home bottom bar notification counts, observe the sync state and
|
||||
@ -41,7 +49,6 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||
private val session: Session,
|
||||
private val uiStateRepository: UiStateRepository,
|
||||
private val selectedGroupStore: SelectedGroupDataSource,
|
||||
private val homeRoomListStore: HomeRoomListDataSource,
|
||||
private val stringProvider: StringProvider)
|
||||
: VectorViewModel<HomeDetailViewState, HomeDetailAction, EmptyViewEvents>(initialState) {
|
||||
|
||||
@ -75,6 +82,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||
override fun handle(action: HomeDetailAction) {
|
||||
when (action) {
|
||||
is HomeDetailAction.SwitchDisplayMode -> handleSwitchDisplayMode(action)
|
||||
HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead()
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +98,26 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleMarkAllRoomsRead() = withState { _ ->
|
||||
// questionable to use viewmodelscope
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
val roomIds = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
|
||||
}
|
||||
)
|
||||
.map { it.roomId }
|
||||
try {
|
||||
awaitCallback<Unit> {
|
||||
session.markAllAsRead(roomIds, it)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
Timber.d(failure, "Failed to mark all as read")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeSyncState() {
|
||||
session.rx()
|
||||
.liveSyncState()
|
||||
@ -113,43 +141,51 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho
|
||||
}
|
||||
|
||||
private fun observeRoomSummaries() {
|
||||
homeRoomListStore
|
||||
.observe()
|
||||
.observeOn(Schedulers.computation())
|
||||
.map { it.asSequence() }
|
||||
.subscribe { summaries ->
|
||||
val invitesDm = summaries
|
||||
.filter { it.membership == Membership.INVITE && it.isDirect }
|
||||
.count()
|
||||
session.getPagedRoomSummariesLive(
|
||||
roomSummaryQueryParams {
|
||||
memberships = Membership.activeMemberships()
|
||||
}
|
||||
)
|
||||
.asObservable()
|
||||
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||
.subscribe {
|
||||
val dmInvites = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.INVITE)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
}
|
||||
).size
|
||||
|
||||
val invitesRoom = summaries
|
||||
.filter { it.membership == Membership.INVITE && it.isDirect.not() }
|
||||
.count()
|
||||
val roomsInvite = session.getRoomSummaries(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.INVITE)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
}
|
||||
).size
|
||||
|
||||
val peopleNotifications = summaries
|
||||
.filter { it.isDirect }
|
||||
.map { it.notificationCount }
|
||||
.sum()
|
||||
val peopleHasHighlight = summaries
|
||||
.filter { it.isDirect }
|
||||
.any { it.highlightCount > 0 }
|
||||
val dmRooms = session.getNotificationCountForRooms(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
}
|
||||
)
|
||||
|
||||
val roomsNotifications = summaries
|
||||
.filter { !it.isDirect }
|
||||
.map { it.notificationCount }
|
||||
.sum()
|
||||
val roomsHasHighlight = summaries
|
||||
.filter { !it.isDirect }
|
||||
.any { it.highlightCount > 0 }
|
||||
val otherRooms = session.getNotificationCountForRooms(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
|
||||
}
|
||||
)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
notificationCountCatchup = peopleNotifications + roomsNotifications + invitesDm + invitesRoom,
|
||||
notificationHighlightCatchup = peopleHasHighlight || roomsHasHighlight,
|
||||
notificationCountPeople = peopleNotifications + invitesDm,
|
||||
notificationHighlightPeople = peopleHasHighlight || invitesDm > 0,
|
||||
notificationCountRooms = roomsNotifications + invitesRoom,
|
||||
notificationHighlightRooms = roomsHasHighlight || invitesRoom > 0
|
||||
notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites,
|
||||
notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight,
|
||||
notificationCountPeople = dmRooms.totalCount + dmInvites,
|
||||
notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0,
|
||||
notificationCountRooms = otherRooms.totalCount + roomsInvite,
|
||||
notificationHighlightRooms = otherRooms.isHighlight || roomsInvite > 0,
|
||||
hasUnreadMessages = dmRooms.totalCount + otherRooms.totalCount > 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -34,5 +34,6 @@ data class HomeDetailViewState(
|
||||
val notificationHighlightPeople: Boolean = false,
|
||||
val notificationCountRooms: Int = 0,
|
||||
val notificationHighlightRooms: Boolean = false,
|
||||
val hasUnreadMessages: Boolean = false,
|
||||
val syncState: SyncState = SyncState.Idle
|
||||
) : MvRxState
|
||||
|
@ -21,36 +21,44 @@ import android.content.pm.ShortcutManager
|
||||
import android.os.Build
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import io.reactivex.Observable
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import io.reactivex.disposables.Disposables
|
||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||
import org.matrix.android.sdk.rx.asObservable
|
||||
import javax.inject.Inject
|
||||
|
||||
class ShortcutsHandler @Inject constructor(
|
||||
private val context: Context,
|
||||
private val homeRoomListStore: HomeRoomListDataSource,
|
||||
private val shortcutCreator: ShortcutCreator
|
||||
private val shortcutCreator: ShortcutCreator,
|
||||
private val activeSessionHolder: ActiveSessionHolder
|
||||
) {
|
||||
|
||||
fun observeRoomsAndBuildShortcuts(): Disposable {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||
// No op
|
||||
return Observable.empty<Unit>().subscribe()
|
||||
return Disposables.empty()
|
||||
}
|
||||
|
||||
return homeRoomListStore
|
||||
.observe()
|
||||
.distinctUntilChanged()
|
||||
.observeOn(Schedulers.computation())
|
||||
.subscribe { rooms ->
|
||||
return activeSessionHolder.getSafeActiveSession()
|
||||
?.getPagedRoomSummariesLive(
|
||||
roomSummaryQueryParams {
|
||||
memberships = listOf(Membership.JOIN)
|
||||
roomTagQueryFilter = RoomTagQueryFilter(isFavorite = true, null, null)
|
||||
}
|
||||
)
|
||||
?.asObservable()
|
||||
?.subscribe { rooms ->
|
||||
val shortcuts = rooms
|
||||
.filter { room -> room.isFavorite }
|
||||
.take(n = 4) // Android only allows us to create 4 shortcuts
|
||||
.map { shortcutCreator.create(it) }
|
||||
|
||||
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
|
||||
ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
|
||||
}
|
||||
?: Disposables.empty()
|
||||
}
|
||||
|
||||
fun clearShortcuts() {
|
||||
|
@ -22,12 +22,11 @@ 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: RoomsSection) : RoomListAction()
|
||||
data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
||||
data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction()
|
||||
data class FilterWith(val filter: String) : RoomListAction()
|
||||
data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction()
|
||||
data class ToggleTag(val roomId: String, val tag: String) : RoomListAction()
|
||||
data class LeaveRoom(val roomId: String) : RoomListAction()
|
||||
object MarkAllRoomsRead : 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -20,19 +20,15 @@ import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
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 +40,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 +50,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 +62,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 {
|
||||
|
||||
@ -85,28 +82,25 @@ class RoomListFragment @Inject constructor(
|
||||
return FragmentRoomListBinding.inflate(inflater, container, false)
|
||||
}
|
||||
|
||||
private var hasUnreadRooms = false
|
||||
data class SectionKey(
|
||||
val name: String,
|
||||
val isExpanded: Boolean,
|
||||
val notifyOfLocalEcho: Boolean
|
||||
)
|
||||
|
||||
override fun getMenuRes() = R.menu.room_list
|
||||
data class SectionAdapterInfo(
|
||||
var section: SectionKey,
|
||||
val headerHeaderAdapter: SectionHeaderAdapter,
|
||||
val contentAdapter: RoomSummaryPagedController
|
||||
)
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_home_mark_all_as_read -> {
|
||||
roomListViewModel.handle(RoomListAction.MarkAllRoomsRead)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
}
|
||||
private val adapterInfosList = mutableListOf<SectionAdapterInfo>()
|
||||
private var concatAdapter : ConcatAdapter? = null
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
views.stateView.contentView = views.roomListView
|
||||
views.stateView.state = StateView.State.Loading
|
||||
setupCreateRoomButton()
|
||||
setupRecyclerView()
|
||||
sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java)
|
||||
@ -125,6 +119,40 @@ class RoomListFragment @Inject constructor(
|
||||
.observe()
|
||||
.subscribe { handleQuickActions(it) }
|
||||
.disposeOnDestroyView()
|
||||
|
||||
roomListViewModel.selectSubscribe(viewLifecycleOwner, RoomListViewState::roomMembershipChanges) { ms ->
|
||||
// it's for invites local echo
|
||||
adapterInfosList.filter { it.section.notifyOfLocalEcho }
|
||||
.onEach {
|
||||
it.contentAdapter.roomChangeMembershipStates = ms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.roomsSectionData.copy(isExpanded = isRoomSectionExpanded)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showFailure(throwable: Throwable) {
|
||||
@ -132,12 +160,15 @@ class RoomListFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
roomController.removeModelBuildListener(modelBuildListener)
|
||||
adapterInfosList.onEach { it.contentAdapter.removeModelBuildListener(modelBuildListener) }
|
||||
adapterInfosList.clear()
|
||||
modelBuildListener = null
|
||||
views.roomListView.cleanup()
|
||||
roomController.listener = null
|
||||
footerController.listener = null
|
||||
// TODO Cleanup listener on the ConcatAdapter's adapters?
|
||||
stateRestorer.clear()
|
||||
views.createChatFabMenu.listener = null
|
||||
concatAdapter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@ -204,13 +235,58 @@ class RoomListFragment @Inject constructor(
|
||||
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
|
||||
views.roomListView.layoutManager = layoutManager
|
||||
views.roomListView.itemAnimator = RoomListAnimator()
|
||||
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()
|
||||
|
||||
roomListViewModel.sections.forEach { section ->
|
||||
val sectionAdapter = SectionHeaderAdapter {
|
||||
roomListViewModel.handle(RoomListAction.ToggleSection(section))
|
||||
}.also {
|
||||
it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName))
|
||||
}
|
||||
|
||||
val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController()
|
||||
.also { controller ->
|
||||
section.livePages.observe(viewLifecycleOwner) { pl ->
|
||||
controller.submitList(pl)
|
||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(isHidden = pl.isEmpty()))
|
||||
checkEmptyState()
|
||||
}
|
||||
section.notificationCount.observe(viewLifecycleOwner) { counts ->
|
||||
sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(
|
||||
notificationCount = counts.totalCount,
|
||||
isHighlighted = counts.isHighlight
|
||||
))
|
||||
}
|
||||
section.isExpanded.observe(viewLifecycleOwner) { _ ->
|
||||
refreshCollapseStates()
|
||||
}
|
||||
controller.listener = this
|
||||
}
|
||||
adapterInfosList.add(
|
||||
SectionAdapterInfo(
|
||||
SectionKey(
|
||||
name = section.sectionName,
|
||||
isExpanded = section.isExpanded.value.orTrue(),
|
||||
notifyOfLocalEcho = section.notifyOfLocalEcho
|
||||
),
|
||||
sectionAdapter,
|
||||
contentAdapter
|
||||
)
|
||||
)
|
||||
concatAdapter.addAdapter(sectionAdapter)
|
||||
concatAdapter.addAdapter(contentAdapter.adapter)
|
||||
}
|
||||
|
||||
// Add the footer controller
|
||||
footerController.listener = this
|
||||
concatAdapter.addAdapter(footerController.adapter)
|
||||
|
||||
this.concatAdapter = concatAdapter
|
||||
views.roomListView.adapter = concatAdapter
|
||||
}
|
||||
|
||||
private val showFabRunnable = Runnable {
|
||||
@ -278,89 +354,41 @@ 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)
|
||||
// Mark all as read menu
|
||||
when (roomListParams.displayMode) {
|
||||
RoomListDisplayMode.NOTIFICATIONS,
|
||||
RoomListDisplayMode.PEOPLE,
|
||||
RoomListDisplayMode.ROOMS -> {
|
||||
val newValue = state.hasUnread
|
||||
if (hasUnreadRooms != newValue) {
|
||||
hasUnreadRooms = newValue
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
footerController.setData(state)
|
||||
}
|
||||
|
||||
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()
|
||||
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 {
|
||||
private fun checkEmptyState() {
|
||||
val hasNoRoom = adapterInfosList.all { it.headerHeaderAdapter.roomsSectionData.isHidden }
|
||||
if (hasNoRoom) {
|
||||
val emptyState = when (roomListParams.displayMode) {
|
||||
RoomListDisplayMode.NOTIFICATIONS -> {
|
||||
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),
|
||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm),
|
||||
isBigImage = true,
|
||||
message = getString(R.string.room_list_people_empty_body)
|
||||
)
|
||||
RoomListDisplayMode.ROOMS ->
|
||||
StateView.State.Empty(
|
||||
title = getString(R.string.room_list_rooms_empty_title),
|
||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room),
|
||||
isBigImage = true,
|
||||
message = getString(R.string.room_list_rooms_empty_body)
|
||||
)
|
||||
else ->
|
||||
// Always display the content in this mode, because if the footer
|
||||
StateView.State.Content
|
||||
}
|
||||
RoomListDisplayMode.PEOPLE ->
|
||||
StateView.State.Empty(
|
||||
title = getString(R.string.room_list_people_empty_title),
|
||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm),
|
||||
isBigImage = true,
|
||||
message = getString(R.string.room_list_people_empty_body)
|
||||
)
|
||||
RoomListDisplayMode.ROOMS ->
|
||||
StateView.State.Empty(
|
||||
title = getString(R.string.room_list_rooms_empty_title),
|
||||
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room),
|
||||
isBigImage = true,
|
||||
message = getString(R.string.room_list_rooms_empty_body)
|
||||
)
|
||||
else ->
|
||||
// Always display the content in this mode, because if the footer
|
||||
StateView.State.Content
|
||||
views.stateView.state = emptyState
|
||||
} else {
|
||||
views.stateView.state = StateView.State.Content
|
||||
}
|
||||
views.stateView.state = emptyState
|
||||
}
|
||||
|
||||
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 +405,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 +426,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,61 @@
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.RoomCategoryFilter
|
||||
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
|
||||
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 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.asObservable
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
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>>)
|
||||
: VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
|
||||
class RoomListViewModel @Inject constructor(
|
||||
initialState: RoomListViewState,
|
||||
private val session: Session,
|
||||
private val stringProvider: StringProvider
|
||||
) : VectorViewModel<RoomListViewState, RoomListAction, RoomListViewEvents>(initialState) {
|
||||
|
||||
interface Factory {
|
||||
fun create(initialState: RoomListViewState): RoomListViewModel
|
||||
}
|
||||
|
||||
private var updatableQuery: UpdatableFilterLivePageResult? = null
|
||||
|
||||
init {
|
||||
observeMembershipChanges()
|
||||
}
|
||||
|
||||
private fun observeMembershipChanges() {
|
||||
session.rx()
|
||||
.liveRoomChangeMembershipState()
|
||||
.subscribe {
|
||||
setState { copy(roomMembershipChanges = it) }
|
||||
}
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
companion object : MvRxViewModelFactory<RoomListViewModel, RoomListViewState> {
|
||||
|
||||
@JvmStatic
|
||||
@ -56,28 +80,136 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
}
|
||||
}
|
||||
|
||||
private val displayMode = initialState.displayMode
|
||||
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
|
||||
val sections: List<RoomsSection> by lazy {
|
||||
val sections = mutableListOf<RoomsSection>()
|
||||
if (initialState.displayMode == RoomListDisplayMode.PEOPLE) {
|
||||
addSection(sections, R.string.invitations_header, true) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
}
|
||||
|
||||
init {
|
||||
observeRoomSummaries()
|
||||
observeMembershipChanges()
|
||||
addSection(sections, R.string.bottom_action_favourites) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
|
||||
it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null)
|
||||
}
|
||||
|
||||
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, true) {
|
||||
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()
|
||||
},
|
||||
{ qpm ->
|
||||
val name = stringProvider.getString(R.string.bottom_action_rooms)
|
||||
session.getFilteredPagedRoomSummariesLive(qpm)
|
||||
.let { updatableFilterLivePageResult ->
|
||||
updatableQuery = updatableFilterLivePageResult
|
||||
sections.add(RoomsSection(name, updatableFilterLivePageResult.livePagedList))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) {
|
||||
addSection(sections, R.string.invitations_header, true) {
|
||||
it.memberships = listOf(Membership.INVITE)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ALL
|
||||
}
|
||||
|
||||
addSection(sections, R.string.bottom_action_rooms, true) {
|
||||
it.memberships = listOf(Membership.JOIN)
|
||||
it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS
|
||||
}
|
||||
}
|
||||
|
||||
sections
|
||||
}
|
||||
|
||||
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)
|
||||
is RoomListAction.MarkAllRoomsRead -> handleMarkAllRoomsRead()
|
||||
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,
|
||||
notifyOfLocalEcho: Boolean = false,
|
||||
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(
|
||||
sectionName = name,
|
||||
livePages = livePagedList,
|
||||
notifyOfLocalEcho = notifyOfLocalEcho
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
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 +220,14 @@ 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(roomSection: RoomsSection) {
|
||||
roomSection.isExpanded.postValue(!roomSection.isExpanded.value.orFalse())
|
||||
/* TODO Cleanup if it is working
|
||||
sections.find { it.sectionName == roomSection.sectionName }
|
||||
?.let { section ->
|
||||
section.isExpanded.postValue(!section.isExpanded.value.orFalse())
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private fun handleFilter(action: RoomListAction.FilterWith) {
|
||||
@ -98,23 +236,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
roomFilter = action.filter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
updatableQuery?.updateQuery(
|
||||
roomSummaryQueryParams {
|
||||
memberships = Membership.activeMemberships()
|
||||
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state ->
|
||||
@ -126,6 +253,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState,
|
||||
return@withState
|
||||
}
|
||||
|
||||
// quick echo
|
||||
setState {
|
||||
copy(
|
||||
roomMembershipChanges = roomMembershipChanges.mapValues {
|
||||
if (it.key == roomId) {
|
||||
ChangeMembershipState.Joining
|
||||
} else {
|
||||
it.value
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val room = session.getRoom(roomId) ?: return@withState
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
@ -163,15 +303,6 @@ 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()) }
|
||||
}
|
||||
|
||||
private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) {
|
||||
val room = session.getRoom(action.roomId)
|
||||
if (room != null) {
|
||||
@ -226,46 +357,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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,73 +16,15 @@
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
typealias RoomSummaries = LinkedHashMap<RoomCategory, List<RoomSummary>>
|
||||
|
||||
enum class RoomCategory(@StringRes val titleRes: Int) {
|
||||
INVITE(R.string.invitations_header),
|
||||
FAVOURITE(R.string.bottom_action_favourites),
|
||||
DIRECT(R.string.bottom_action_people_x),
|
||||
GROUP(R.string.bottom_action_rooms),
|
||||
LOW_PRIORITY(R.string.low_priority_header),
|
||||
SERVER_NOTICE(R.string.system_alerts_header)
|
||||
}
|
||||
|
||||
fun RoomSummaries?.isNullOrEmpty(): Boolean {
|
||||
return this == null || this.values.flatten().isEmpty()
|
||||
}
|
||||
|
@ -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,68 @@
|
||||
/*
|
||||
* 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.utils.createUIHandler
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import javax.inject.Inject
|
||||
|
||||
class RoomSummaryPagedControllerFactory @Inject constructor(
|
||||
private val roomSummaryItemFactory: RoomSummaryItemFactory
|
||||
) {
|
||||
|
||||
fun createRoomSummaryPagedController(): RoomSummaryPagedController {
|
||||
return RoomSummaryPagedController(roomSummaryItemFactory)
|
||||
}
|
||||
}
|
||||
|
||||
class RoomSummaryPagedController(
|
||||
private val roomSummaryItemFactory: RoomSummaryItemFactory
|
||||
) : PagedListEpoxyController<RoomSummary>(
|
||||
// Important it must match the PageList builder notify Looper
|
||||
modelBuildingHandler = createUIHandler()
|
||||
) {
|
||||
|
||||
var listener: RoomListListener? = null
|
||||
|
||||
var roomChangeMembershipStates: Map<String, ChangeMembershipState>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
// ideally we could search for visible models and update only those
|
||||
requestForcedModelBuild()
|
||||
}
|
||||
|
||||
override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> {
|
||||
// for place holder if enabled
|
||||
item ?: return roomSummaryItemFactory.createRoomItem(
|
||||
roomSummary = RoomSummary(
|
||||
roomId = "null_item_pos_$currentPosition",
|
||||
name = "",
|
||||
encryptionEventTs = null,
|
||||
isEncrypted = false,
|
||||
typingUsers = emptyList()
|
||||
),
|
||||
selectedRoomIds = emptySet(),
|
||||
onClick = null,
|
||||
onLongClick = null
|
||||
)
|
||||
|
||||
return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), listener)
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.PagedList
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
|
||||
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 notifyOfLocalEcho: Boolean = false
|
||||
)
|
@ -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 RoomsSectionData(
|
||||
val name: String,
|
||||
val isExpanded: Boolean = true,
|
||||
val notificationCount: Int = 0,
|
||||
val isHighlighted: Boolean = false,
|
||||
val isHidden: Boolean = true
|
||||
)
|
||||
|
||||
lateinit var roomsSectionData: RoomsSectionData
|
||||
private set
|
||||
|
||||
fun updateSection(newRoomsSectionData: RoomsSectionData) {
|
||||
if (!::roomsSectionData.isInitialized || newRoomsSectionData != roomsSectionData) {
|
||||
roomsSectionData = newRoomsSectionData
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int) = roomsSectionData.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(roomsSectionData)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = if (roomsSectionData.isHidden) 0 else 1
|
||||
|
||||
class VH constructor(
|
||||
private val binding: ItemRoomCategoryBinding,
|
||||
onClickAction: (() -> Unit)
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
init {
|
||||
binding.root.setOnClickListener(DebouncedClickListener({
|
||||
onClickAction.invoke()
|
||||
}))
|
||||
}
|
||||
|
||||
fun bind(roomsSectionData: RoomsSectionData) {
|
||||
binding.roomCategoryTitleView.text = roomsSectionData.name
|
||||
val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.riotx_text_secondary)
|
||||
val expandedArrowDrawableRes = if (roomsSectionData.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(roomsSectionData.notificationCount, roomsSectionData.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…
Reference in New Issue
Block a user