From cf868f885f1225b54d8c7f5ec1997fc59b0807fe Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 10:26:37 +0200 Subject: [PATCH 01/18] Room summary paged initial commit --- .../android/sdk/api/query/QueryStringValue.kt | 1 + .../sdk/api/query/RoomTagQueryFilter.kt | 23 ++ .../sdk/api/session/room/RoomService.kt | 8 + .../session/room/RoomSummaryQueryParams.kt | 17 +- .../summary/RoomAggregateNotificationCount.kt | 26 ++ .../database/RealmSessionStoreMigration.kt | 47 +++- .../SessionRealmConfigurationFactory.kt | 1 + .../database/mapper/RoomSummaryMapper.kt | 2 +- .../database/model/RoomSummaryEntity.kt | 230 +++++++++++++--- .../session/room/DefaultRoomService.kt | 19 ++ .../room/summary/RoomSummaryDataSource.kt | 96 ++++++- .../room/summary/RoomSummaryUpdater.kt | 10 +- .../internal/session/sync/RoomTagHandler.kt | 10 +- .../java/im/vector/app/AppStateHandler.kt | 2 +- .../app/core/platform/VectorBaseFragment.kt | 6 + .../features/home/room/list/RoomListAction.kt | 2 +- .../room/list/RoomListFooterController.kt | 54 ++++ .../home/room/list/RoomListFragment.kt | 249 +++++++++++------- .../home/room/list/RoomListListener.kt | 27 ++ .../home/room/list/RoomListViewModel.kt | 224 ++++++++++------ .../room/list/RoomListViewModelFactory.kt | 6 +- .../home/room/list/RoomListViewState.kt | 47 +--- .../home/room/list/RoomSummaryController.kt | 170 ------------ .../home/room/list/RoomSummaryItemFactory.kt | 4 +- .../room/list/RoomSummaryPagedController.kt | 71 +++++ .../home/room/list/SectionHeaderAdapter.kt | 100 +++++++ 26 files changed, 1007 insertions(+), 445 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 8f83beface..99a86e185b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -33,3 +33,4 @@ sealed class QueryStringValue { INSENSITIVE } } + diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt new file mode 100644 index 0000000000..613916bc18 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt @@ -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? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 5f02b77a1e..4814805dd8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData +import androidx.paging.PagedList import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState @@ -26,7 +27,9 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount /** * This interface defines methods to get rooms. It's implemented at the session level. @@ -178,4 +181,9 @@ interface RoomService { * This call will try to gather some information on this room, but it could fail and get nothing more */ fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) + + fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> + + fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index f859d74a6f..a57514023b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -17,12 +17,19 @@ package org.matrix.android.sdk.api.session.room import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.room.model.Membership fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams { return RoomSummaryQueryParams.Builder().apply(init).build() } +enum class RoomCategoryFilter { + ONLY_DM, + ONLY_ROOMS, + ALL +} + /** * This class can be used to filter room summaries to use with: * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] @@ -31,7 +38,9 @@ data class RoomSummaryQueryParams( val roomId: QueryStringValue, val displayName: QueryStringValue, val canonicalAlias: QueryStringValue, - val memberships: List + val memberships: List, + val roomCategoryFilter: RoomCategoryFilter?, + val roomTagQueryFilter: RoomTagQueryFilter? ) { class Builder { @@ -40,12 +49,16 @@ data class RoomSummaryQueryParams( var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition var memberships: List = Membership.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 ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt new file mode 100644 index 0000000000..2aa122c84e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.summary + +data class RoomAggregateNotificationCount( + val notificationCount: Int, + val highlightCount: Int +) { + fun totalCount() = notificationCount + highlightCount + + fun isHighlight() = highlightCount > 0 +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index c7fe7ab447..1daae906f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -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) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 244fe3432a..770ef46080 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -72,6 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) +// .deleteRealmIfMigrationNeeded() .migration(migration) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 2e54a4cd52..6dc70b60fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -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) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 37696c9082..6007ae504a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -16,61 +16,221 @@ package org.matrix.android.sdk.internal.database.model +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.Index +import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState -import io.realm.RealmList -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import timber.log.Timber internal open class RoomSummaryEntity( - @PrimaryKey var roomId: String = "", - var displayName: String? = "", - var avatarUrl: String? = "", - var name: String? = "", - var topic: String? = "", - var latestPreviewableEvent: TimelineEventEntity? = null, - var heroes: RealmList = RealmList(), - var joinedMembersCount: Int? = 0, - var invitedMembersCount: Int? = 0, - var isDirect: Boolean = false, - var directUserId: String? = null, - var otherMemberIds: RealmList = RealmList(), - var notificationCount: Int = 0, - var highlightCount: Int = 0, - var readMarkerId: String? = null, - var hasUnreadMessages: Boolean = false, - var tags: RealmList = RealmList(), - var userDrafts: UserDraftsEntity? = null, - var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS, - var canonicalAlias: String? = null, - var aliases: RealmList = 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 = 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 = 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 = RealmList() + + fun tags(): RealmList = tags + + fun updateTags(newTags: List>) { + val toDelete = mutableListOf() + tags.forEach { existingTag -> + val updatedTag = newTags.firstOrNull { it.first == existingTag.tagName } + if (updatedTag == null) { + toDelete.add(existingTag) + } else { + existingTag.tagOrder = updatedTag.second + } + } + toDelete.onEach { it.deleteFromRealm() } + newTags.forEach { newTag -> + if (tags.indexOfFirst { it.tagName == newTag.first } == -1) { + // we must add it + tags.add( + RoomTagEntity(newTag.first, newTag.second) + ) + } + } + + isFavourite = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_FAVOURITE } != -1 + isLowPriority = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY } != -1 + isServerNotice = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE } != -1 + } + + @Index + var isFavourite: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index + var isLowPriority: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index + var isServerNotice: Boolean = false + set(value) { + if (value != field) field = value + } + + var userDrafts: UserDraftsEntity? = null + set(value) { + if (value != field) field = value + } + + var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS + set(value) { + if (value != field) field = value + } + + var canonicalAlias: String? = null + set(value) { + if (value != field) field = value + } + + var aliases: RealmList = RealmList() + + fun updateAliases(newAliases: List) { + // only update underlying field if there is a diff + if (newAliases.toSet() != aliases.toSet()) { + Timber.w("VAL: aliases updated") + aliases.clear() + aliases.addAll(newAliases) + } + } + + // this is required for querying + var flatAliases: String = "" + set(value) { + if (value != field) field = value + } + + var isEncrypted: Boolean = false + set(value) { + if (value != field) field = value + } + + var encryptionEventTs: Long? = 0 + set(value) { + if (value != field) field = value + } + + var roomEncryptionTrustLevelStr: String? = null + set(value) { + if (value != field) field = value + } + + var inviterId: String? = null + set(value) { + if (value != field) field = value + } + + var hasFailedSending: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index private var membershipStr: String = Membership.NONE.name + var membership: Membership get() { return Membership.valueOf(membershipStr) } set(value) { - 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 +244,9 @@ internal open class RoomSummaryEntity( } } set(value) { - roomEncryptionTrustLevelStr = value?.name + if (value?.name != roomEncryptionTrustLevelStr) { + roomEncryptionTrustLevelStr = value?.name + } } companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 383dd876d3..5feb9c9d5a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event @@ -45,6 +46,7 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomT import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask import org.matrix.android.sdk.internal.task.TaskExecutor @@ -96,6 +98,18 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummariesLive(queryParams) } + override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : LiveData> { + return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams) + } + + override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : UpdatableFilterLivePageResult { + return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams) + } + + override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { + return roomSummaryDataSource.getNotificationCountForRooms(queryParams) + } + override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List { return roomSummaryDataSource.getBreadcrumbs(queryParams) } @@ -178,3 +192,8 @@ internal class DefaultRoomService @Inject constructor( .executeBy(taskExecutor) } } + +interface UpdatableFilterLivePageResult { + val livePagedList: LiveData> + fun updateQuery(queryParams: RoomSummaryQueryParams) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 107055b8c3..a806123237 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -18,10 +18,17 @@ package org.matrix.android.sdk.internal.session.room.summary import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.Sort +import org.matrix.android.sdk.api.session.room.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper @@ -31,9 +38,8 @@ import org.matrix.android.sdk.internal.database.query.findByAlias import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.query.process +import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.util.fetchCopyMap -import io.realm.Realm -import io.realm.RealmQuery import javax.inject.Inject internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, @@ -98,6 +104,71 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } + fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + roomSummariesQuery(realm, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + val dataSourceFactory = realmDataSourceFactory.map { + roomSummaryMapper.map(it) + } + return monarchy.findAllPagedWithChanges(realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, + PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build()) + ) + } + + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + roomSummariesQuery(realm, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + val dataSourceFactory = realmDataSourceFactory.map { + roomSummaryMapper.map(it) + } + + val mapped = monarchy.findAllPagedWithChanges(realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, + PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build()) + ) + + return object : UpdatableFilterLivePageResult { + override val livePagedList: LiveData> + get() = mapped + + override fun updateQuery(queryParams: RoomSummaryQueryParams) { + realmDataSourceFactory.updateQuery { + roomSummariesQuery(it, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + } + } + } + + fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { + var notificationCount: RoomAggregateNotificationCount? = null + monarchy.doWithRealm { realm -> + val roomSummariesQuery = roomSummariesQuery(realm, queryParams) + val notifCount = roomSummariesQuery.sum(RoomSummaryEntityFields.NOTIFICATION_COUNT).toInt() + val highlightCount = roomSummariesQuery.sum(RoomSummaryEntityFields.HIGHLIGHT_COUNT).toInt() + notificationCount = RoomAggregateNotificationCount( + notifCount, + highlightCount + ) + } + return notificationCount!! + } + private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery { val query = RoomSummaryEntity.where(realm) query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) @@ -105,6 +176,27 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) + + queryParams.roomCategoryFilter?.let { + when (it) { + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ALL -> { + // nop + } + } + } + queryParams.roomTagQueryFilter?.let { + it.isFavorite?.let { fav -> + query.equalTo(RoomSummaryEntityFields.IS_FAVOURITE, fav) + } + it.isLowPriority?.let { lp -> + query.equalTo(RoomSummaryEntityFields.IS_LOW_PRIORITY, lp) + } + it.isServerNotice?.let { lp -> + query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, lp) + } + } return query } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index cd1bb69612..69e1332dab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -98,6 +98,11 @@ internal class RoomSummaryUpdater @Inject constructor( val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) + val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs + if (lastActivityFromEvent != null) { + roomSummaryEntity.lastActivityTime = lastActivityFromEvent + } + roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 // avoid this call if we are sure there are unread events || !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) @@ -112,8 +117,9 @@ internal class RoomSummaryUpdater @Inject constructor( val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases .orEmpty() - roomSummaryEntity.aliases.clear() - roomSummaryEntity.aliases.addAll(roomAliases) +// roomSummaryEntity.aliases.clear() +// roomSummaryEntity.aliases.addAll(roomAliases) + roomSummaryEntity.updateAliases(roomAliases) roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|") roomSummaryEntity.isEncrypted = encryptionEvent != null roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt index f9ae41bc94..add5d841d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt @@ -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) } } diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 1e92f7bc67..d2b59e5513 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -52,7 +52,7 @@ class AppStateHandler @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { - observeRoomsAndGroup() +// observeRoomsAndGroup() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index f515060db6..e3a3b7b29c 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -127,6 +127,11 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre Timber.i("onResume Fragment ${javaClass.simpleName}") } + override fun onPause() { + super.onPause() + Timber.i("onPause Fragment ${javaClass.simpleName}") + } + @CallSuper override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -150,6 +155,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre } override fun onDestroy() { + Timber.i("onDestroy Fragment ${javaClass.simpleName}") uiDisposables.dispose() super.onDestroy() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt index 4a6c1c16fc..0d70f99b3a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat sealed class RoomListAction : VectorViewModelAction { data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction() - data class ToggleCategory(val category: RoomCategory) : RoomListAction() + data class ToggleSection(val section: RoomListViewModel.RoomsSection) : RoomListAction() data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction() data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction() data class FilterWith(val filter: String) : RoomListAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt new file mode 100644 index 0000000000..d4e062d1e4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt @@ -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() { + + 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)) + } + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 30cb360a9d..0e7e35654c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -27,12 +27,10 @@ import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.OnModelBuildFinishedListener -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete -import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -44,6 +42,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.list.actions.RoomListActionsArgs @@ -53,8 +52,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState @@ -66,12 +64,13 @@ data class RoomListParams( ) : Parcelable class RoomListFragment @Inject constructor( - private val roomController: RoomSummaryController, + private val pagedControllerFactory: RoomSummaryPagedControllerFactory, val roomListViewModelFactory: RoomListViewModel.Factory, private val notificationDrawerManager: NotificationDrawerManager, - private val sharedViewPool: RecyclerView.RecycledViewPool + private val footerController: RoomListFooterController, + private val userPreferencesProvider: UserPreferencesProvider ) : VectorBaseFragment(), - RoomSummaryController.Listener, + RoomListListener, OnBackPressed, NotifsFabMenuView.Listener { @@ -87,6 +86,20 @@ class RoomListFragment @Inject constructor( private var hasUnreadRooms = false + data class SectionKey( + val name: String, + val isExpanded: Boolean + ) + + data class SectionAdapterInfo( + var section: SectionKey, + val headerHeaderAdapter: SectionHeaderAdapter, + val contentAdapter: RoomSummaryPagedController + ) + + private val adapterInfosList = mutableListOf() + private var concatAdapter: ConcatAdapter? = null + override fun getMenuRes() = R.menu.room_list override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -112,10 +125,10 @@ class RoomListFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents { when (it) { - is RoomListViewEvents.Loading -> showLoading(it.message) - is RoomListViewEvents.Failure -> showFailure(it.throwable) + is RoomListViewEvents.Loading -> showLoading(it.message) + is RoomListViewEvents.Failure -> showFailure(it.throwable) is RoomListViewEvents.SelectRoom -> handleSelectRoom(it) - is RoomListViewEvents.Done -> Unit + is RoomListViewEvents.Done -> Unit }.exhaustive } @@ -127,15 +140,49 @@ class RoomListFragment @Inject constructor( .disposeOnDestroyView() } + private fun refreshCollapseStates() { + var contentInsertIndex = 1 + roomListViewModel.sections.forEachIndexed { index, roomsSection -> + val actualBlock = adapterInfosList[index] + val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue() + if (actualBlock.section.isExpanded && !isRoomSectionExpanded) { + // we have to remove the content adapter + concatAdapter?.removeAdapter(actualBlock.contentAdapter.adapter) + } else if (!actualBlock.section.isExpanded && isRoomSectionExpanded) { + // we must add it back! + concatAdapter?.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) + } + contentInsertIndex = if (isRoomSectionExpanded) { + contentInsertIndex + 2 + } else { + contentInsertIndex + 1 + } + actualBlock.section = actualBlock.section.copy( + isExpanded = isRoomSectionExpanded + ) + actualBlock.headerHeaderAdapter.updateSection( + actualBlock.headerHeaderAdapter.section.copy(isExpanded = isRoomSectionExpanded) + ) + } + } + override fun showFailure(throwable: Throwable) { showErrorInSnackbar(throwable) } override fun onDestroyView() { - roomController.removeModelBuildListener(modelBuildListener) + adapterInfosList.onEach { it.contentAdapter.removeModelBuildListener(modelBuildListener) } + adapterInfosList.clear() +// roomController.removeModelBuildListener(modelBuildListener) modelBuildListener = null views.roomListView.cleanup() - roomController.listener = null +// controllers.onEach { +// it.listener = null +// // concatAdapter.removeAdapter(it.adapter) +// } +// controllers.clear() +// roomController.listener = null +// favRoomController.listener = null stateRestorer.clear() views.createChatFabMenu.listener = null super.onDestroyView() @@ -148,8 +195,8 @@ class RoomListFragment @Inject constructor( private fun setupCreateRoomButton() { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true - RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true - RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true + RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true + RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true else -> Unit // No button in this mode } @@ -204,21 +251,68 @@ class RoomListFragment @Inject constructor( stateRestorer = LayoutManagerStateRestorer(layoutManager).register() views.roomListView.layoutManager = layoutManager views.roomListView.itemAnimator = RoomListAnimator() - views.roomListView.setRecycledViewPool(sharedViewPool) +// views.roomListView.setRecycledViewPool(sharedViewPool) layoutManager.recycleChildrenOnDetach = true - roomController.listener = this + modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } - roomController.addModelBuildListener(modelBuildListener) - views.roomListView.adapter = roomController.adapter - views.stateView.contentView = views.roomListView + + val concatAdapter = ConcatAdapter() + val hasOnlyOneSection = roomListViewModel.sections.size == 1 + roomListViewModel.sections.forEach { section -> + + val sectionAdapter = SectionHeaderAdapter { + roomListViewModel.handle(RoomListAction.ToggleSection(section)) + }.also { + it.updateSection(SectionHeaderAdapter.SectionViewModel( + section.sectionName + )) + } + + val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController().also { + section.livePages.observe(viewLifecycleOwner) { pl -> + it.submitList(pl) + sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty() || hasOnlyOneSection)) + checkEmptyState() + } + section.notificationCount.observe(viewLifecycleOwner) { counts -> + sectionAdapter.updateSection(sectionAdapter.section.copy( + notificationCount = counts.totalCount(), + isHighlighted = counts.isHighlight() + )) + } + section.isExpanded.observe(viewLifecycleOwner) { _ -> + refreshCollapseStates() + } + it.listener = this + } + adapterInfosList.add( + SectionAdapterInfo( + SectionKey( + name = section.sectionName, + isExpanded = section.isExpanded.value.orTrue() + ), + sectionAdapter, + contentAdapter + ) + ) + concatAdapter.addAdapter(sectionAdapter) + concatAdapter.addAdapter(contentAdapter.adapter) + } + + // Add the footer controller + this.footerController.listener = this + concatAdapter.addAdapter(footerController.adapter) + + this.concatAdapter = concatAdapter + views.roomListView.adapter = concatAdapter } private val showFabRunnable = Runnable { if (isAdded) { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show() - RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() - RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() + RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() + RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() else -> Unit } } @@ -226,28 +320,28 @@ class RoomListFragment @Inject constructor( private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { when (quickAction) { - is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { + is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY)) } - is RoomListQuickActionsSharedAction.NotificationsAll -> { + is RoomListQuickActionsSharedAction.NotificationsAll -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES)) } is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MENTIONS_ONLY)) } - is RoomListQuickActionsSharedAction.NotificationsMute -> { + is RoomListQuickActionsSharedAction.NotificationsMute -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MUTE)) } - is RoomListQuickActionsSharedAction.Settings -> { + is RoomListQuickActionsSharedAction.Settings -> { navigator.openRoomProfile(requireActivity(), quickAction.roomId) } - is RoomListQuickActionsSharedAction.Favorite -> { + is RoomListQuickActionsSharedAction.Favorite -> { roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_FAVOURITE)) } - is RoomListQuickActionsSharedAction.LowPriority -> { + is RoomListQuickActionsSharedAction.LowPriority -> { roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_LOW_PRIORITY)) } - is RoomListQuickActionsSharedAction.Leave -> { + is RoomListQuickActionsSharedAction.Leave -> { promptLeaveRoom(quickAction.roomId) } }.exhaustive @@ -278,12 +372,7 @@ class RoomListFragment @Inject constructor( } override fun invalidate() = withState(roomListViewModel) { state -> - when (state.asyncFilteredRooms) { - is Incomplete -> renderLoading() - is Success -> renderSuccess(state) - is Fail -> renderFailure(state.asyncFilteredRooms.error) - } - roomController.update(state) + footerController.setData(state) // Mark all as read menu when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS, @@ -299,68 +388,38 @@ class RoomListFragment @Inject constructor( } } - private fun renderSuccess(state: RoomListViewState) { - val allRooms = state.asyncRooms() - val filteredRooms = state.asyncFilteredRooms() - if (filteredRooms.isNullOrEmpty()) { - renderEmptyState(allRooms) - } else { - views.stateView.state = StateView.State.Content - } - } - - private fun renderEmptyState(allRooms: List?) { - 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.section.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 +436,11 @@ class RoomListFragment @Inject constructor( } override fun onRoomLongClicked(room: RoomSummary): Boolean { - roomController.onRoomLongClicked() + userPreferencesProvider.neverShowLongClickOnRoomHelpAgain() + withState(roomListViewModel) { + // refresh footer + footerController.setData(it) + } RoomListQuickActionsBottomSheet .newInstance(room.roomId, RoomListActionsArgs.Mode.FULL) .show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS") @@ -394,10 +457,6 @@ class RoomListFragment @Inject constructor( roomListViewModel.handle(RoomListAction.RejectInvitation(room)) } - override fun onToggleRoomCategory(roomCategory: RoomCategory) { - roomListViewModel.handle(RoomListAction.ToggleCategory(roomCategory)) - } - override fun createRoom(initialName: String) { navigator.openCreateRoom(requireActivity(), initialName) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt new file mode 100644 index 0000000000..e9833d1560 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt @@ -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) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 3a5e797f98..36551dece7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -16,37 +16,50 @@ package im.vector.app.features.home.room.list +import androidx.annotation.StringRes +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import androidx.paging.PagedList import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext +import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.DataSource +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.home.RoomListDisplayMode import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.RoomCategoryFilter +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic -import org.matrix.android.sdk.rx.rx +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount +import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult +import org.matrix.android.sdk.rx.asObservable import timber.log.Timber -import java.lang.Exception import javax.inject.Inject class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private val session: Session, - private val roomSummariesSource: DataSource>) + private val stringProvider: StringProvider) : VectorViewModel(initialState) { interface Factory { fun create(initialState: RoomListViewState): RoomListViewModel } + private var updatableQuery: UpdatableFilterLivePageResult? = null + companion object : MvRxViewModelFactory { @JvmStatic @@ -56,28 +69,121 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - private val displayMode = initialState.displayMode - private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode) + data class RoomsSection( + val sectionName: String, + val livePages: LiveData>, + val isExpanded: MutableLiveData = MutableLiveData(true), + val notificationCount: MutableLiveData = + MutableLiveData(RoomAggregateNotificationCount(0, 0)) + ) + + val sections: List by lazy { + val sections = mutableListOf() + if (initialState.displayMode == RoomListDisplayMode.PEOPLE) { + + addSection(sections, R.string.invitations_header) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + + addSection(sections, R.string.bottom_action_people_x) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + } else if (initialState.displayMode == RoomListDisplayMode.ROOMS) { + + addSection(sections, R.string.invitations_header) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + + addSection(sections, R.string.bottom_action_favourites) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null) + } + + addSection(sections, R.string.bottom_action_rooms) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.roomTagQueryFilter = RoomTagQueryFilter(false, false, false) + } + + addSection(sections, R.string.low_priority_header) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.roomTagQueryFilter = RoomTagQueryFilter(null, true, null) + } + + addSection(sections, R.string.system_alerts_header) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true) + } + } else if (initialState.displayMode == RoomListDisplayMode.FILTERED) { + withQueryParams({ + it.memberships = Membership.activeMemberships() +// it.displayName = QueryStringValue.Contains("") + }) { qpm -> + val name = stringProvider.getString(R.string.bottom_action_rooms) + session.getFilteredPagedRoomSummariesLive(qpm) + .let { livePagedList -> + updatableQuery = livePagedList + sections.add(RoomsSection(name, livePagedList.livePagedList)) + } + } + } + sections + } init { - observeRoomSummaries() - observeMembershipChanges() } override fun handle(action: RoomListAction) { when (action) { - is RoomListAction.SelectRoom -> handleSelectRoom(action) - is RoomListAction.ToggleCategory -> handleToggleCategory(action) - is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action) - is RoomListAction.RejectInvitation -> handleRejectInvitation(action) - is RoomListAction.FilterWith -> handleFilter(action) - is RoomListAction.MarkAllRoomsRead -> handleMarkAllRoomsRead() - is RoomListAction.LeaveRoom -> handleLeaveRoom(action) + is RoomListAction.SelectRoom -> handleSelectRoom(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.ToggleTag -> handleToggleTag(action) + is RoomListAction.ToggleSection -> handleToggleSection(action.section) }.exhaustive } + private fun addSection(sections: MutableList, @StringRes nameRes: Int, query: (RoomSummaryQueryParams.Builder) -> Unit) { + withQueryParams({ + query.invoke(it) + }) { roomQueryParams -> + + val name = stringProvider.getString(nameRes) + session.getPagedRoomSummariesLive(roomQueryParams) + .let { livePagedList -> + + // use it also as a source to update count + livePagedList.asObservable() + .observeOn(Schedulers.computation()) + .subscribe { + sections.find { it.sectionName == name } + ?.notificationCount + ?.postValue(session.getNotificationCountForRooms(roomQueryParams)) + }.disposeOnClear() + + sections.add(RoomsSection(name, livePagedList)) + } + } + } + + private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) { + RoomSummaryQueryParams.Builder().apply { + builder.invoke(this) + }.build().let { + block(it) + } + } + fun isPublicRoom(roomId: String): Boolean { return session.getRoom(roomId)?.isPublic().orFalse() } @@ -88,8 +194,11 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, _viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary)) } - private fun handleToggleCategory(action: RoomListAction.ToggleCategory) = setState { - this.toggle(action.category) + private fun handleToggleSection(section: RoomsSection) { + sections.find { it.sectionName == section.sectionName } + ?.let { section -> + section.isExpanded.postValue(!section.isExpanded.value.orFalse()) + } } private fun handleFilter(action: RoomListAction.FilterWith) { @@ -98,23 +207,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, roomFilter = action.filter ) } - } - - 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 { + this.memberships = Membership.activeMemberships() + this.displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) } + ) } private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state -> @@ -164,12 +262,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } private fun handleMarkAllRoomsRead() = withState { state -> - state.asyncFilteredRooms.invoke() - ?.flatMap { it.value } - ?.filter { it.membership == Membership.JOIN } - ?.map { it.roomId } - ?.toList() - ?.let { session.markAllAsRead(it, NoOpMatrixCallback()) } +// state.asyncFilteredRooms.invoke() +// ?.flatMap { it.value } +// ?.filter { it.membership == Membership.JOIN } +// ?.map { it.roomId } +// ?.toList() +// ?.let { session.markAllAsRead(it, NoOpMatrixCallback()) } } private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) { @@ -211,7 +309,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private fun String.otherTag(): String? { return when (this) { - RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY + RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY RoomTag.ROOM_TAG_LOW_PRIORITY -> RoomTag.ROOM_TAG_FAVOURITE else -> null } @@ -226,46 +324,4 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, _viewEvents.post(value) } } - - private fun observeMembershipChanges() { - session.rx() - .liveRoomChangeMembershipState() - .subscribe { - Timber.v("ChangeMembership states: $it") - setState { copy(roomMembershipChanges = it) } - } - .disposeOnClear() - } - - private fun buildRoomSummaries(rooms: List): RoomSummaries { - // Set up init size on directChats and groupRooms as they are the biggest ones - val invites = ArrayList() - val favourites = ArrayList() - val directChats = ArrayList(rooms.size) - val groupRooms = ArrayList(rooms.size) - val lowPriorities = ArrayList() - val serverNotices = ArrayList() - - 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) - } - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt index 44ca8cefda..d36bc45ab6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt @@ -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, - private val homeRoomListDataSource: Provider) + private val stringProvider: StringProvider) : RoomListViewModel.Factory { override fun create(initialState: RoomListViewState): RoomListViewModel { return RoomListViewModel( initialState, session.get(), - homeRoomListDataSource.get() + stringProvider ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt index 095262d74b..be4eae05db 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt @@ -17,59 +17,26 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes -import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState -import com.airbnb.mvrx.Uninitialized import im.vector.app.R import im.vector.app.features.home.RoomListDisplayMode import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState -import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary data class RoomListViewState( val displayMode: RoomListDisplayMode, - val asyncRooms: Async> = Uninitialized, val roomFilter: String = "", - val asyncFilteredRooms: Async = Uninitialized, - val roomMembershipChanges: Map = 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 = emptyMap() ) : MvRxState { constructor(args: RoomListParams) : this(displayMode = args.displayMode) - fun isCategoryExpanded(roomCategory: RoomCategory): Boolean { - return when (roomCategory) { - RoomCategory.INVITE -> isInviteExpanded - RoomCategory.FAVOURITE -> isFavouriteRoomsExpanded - RoomCategory.DIRECT -> isDirectRoomsExpanded - RoomCategory.GROUP -> isGroupRoomsExpanded - RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded - RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded - } - } - - fun toggle(roomCategory: RoomCategory): RoomListViewState { - return when (roomCategory) { - RoomCategory.INVITE -> copy(isInviteExpanded = !isInviteExpanded) - RoomCategory.FAVOURITE -> copy(isFavouriteRoomsExpanded = !isFavouriteRoomsExpanded) - RoomCategory.DIRECT -> copy(isDirectRoomsExpanded = !isDirectRoomsExpanded) - RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded) - RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded) - RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded) - } - } - - val hasUnread: Boolean - get() = asyncFilteredRooms.invoke() - ?.flatMap { it.value } - ?.filter { it.membership == Membership.JOIN } - ?.any { it.hasUnreadMessages } - ?: false + val hasUnread: Boolean = false +// get() = asyncFilteredRooms.invoke() +// ?.flatMap { it.value } +// ?.filter { it.membership == Membership.JOIN } +// ?.any { it.hasUnreadMessages } +// ?: false } typealias RoomSummaries = LinkedHashMap> diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryController.kt deleted file mode 100644 index d7cace9edb..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryController.kt +++ /dev/null @@ -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, - @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, - roomChangedMembershipStates: Map, - selectedRoomIds: Set) { - 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) - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 7d7ed1637f..fa6c970d8a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -40,7 +40,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor fun create(roomSummary: RoomSummary, roomChangeMembershipStates: Map, selectedRoomIds: Set, - 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 { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt new file mode 100644 index 0000000000..75171dad39 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.resources.UserPreferencesProvider +import im.vector.app.core.utils.createUIHandler +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import javax.inject.Inject + +class RoomSummaryPagedControllerFactory @Inject constructor(private val stringProvider: StringProvider, + private val userPreferencesProvider: UserPreferencesProvider, + private val roomSummaryItemFactory: RoomSummaryItemFactory) { + + fun createRoomSummaryPagedController(): RoomSummaryPagedController { + return RoomSummaryPagedController(stringProvider, userPreferencesProvider, roomSummaryItemFactory) + } +} + +class RoomSummaryPagedController constructor(private val stringProvider: StringProvider, + private val userPreferencesProvider: UserPreferencesProvider, + private val roomSummaryItemFactory: RoomSummaryItemFactory) + : PagedListEpoxyController( +// Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + var listener: RoomListListener? = null + + override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { + val unwrappedItem = item + // for place holder if enabled + ?: return roomSummaryItemFactory.createRoomItem( + RoomSummary( + roomId = "null_item_pos_$currentPosition", + name = "", + encryptionEventTs = null, + isEncrypted = false, + typingUsers = emptyList() + ), emptySet(), null, null) + +// GenericItem_().apply { id("null_item_pos_$currentPosition") } + + return roomSummaryItemFactory.create(unwrappedItem, emptyMap(), emptySet(), listener) + } + +// override fun onModelBound(holder: EpoxyViewHolder, boundModel: EpoxyModel<*>, position: Int, previouslyBoundModel: EpoxyModel<*>?) { +// Timber.w("VAL: Will load around $position") +// super.onModelBound(holder, boundModel, position, previouslyBoundModel) +// } +// fun onRoomLongClicked() { +// userPreferencesProvider.neverShowLongClickOnRoomHelpAgain() +// requestModelBuild() +// } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt new file mode 100644 index 0000000000..30f0e671f9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt @@ -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() { + + data class SectionViewModel( + val name: String, + val isExpanded: Boolean = true, + val notificationCount: Int = 0, + val isHighlighted: Boolean = false, + val isHidden: Boolean = true + ) + + lateinit var section: SectionViewModel + private set + + fun updateSection(newSection: SectionViewModel) { + if (!::section.isInitialized || newSection != section) { + section = newSection + notifyDataSetChanged() + } + } + + init { + setHasStableIds(true) + } + + override fun getItemId(position: Int) = section.hashCode().toLong() + + override fun getItemViewType(position: Int) = R.layout.item_room_category + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { + return VH.create(parent, this.onClickAction) + } + + override fun onBindViewHolder(holder: VH, position: Int) { + holder.bind(section) + } + + override fun getItemCount(): Int = 1.takeIf { section.isHidden.not() } ?: 0 + + class VH constructor( + private val binding: ItemRoomCategoryBinding, + onClickAction: (() -> Unit) + ) : RecyclerView.ViewHolder(binding.root) { + + init { + binding.root.setOnClickListener(DebouncedClickListener({ + onClickAction.invoke() + })) + } + + fun bind(section: SectionViewModel) { + binding.roomCategoryTitleView.text = section.name + val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.riotx_text_secondary) + val expandedArrowDrawableRes = if (section.isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white + val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also { + DrawableCompat.setTint(it, tintColor) + } + binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(section.notificationCount, section.isHighlighted)) + binding.roomCategoryTitleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) + } + + companion object { + fun create(parent: ViewGroup, onClickAction: () -> Unit): VH { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_room_category, parent, false) + val binding = ItemRoomCategoryBinding.bind(view) + return VH(binding, onClickAction) + } + } + } +} From 38af0caa3fe3f0cc4ac432ab2e393abe82c42b79 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 11:51:04 +0200 Subject: [PATCH 02/18] Fix / missing DM favorites --- .../vector/app/features/home/room/list/RoomListViewModel.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 36551dece7..b8dc3282b7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -86,6 +86,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM } + 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 From 52f98dc40547ba79969d2481768ec1dcf0763b50 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 11:51:44 +0200 Subject: [PATCH 03/18] Remove HomeListDataSource --- .../android/sdk/api/query/QueryStringValue.kt | 1 - .../java/im/vector/app/AppStateHandler.kt | 50 +------------- .../im/vector/app/core/di/VectorComponent.kt | 3 - .../app/features/home/HomeDetailViewModel.kt | 67 +++++++++---------- .../features/home/HomeRoomListDataSource.kt | 25 ------- .../app/features/home/ShortcutsHandler.kt | 24 ++++--- 6 files changed, 49 insertions(+), 121 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 99a86e185b..8f83beface 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -33,4 +33,3 @@ sealed class QueryStringValue { INSENSITIVE } } - diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index d2b59e5513..81d1fdb636 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -19,32 +19,19 @@ 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 */ @Singleton class AppStateHandler @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, - private val homeRoomListDataSource: HomeRoomListDataSource, private val selectedGroupDataSource: SelectedGroupDataSource, private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver { @@ -52,45 +39,10 @@ class AppStateHandler @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { -// observeRoomsAndGroup() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun entersBackground() { compositeDisposable.clear() } - - private fun observeRoomsAndGroup() { - Observable - .combineLatest, Option, List>( - 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) - } } diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index 23d6b618fe..cae7a2ece6 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index c261081055..f4c66212b9 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -20,18 +20,21 @@ 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 org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.RoomCategoryFilter 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 org.matrix.android.sdk.rx.rx +import java.util.concurrent.TimeUnit /** * View model used to update the home bottom bar notification counts, observe the sync state and @@ -41,7 +44,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(initialState) { @@ -113,43 +115,40 @@ 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 ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt b/vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt deleted file mode 100644 index 6bcd6f01eb..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt +++ /dev/null @@ -1,25 +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 - -import im.vector.app.core.utils.BehaviorDataSource -import org.matrix.android.sdk.api.session.room.model.RoomSummary -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class HomeRoomListDataSource @Inject constructor() : BehaviorDataSource>() diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 3684a8b3f8..99cc56bd99 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -21,15 +21,20 @@ import android.content.pm.ShortcutManager import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat +import im.vector.app.core.di.ActiveSessionHolder import io.reactivex.Observable 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 { @@ -38,19 +43,20 @@ class ShortcutsHandler @Inject constructor( return Observable.empty().subscribe() } - return homeRoomListStore - .observe() - .distinctUntilChanged() - .observeOn(Schedulers.computation()) - .subscribe { rooms -> + return activeSessionHolder.getSafeActiveSession()?.getPagedRoomSummariesLive(roomSummaryQueryParams { + this.memberships = listOf(Membership.JOIN) + this.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() { From 41176c3e2667f0d498259f9eb0e7a3234a2b61aa Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 12:31:02 +0200 Subject: [PATCH 04/18] Support only notif display mode + code quality fixes --- .../api/session/room/RoomSummaryQueryParams.kt | 1 + .../room/summary/RoomSummaryDataSource.kt | 1 + tools/check/forbidden_strings_in_code.txt | 2 +- .../app/features/home/HomeDetailViewModel.kt | 9 +++++---- .../home/room/list/RoomListViewModel.kt | 18 ++++++++++++++---- 5 files changed, 22 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index a57514023b..e0862f229a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -27,6 +27,7 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = { enum class RoomCategoryFilter { ONLY_DM, ONLY_ROOMS, + ONLY_WITH_NOTIFICATIONS, ALL } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index a806123237..769321cb8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -181,6 +181,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat 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 } diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 7b7c44b9fd..5a53ececec 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index f4c66212b9..4810abe08b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -115,10 +115,11 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } private fun observeRoomSummaries() { - session.getPagedRoomSummariesLive(roomSummaryQueryParams { - memberships = Membership.activeMemberships() - }) - .asObservable() + session.getPagedRoomSummariesLive( + roomSummaryQueryParams { + memberships = Membership.activeMemberships() + } + ).asObservable() .throttleFirst(300, TimeUnit.MILLISECONDS) .subscribe { val dmInvites = session.getRoomSummaries(roomSummaryQueryParams { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index b8dc3282b7..e02a7b8979 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -129,7 +129,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } else if (initialState.displayMode == RoomListDisplayMode.FILTERED) { withQueryParams({ it.memberships = Membership.activeMemberships() -// it.displayName = QueryStringValue.Contains("") }) { qpm -> val name = stringProvider.getString(R.string.bottom_action_rooms) session.getFilteredPagedRoomSummariesLive(qpm) @@ -139,10 +138,21 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } } - sections - } + else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { + withQueryParams({ + it.memberships = Membership.activeMemberships() + it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS + }) { qpm -> + val name = stringProvider.getString(R.string.bottom_action_rooms) + session.getFilteredPagedRoomSummariesLive(qpm) + .let { livePagedList -> + updatableQuery = livePagedList + sections.add(RoomsSection(name, livePagedList.livePagedList)) + } + } + } - init { + sections } override fun handle(action: RoomListAction) { From cf581ecfcf5d9a27534f812aebd251b317119498 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 13:00:20 +0200 Subject: [PATCH 05/18] Make pagedList config as part of API --- .../sdk/api/session/room/RoomService.kt | 23 ++++++++++++++++--- .../session/room/DefaultRoomService.kt | 12 ++++++---- .../room/summary/RoomSummaryDataSource.kt | 20 ++++------------ .../home/room/list/RoomListViewModel.kt | 3 +-- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 4814805dd8..7509c2d975 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -25,11 +25,11 @@ 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.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription -import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount /** * This interface defines methods to get rooms. It's implemented at the session level. @@ -182,8 +182,25 @@ interface RoomService { */ fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) - fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> + fun getPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build() + ): LiveData> fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount - fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult + + fun getFilteredPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build() + ): UpdatableFilterLivePageResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 5feb9c9d5a..34904110a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -30,6 +30,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.api.util.toOptional @@ -46,7 +47,6 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomT import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask -import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask import org.matrix.android.sdk.internal.task.TaskExecutor @@ -98,12 +98,14 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummariesLive(queryParams) } - override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : LiveData> { - return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams) + override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config) + : LiveData> { + return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig) } - override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : UpdatableFilterLivePageResult { - return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams) + override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config) + : UpdatableFilterLivePageResult { + return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig) } override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 769321cb8c..a62d82782a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -104,7 +104,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } - fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> { + fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config): LiveData> { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams) .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) @@ -113,17 +113,11 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat roomSummaryMapper.map(it) } return monarchy.findAllPagedWithChanges(realmDataSourceFactory, - LivePagedListBuilder(dataSourceFactory, - PagedList.Config.Builder() - .setPageSize(10) - .setInitialLoadSizeHint(20) - .setEnablePlaceholders(false) - .setPrefetchDistance(10) - .build()) + LivePagedListBuilder(dataSourceFactory, pagedListConfig) ) } - fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult { + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams) .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) @@ -133,13 +127,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } val mapped = monarchy.findAllPagedWithChanges(realmDataSourceFactory, - LivePagedListBuilder(dataSourceFactory, - PagedList.Config.Builder() - .setPageSize(10) - .setInitialLoadSizeHint(20) - .setEnablePlaceholders(false) - .setPrefetchDistance(10) - .build()) + LivePagedListBuilder(dataSourceFactory, pagedListConfig) ) return object : UpdatableFilterLivePageResult { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index e02a7b8979..626d058c65 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -137,8 +137,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, sections.add(RoomsSection(name, livePagedList.livePagedList)) } } - } - else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { + } else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { withQueryParams({ it.memberships = Membership.activeMemberships() it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS From b6f0f12515b3cf64a47c6965a81e609e387f07af Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 13:01:25 +0200 Subject: [PATCH 06/18] lint --- .../vector/app/features/home/room/list/RoomListViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 626d058c65..4b1af3cbaf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -209,8 +209,8 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, _viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary)) } - private fun handleToggleSection(section: RoomsSection) { - sections.find { it.sectionName == section.sectionName } + private fun handleToggleSection(roomSection: RoomsSection) { + sections.find { it.sectionName == roomSection.sectionName } ?.let { section -> section.isExpanded.postValue(!section.isExpanded.value.orFalse()) } @@ -276,7 +276,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - private fun handleMarkAllRoomsRead() = withState { state -> + private fun handleMarkAllRoomsRead() = withState { _ -> // state.asyncFilteredRooms.invoke() // ?.flatMap { it.value } // ?.filter { it.membership == Membership.JOIN } From c23437d45afc9b17c5017a8b3ac3718ac2223c1c Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 13:57:31 +0200 Subject: [PATCH 07/18] Code quality --- .../summary/RoomAggregateNotificationCount.kt | 2 +- .../app/features/home/HomeDetailViewModel.kt | 40 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt index 2aa122c84e..0c581508b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 4810abe08b..07e2426200 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -122,25 +122,33 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho ).asObservable() .throttleFirst(300, TimeUnit.MILLISECONDS) .subscribe { - val dmInvites = session.getRoomSummaries(roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_DM - }).size + val dmInvites = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + ).size - val roomsInvite = session.getRoomSummaries(roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - }).size + val roomsInvite = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + ).size - val dmRooms = session.getNotificationCountForRooms(roomSummaryQueryParams { - memberships = listOf(Membership.JOIN) - roomCategoryFilter = RoomCategoryFilter.ONLY_DM - }) + val dmRooms = session.getNotificationCountForRooms( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + ) - val otherRooms = session.getNotificationCountForRooms(roomSummaryQueryParams { - memberships = listOf(Membership.JOIN) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - }) + val otherRooms = session.getNotificationCountForRooms( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + ) setState { copy( From b390980ca2cb70d221529faa0b8202642d9bef4f Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 16:51:32 +0200 Subject: [PATCH 08/18] Resurrect mark all as read --- .../app/features/home/HomeDetailAction.kt | 1 + .../app/features/home/HomeDetailFragment.kt | 33 +++++++++++++++-- .../app/features/home/HomeDetailViewModel.kt | 30 +++++++++++++++- .../app/features/home/HomeDetailViewState.kt | 1 + .../features/home/room/list/RoomListAction.kt | 1 - .../home/room/list/RoomListFragment.kt | 35 ------------------- .../home/room/list/RoomListViewModel.kt | 10 ------ 7 files changed, 62 insertions(+), 49 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailAction.kt index 447820ed7b..c64f9d453d 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailAction.kt @@ -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() } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 4c7b7aa991..5def43b60b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -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) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 07e2426200..8a73b2e0f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -16,6 +16,7 @@ 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 @@ -28,12 +29,16 @@ 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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.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 /** @@ -77,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() } } @@ -92,6 +98,27 @@ 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 { + this.memberships = listOf(Membership.JOIN) + this.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS + } + ).map { + it.roomId + } + try { + awaitCallback { + session.markAllAsRead(roomIds, it) + } + } catch (failure: Throwable) { + Timber.d(failure, "Failed to mark all as read") + } + } + } + private fun observeSyncState() { session.rx() .liveSyncState() @@ -157,7 +184,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho notificationCountPeople = dmRooms.totalCount() + dmInvites, notificationHighlightPeople = dmRooms.isHighlight() || dmInvites > 0, notificationCountRooms = otherRooms.totalCount() + roomsInvite, - notificationHighlightRooms = otherRooms.isHighlight() || roomsInvite > 0 + notificationHighlightRooms = otherRooms.isHighlight() || roomsInvite > 0, + hasUnreadMessages = dmRooms.totalCount() + otherRooms.totalCount() > 0 ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt index f5e4bc9fa3..533c9166f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt index 0d70f99b3a..a5c071680d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt @@ -29,5 +29,4 @@ sealed class RoomListAction : VectorViewModelAction { 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() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 0e7e35654c..ac59283e7a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -20,8 +20,6 @@ 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 @@ -84,8 +82,6 @@ class RoomListFragment @Inject constructor( return FragmentRoomListBinding.inflate(inflater, container, false) } - private var hasUnreadRooms = false - data class SectionKey( val name: String, val isExpanded: Boolean @@ -100,24 +96,6 @@ class RoomListFragment @Inject constructor( private val adapterInfosList = mutableListOf() private var concatAdapter: ConcatAdapter? = null - override fun getMenuRes() = R.menu.room_list - - 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) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupCreateRoomButton() @@ -373,19 +351,6 @@ class RoomListFragment @Inject constructor( override fun invalidate() = withState(roomListViewModel) { state -> footerController.setData(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 - } } private fun checkEmptyState() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 4b1af3cbaf..76e8b1adaf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -160,7 +160,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, 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) @@ -276,15 +275,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - private fun handleMarkAllRoomsRead() = withState { _ -> -// 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) { From cd6fab0e2d35a0a5d92f563acf35188bea84aaa8 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 17:36:24 +0200 Subject: [PATCH 09/18] Fix empty state + cleaning + changelog --- CHANGES.md | 1 + .../im/vector/app/features/home/room/list/RoomListFragment.kt | 2 +- .../vector/app/features/home/room/list/SectionHeaderAdapter.kt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ed549c918f..45b3905375 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index ac59283e7a..cf74cae37c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -249,7 +249,7 @@ class RoomListFragment @Inject constructor( val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController().also { section.livePages.observe(viewLifecycleOwner) { pl -> it.submitList(pl) - sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty() || hasOnlyOneSection)) + sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty())) checkEmptyState() } section.notificationCount.observe(viewLifecycleOwner) { counts -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt index 30f0e671f9..b137947ef7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt @@ -64,7 +64,7 @@ class SectionHeaderAdapter constructor( holder.bind(section) } - override fun getItemCount(): Int = 1.takeIf { section.isHidden.not() } ?: 0 + override fun getItemCount(): Int = if (section.isHidden) 0 else 1 class VH constructor( private val binding: ItemRoomCategoryBinding, From 52ba67c9c04c9de52d2b9f98b34ba878d20c588b Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Apr 2021 09:12:18 +0200 Subject: [PATCH 10/18] unused val --- .../im/vector/app/features/home/room/list/RoomListFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index cf74cae37c..05bf9a8a84 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -235,7 +235,7 @@ class RoomListFragment @Inject constructor( modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } val concatAdapter = ConcatAdapter() - val hasOnlyOneSection = roomListViewModel.sections.size == 1 +// val hasOnlyOneSection = roomListViewModel.sections.size == 1 roomListViewModel.sections.forEach { section -> val sectionAdapter = SectionHeaderAdapter { From ec3266f7e8cfe30e339db16888d4ac89f15e71a3 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Apr 2021 15:47:58 +0200 Subject: [PATCH 11/18] remove dead code --- .../home/room/list/RoomListViewState.kt | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt index be4eae05db..104a3710f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt @@ -16,12 +16,9 @@ package im.vector.app.features.home.room.list -import androidx.annotation.StringRes import com.airbnb.mvrx.MvRxState -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.RoomSummary data class RoomListViewState( val displayMode: RoomListDisplayMode, @@ -30,26 +27,4 @@ data class RoomListViewState( ) : MvRxState { constructor(args: RoomListParams) : this(displayMode = args.displayMode) - - val hasUnread: Boolean = false -// get() = asyncFilteredRooms.invoke() -// ?.flatMap { it.value } -// ?.filter { it.membership == Membership.JOIN } -// ?.any { it.hasUnreadMessages } -// ?: false -} - -typealias RoomSummaries = LinkedHashMap> - -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() } From bf6058dc32b24ca6b36eec90883dc02fe5753a97 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Apr 2021 15:55:42 +0200 Subject: [PATCH 12/18] Show local echo of joining action --- .../home/room/list/RoomListFragment.kt | 14 +++- .../home/room/list/RoomListViewModel.kt | 65 +++++++++++++++---- .../room/list/RoomSummaryPagedController.kt | 24 +++---- 3 files changed, 76 insertions(+), 27 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 05bf9a8a84..8323c3b155 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -84,7 +84,8 @@ class RoomListFragment @Inject constructor( data class SectionKey( val name: String, - val isExpanded: Boolean + val isExpanded: Boolean, + val notifyOfLocalEcho: Boolean ) data class SectionAdapterInfo( @@ -116,6 +117,14 @@ 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() { @@ -267,7 +276,8 @@ class RoomListFragment @Inject constructor( SectionAdapterInfo( SectionKey( name = section.sectionName, - isExpanded = section.isExpanded.value.orTrue() + isExpanded = section.isExpanded.value.orTrue(), + notifyOfLocalEcho = section.notifyOfLocalEcho ), sectionAdapter, contentAdapter diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 76e8b1adaf..21e4e5830c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.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 @@ -46,6 +47,7 @@ import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.rx.asObservable +import org.matrix.android.sdk.rx.rx import timber.log.Timber import javax.inject.Inject @@ -60,6 +62,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private var updatableQuery: UpdatableFilterLivePageResult? = null + init { + observeMembershipChanges() + } + + private fun observeMembershipChanges() { + session.rx() + .liveRoomChangeMembershipState() + .subscribe { + setState { copy(roomMembershipChanges = it) } + } + .disposeOnClear() + } + companion object : MvRxViewModelFactory { @JvmStatic @@ -74,14 +89,15 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, val livePages: LiveData>, val isExpanded: MutableLiveData = MutableLiveData(true), val notificationCount: MutableLiveData = - MutableLiveData(RoomAggregateNotificationCount(0, 0)) + MutableLiveData(RoomAggregateNotificationCount(0, 0)), + val notifyOfLocalEcho: Boolean = false ) val sections: List by lazy { val sections = mutableListOf() if (initialState.displayMode == RoomListDisplayMode.PEOPLE) { - addSection(sections, R.string.invitations_header) { + addSection(sections, R.string.invitations_header, true) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM } @@ -98,7 +114,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } else if (initialState.displayMode == RoomListDisplayMode.ROOMS) { - addSection(sections, R.string.invitations_header) { + addSection(sections, R.string.invitations_header, true) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS } @@ -138,16 +154,15 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } } else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { - withQueryParams({ - it.memberships = Membership.activeMemberships() + + 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 - }) { qpm -> - val name = stringProvider.getString(R.string.bottom_action_rooms) - session.getFilteredPagedRoomSummariesLive(qpm) - .let { livePagedList -> - updatableQuery = livePagedList - sections.add(RoomsSection(name, livePagedList.livePagedList)) - } } } @@ -167,7 +182,10 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, }.exhaustive } - private fun addSection(sections: MutableList, @StringRes nameRes: Int, query: (RoomSummaryQueryParams.Builder) -> Unit) { + private fun addSection(sections: MutableList, + @StringRes nameRes: Int, + notifyOfLocalEcho: Boolean = false, + query: (RoomSummaryQueryParams.Builder) -> Unit) { withQueryParams({ query.invoke(it) }) { roomQueryParams -> @@ -185,7 +203,13 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, ?.postValue(session.getNotificationCountForRooms(roomQueryParams)) }.disposeOnClear() - sections.add(RoomsSection(name, livePagedList)) + sections.add( + RoomsSection( + sectionName = name, + livePages = livePagedList, + notifyOfLocalEcho = notifyOfLocalEcho + ) + ) } } } @@ -238,6 +262,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 { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt index 75171dad39..38137e0033 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt @@ -18,24 +18,19 @@ package im.vector.app.features.home.room.list import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.core.utils.createUIHandler +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject -class RoomSummaryPagedControllerFactory @Inject constructor(private val stringProvider: StringProvider, - private val userPreferencesProvider: UserPreferencesProvider, - private val roomSummaryItemFactory: RoomSummaryItemFactory) { +class RoomSummaryPagedControllerFactory @Inject constructor(private val roomSummaryItemFactory: RoomSummaryItemFactory) { fun createRoomSummaryPagedController(): RoomSummaryPagedController { - return RoomSummaryPagedController(stringProvider, userPreferencesProvider, roomSummaryItemFactory) + return RoomSummaryPagedController(roomSummaryItemFactory) } } -class RoomSummaryPagedController constructor(private val stringProvider: StringProvider, - private val userPreferencesProvider: UserPreferencesProvider, - private val roomSummaryItemFactory: RoomSummaryItemFactory) +class RoomSummaryPagedController constructor(private val roomSummaryItemFactory: RoomSummaryItemFactory) : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() @@ -43,9 +38,16 @@ class RoomSummaryPagedController constructor(private val stringProvider: StringP var listener: RoomListListener? = null + var roomChangeMembershipStates: Map? = 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<*> { val unwrappedItem = item - // for place holder if enabled + // for place holder if enabled ?: return roomSummaryItemFactory.createRoomItem( RoomSummary( roomId = "null_item_pos_$currentPosition", @@ -57,7 +59,7 @@ class RoomSummaryPagedController constructor(private val stringProvider: StringP // GenericItem_().apply { id("null_item_pos_$currentPosition") } - return roomSummaryItemFactory.create(unwrappedItem, emptyMap(), emptySet(), listener) + return roomSummaryItemFactory.create(unwrappedItem, roomChangeMembershipStates ?: emptyMap(), emptySet(), listener) } // override fun onModelBound(holder: EpoxyViewHolder, boundModel: EpoxyModel<*>, position: Int, previouslyBoundModel: EpoxyModel<*>?) { From f60e649d76b2402a7d7ca0079165744ed50fa49a Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Apr 2021 15:56:03 +0200 Subject: [PATCH 13/18] Fix / newly joined or created are at the bottom of room list --- .../session/room/create/CreateRoomTask.kt | 15 ++++++++---- .../room/membership/joining/JoinRoomTask.kt | 23 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 9c16bd1b0f..cf5e4d64cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -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) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 3b7639d42f..efb20863b5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -16,21 +16,24 @@ 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.create.JoinRoomResponse 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 @@ -69,12 +72,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) } From b47ced68b5296bd90fc656c94d1fafb808137583 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 1 Apr 2021 14:39:02 +0200 Subject: [PATCH 14/18] Move UpdatableFilterLivePageResult to the correct package --- .../sdk/api/session/room/RoomService.kt | 1 - .../room/UpdatableFilterLivePageResult.kt | 27 +++++++++++++++++++ .../session/room/DefaultRoomService.kt | 5 +--- .../room/summary/RoomSummaryDataSource.kt | 2 +- .../home/room/list/RoomListViewModel.kt | 2 +- 5 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 7509c2d975..819ba054ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -28,7 +28,6 @@ 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.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt new file mode 100644 index 0000000000..38462d5ac6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt @@ -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 org.matrix.android.sdk.api.session.room + +import androidx.lifecycle.LiveData +import androidx.paging.PagedList +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +interface UpdatableFilterLivePageResult { + val livePagedList: LiveData> + + fun updateQuery(queryParams: RoomSummaryQueryParams) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 34904110a1..f1d4bfca3f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -25,6 +25,7 @@ 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 @@ -195,7 +196,3 @@ internal class DefaultRoomService @Inject constructor( } } -interface UpdatableFilterLivePageResult { - val livePagedList: LiveData> - fun updateQuery(queryParams: RoomSummaryQueryParams) -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index a62d82782a..02ecce6afa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -26,6 +26,7 @@ import io.realm.RealmQuery import io.realm.Sort import org.matrix.android.sdk.api.session.room.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.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 @@ -38,7 +39,6 @@ import org.matrix.android.sdk.internal.database.query.findByAlias import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.query.process -import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.util.fetchCopyMap import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 21e4e5830c..2a214e6e90 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.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 @@ -45,7 +46,6 @@ 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.api.session.room.summary.RoomAggregateNotificationCount -import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import timber.log.Timber From b9f73c6cc3056706fda07a586a8ce39c1c129d4c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 1 Apr 2021 18:19:05 +0200 Subject: [PATCH 15/18] BMA's cleanup --- .../sdk/api/query/RoomCategoryFilter.kt | 24 ++++ .../sdk/api/session/room/RoomService.kt | 39 +++--- .../session/room/RoomSummaryQueryParams.kt | 8 +- .../summary/RoomAggregateNotificationCount.kt | 5 +- .../SessionRealmConfigurationFactory.kt | 1 - .../database/model/RoomSummaryEntity.kt | 19 ++- .../room/summary/RoomSummaryDataSource.kt | 27 ++-- .../room/summary/RoomSummaryUpdater.kt | 3 - .../java/im/vector/app/AppStateHandler.kt | 7 +- .../app/core/platform/VectorBaseFragment.kt | 2 + .../app/features/home/HomeDetailViewModel.kt | 30 ++-- .../app/features/home/ShortcutsHandler.kt | 15 +- .../features/home/room/list/RoomListAction.kt | 2 +- .../home/room/list/RoomListFragment.kt | 98 ++++++------- .../home/room/list/RoomListViewModel.kt | 129 ++++++++---------- .../room/list/RoomSummaryPagedController.kt | 45 +++--- .../features/home/room/list/RoomsSection.kt | 31 +++++ .../home/room/list/SectionHeaderAdapter.kt | 24 ++-- 18 files changed, 266 insertions(+), 243 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt new file mode 100644 index 0000000000..c8ccc4c8a3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt @@ -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 +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 819ba054ea..8c833644ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -181,25 +181,28 @@ interface RoomService { */ fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) - fun getPagedRoomSummariesLive( - queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config = PagedList.Config.Builder() - .setPageSize(10) - .setInitialLoadSizeHint(20) - .setEnablePlaceholders(false) - .setPrefetchDistance(10) - .build() - ): LiveData> + /** + * TODO Doc + */ + fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData> + /** + * TODO Doc + */ + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult + + /** + * TODO Doc + */ fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount - fun getFilteredPagedRoomSummariesLive( - queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config = PagedList.Config.Builder() - .setPageSize(10) - .setInitialLoadSizeHint(20) - .setEnablePlaceholders(false) - .setPrefetchDistance(10) - .build() - ): UpdatableFilterLivePageResult + private val defaultPagedListConfig + get() = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index e0862f229a..7e04ebb5f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -17,6 +17,7 @@ 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 @@ -24,13 +25,6 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = { return RoomSummaryQueryParams.Builder().apply(init).build() } -enum class RoomCategoryFilter { - ONLY_DM, - ONLY_ROOMS, - ONLY_WITH_NOTIFICATIONS, - ALL -} - /** * This class can be used to filter room summaries to use with: * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt index 0c581508b8..066178b1ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt @@ -20,7 +20,6 @@ data class RoomAggregateNotificationCount( val notificationCount: Int, val highlightCount: Int ) { - fun totalCount() = notificationCount + highlightCount - - fun isHighlight() = highlightCount > 0 + val totalCount = notificationCount + highlightCount + val isHighlight = highlightCount > 0 } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 770ef46080..244fe3432a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -72,7 +72,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) -// .deleteRealmIfMigrationNeeded() .migration(migration) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 6007ae504a..669074ffd2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -106,7 +106,7 @@ internal open class RoomSummaryEntity( private var tags: RealmList = RealmList() - fun tags(): RealmList = tags + fun tags(): List = tags fun updateTags(newTags: List>) { val toDelete = mutableListOf() @@ -118,9 +118,9 @@ internal open class RoomSummaryEntity( existingTag.tagOrder = updatedTag.second } } - toDelete.onEach { it.deleteFromRealm() } + toDelete.forEach { it.deleteFromRealm() } newTags.forEach { newTag -> - if (tags.indexOfFirst { it.tagName == newTag.first } == -1) { + if (tags.all { it.tagName != newTag.first }) { // we must add it tags.add( RoomTagEntity(newTag.first, newTag.second) @@ -128,9 +128,9 @@ internal open class RoomSummaryEntity( } } - isFavourite = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_FAVOURITE } != -1 - isLowPriority = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY } != -1 - isServerNotice = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE } != -1 + 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 @@ -170,18 +170,15 @@ internal open class RoomSummaryEntity( fun updateAliases(newAliases: List) { // only update underlying field if there is a diff - if (newAliases.toSet() != aliases.toSet()) { - Timber.w("VAL: aliases updated") + 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 = "" - set(value) { - if (value != field) field = value - } var isEncrypted: Boolean = false set(value) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 02ecce6afa..dd3fbe04b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -24,7 +24,7 @@ import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort -import org.matrix.android.sdk.api.session.room.RoomCategoryFilter +import org.matrix.android.sdk.api.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 @@ -104,7 +104,8 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } - fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config): LiveData> { + fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config): LiveData> { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams) .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) @@ -112,12 +113,14 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat val dataSourceFactory = realmDataSourceFactory.map { roomSummaryMapper.map(it) } - return monarchy.findAllPagedWithChanges(realmDataSourceFactory, + return monarchy.findAllPagedWithChanges( + realmDataSourceFactory, LivePagedListBuilder(dataSourceFactory, pagedListConfig) ) } - fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult { + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams) .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) @@ -126,13 +129,13 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat roomSummaryMapper.map(it) } - val mapped = monarchy.findAllPagedWithChanges(realmDataSourceFactory, + val mapped = monarchy.findAllPagedWithChanges( + realmDataSourceFactory, LivePagedListBuilder(dataSourceFactory, pagedListConfig) ) return object : UpdatableFilterLivePageResult { - override val livePagedList: LiveData> - get() = mapped + override val livePagedList: LiveData> = mapped override fun updateQuery(queryParams: RoomSummaryQueryParams) { realmDataSourceFactory.updateQuery { @@ -167,10 +170,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat 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_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 -> { + RoomCategoryFilter.ALL -> { // nop } } @@ -182,8 +185,8 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat it.isLowPriority?.let { lp -> query.equalTo(RoomSummaryEntityFields.IS_LOW_PRIORITY, lp) } - it.isServerNotice?.let { lp -> - query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, lp) + it.isServerNotice?.let { sn -> + query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn) } } return query diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 69e1332dab..f254c44fda 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -117,10 +117,7 @@ internal class RoomSummaryUpdater @Inject constructor( val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases .orEmpty() -// roomSummaryEntity.aliases.clear() -// roomSummaryEntity.aliases.addAll(roomAliases) roomSummaryEntity.updateAliases(roomAliases) - roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|") roomSummaryEntity.isEncrypted = encryptionEvent != null roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 81d1fdb636..b3b18d4adc 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -19,8 +19,6 @@ package im.vector.app import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent -import im.vector.app.features.grouplist.SelectedGroupDataSource -import im.vector.app.features.home.room.list.ChronologicalRoomComparator import io.reactivex.disposables.CompositeDisposable import javax.inject.Inject import javax.inject.Singleton @@ -29,11 +27,10 @@ import javax.inject.Singleton * 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 selectedGroupDataSource: SelectedGroupDataSource, - private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver { +) : LifecycleObserver { private val compositeDisposable = CompositeDisposable() diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index e3a3b7b29c..258517aa39 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -127,6 +127,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre Timber.i("onResume Fragment ${javaClass.simpleName}") } + @CallSuper override fun onPause() { super.onPause() Timber.i("onPause Fragment ${javaClass.simpleName}") @@ -154,6 +155,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre super.onDestroyView() } + @CallSuper override fun onDestroy() { Timber.i("onDestroy Fragment ${javaClass.simpleName}") uiDisposables.dispose() diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 8a73b2e0f9..c87b19f0e6 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -31,8 +31,8 @@ import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.ui.UiStateRepository 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.RoomCategoryFilter 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 @@ -82,7 +82,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho override fun handle(action: HomeDetailAction) { when (action) { is HomeDetailAction.SwitchDisplayMode -> handleSwitchDisplayMode(action) - HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead() + HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead() } } @@ -103,12 +103,11 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho viewModelScope.launch(Dispatchers.Default) { val roomIds = session.getRoomSummaries( roomSummaryQueryParams { - this.memberships = listOf(Membership.JOIN) - this.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS } - ).map { - it.roomId - } + ) + .map { it.roomId } try { awaitCallback { session.markAllAsRead(roomIds, it) @@ -146,7 +145,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho roomSummaryQueryParams { memberships = Membership.activeMemberships() } - ).asObservable() + ) + .asObservable() .throttleFirst(300, TimeUnit.MILLISECONDS) .subscribe { val dmInvites = session.getRoomSummaries( @@ -179,13 +179,13 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho setState { copy( - 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 + 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 ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 99cc56bd99..00ec8b43f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -40,13 +40,17 @@ class ShortcutsHandler @Inject constructor( fun observeRoomsAndBuildShortcuts(): Disposable { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { // No op - return Observable.empty().subscribe() + return Disposables.empty() } - return activeSessionHolder.getSafeActiveSession()?.getPagedRoomSummariesLive(roomSummaryQueryParams { - this.memberships = listOf(Membership.JOIN) - this.roomTagQueryFilter = RoomTagQueryFilter(isFavorite = true, null, null) - })?.asObservable() + return activeSessionHolder.getSafeActiveSession() + ?.getPagedRoomSummariesLive( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomTagQueryFilter = RoomTagQueryFilter(isFavorite = true, null, null) + } + ) + ?.asObservable() ?.subscribe { rooms -> val shortcuts = rooms .take(n = 4) // Android only allows us to create 4 shortcuts @@ -55,7 +59,6 @@ class ShortcutsHandler @Inject constructor( ShortcutManagerCompat.removeAllDynamicShortcuts(context) ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts) } - ?: Disposables.empty() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt index a5c071680d..883efb2e60 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt @@ -22,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat sealed class RoomListAction : VectorViewModelAction { data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction() - data class ToggleSection(val section: RoomListViewModel.RoomsSection) : 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() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 8323c3b155..8e837e85c6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -95,7 +95,7 @@ class RoomListFragment @Inject constructor( ) private val adapterInfosList = mutableListOf() - private var concatAdapter: ConcatAdapter? = null + private val concatAdapter = ConcatAdapter() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -104,10 +104,10 @@ class RoomListFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents { when (it) { - is RoomListViewEvents.Loading -> showLoading(it.message) - is RoomListViewEvents.Failure -> showFailure(it.throwable) + is RoomListViewEvents.Loading -> showLoading(it.message) + is RoomListViewEvents.Failure -> showFailure(it.throwable) is RoomListViewEvents.SelectRoom -> handleSelectRoom(it) - is RoomListViewEvents.Done -> Unit + is RoomListViewEvents.Done -> Unit }.exhaustive } @@ -134,10 +134,10 @@ class RoomListFragment @Inject constructor( val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue() if (actualBlock.section.isExpanded && !isRoomSectionExpanded) { // we have to remove the content adapter - concatAdapter?.removeAdapter(actualBlock.contentAdapter.adapter) + concatAdapter.removeAdapter(actualBlock.contentAdapter.adapter) } else if (!actualBlock.section.isExpanded && isRoomSectionExpanded) { // we must add it back! - concatAdapter?.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) + concatAdapter.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) } contentInsertIndex = if (isRoomSectionExpanded) { contentInsertIndex + 2 @@ -148,7 +148,7 @@ class RoomListFragment @Inject constructor( isExpanded = isRoomSectionExpanded ) actualBlock.headerHeaderAdapter.updateSection( - actualBlock.headerHeaderAdapter.section.copy(isExpanded = isRoomSectionExpanded) + actualBlock.headerHeaderAdapter.roomsSectionData.copy(isExpanded = isRoomSectionExpanded) ) } } @@ -160,16 +160,10 @@ class RoomListFragment @Inject constructor( override fun onDestroyView() { adapterInfosList.onEach { it.contentAdapter.removeModelBuildListener(modelBuildListener) } adapterInfosList.clear() -// roomController.removeModelBuildListener(modelBuildListener) modelBuildListener = null views.roomListView.cleanup() -// controllers.onEach { -// it.listener = null -// // concatAdapter.removeAdapter(it.adapter) -// } -// controllers.clear() -// roomController.listener = null -// favRoomController.listener = null + footerController.listener = null + // TODO Cleanup listener on the ConcatAdapter's adapters? stateRestorer.clear() views.createChatFabMenu.listener = null super.onDestroyView() @@ -182,8 +176,8 @@ class RoomListFragment @Inject constructor( private fun setupCreateRoomButton() { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true - RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true - RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true + RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true + RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true else -> Unit // No button in this mode } @@ -238,40 +232,35 @@ class RoomListFragment @Inject constructor( stateRestorer = LayoutManagerStateRestorer(layoutManager).register() views.roomListView.layoutManager = layoutManager views.roomListView.itemAnimator = RoomListAnimator() -// views.roomListView.setRecycledViewPool(sharedViewPool) layoutManager.recycleChildrenOnDetach = true modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } - val concatAdapter = ConcatAdapter() -// val hasOnlyOneSection = roomListViewModel.sections.size == 1 roomListViewModel.sections.forEach { section -> - val sectionAdapter = SectionHeaderAdapter { roomListViewModel.handle(RoomListAction.ToggleSection(section)) }.also { - it.updateSection(SectionHeaderAdapter.SectionViewModel( - section.sectionName - )) + it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName)) } - val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController().also { - section.livePages.observe(viewLifecycleOwner) { pl -> - it.submitList(pl) - sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty())) - checkEmptyState() - } - section.notificationCount.observe(viewLifecycleOwner) { counts -> - sectionAdapter.updateSection(sectionAdapter.section.copy( - notificationCount = counts.totalCount(), - isHighlighted = counts.isHighlight() - )) - } - section.isExpanded.observe(viewLifecycleOwner) { _ -> - refreshCollapseStates() - } - it.listener = this - } + 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( @@ -288,10 +277,9 @@ class RoomListFragment @Inject constructor( } // Add the footer controller - this.footerController.listener = this + footerController.listener = this concatAdapter.addAdapter(footerController.adapter) - this.concatAdapter = concatAdapter views.roomListView.adapter = concatAdapter } @@ -299,8 +287,8 @@ class RoomListFragment @Inject constructor( if (isAdded) { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show() - RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() - RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() + RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() + RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() else -> Unit } } @@ -308,28 +296,28 @@ class RoomListFragment @Inject constructor( private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { when (quickAction) { - is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { + is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY)) } - is RoomListQuickActionsSharedAction.NotificationsAll -> { + is RoomListQuickActionsSharedAction.NotificationsAll -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES)) } is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MENTIONS_ONLY)) } - is RoomListQuickActionsSharedAction.NotificationsMute -> { + is RoomListQuickActionsSharedAction.NotificationsMute -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MUTE)) } - is RoomListQuickActionsSharedAction.Settings -> { + is RoomListQuickActionsSharedAction.Settings -> { navigator.openRoomProfile(requireActivity(), quickAction.roomId) } - is RoomListQuickActionsSharedAction.Favorite -> { + is RoomListQuickActionsSharedAction.Favorite -> { roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_FAVOURITE)) } - is RoomListQuickActionsSharedAction.LowPriority -> { + is RoomListQuickActionsSharedAction.LowPriority -> { roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_LOW_PRIORITY)) } - is RoomListQuickActionsSharedAction.Leave -> { + is RoomListQuickActionsSharedAction.Leave -> { promptLeaveRoom(quickAction.roomId) } }.exhaustive @@ -364,7 +352,7 @@ class RoomListFragment @Inject constructor( } private fun checkEmptyState() { - val hasNoRoom = adapterInfosList.all { it.headerHeaderAdapter.section.isHidden } + val hasNoRoom = adapterInfosList.all { it.headerHeaderAdapter.roomsSectionData.isHidden } if (hasNoRoom) { val emptyState = when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> { @@ -373,14 +361,14 @@ class RoomListFragment @Inject constructor( image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper), message = getString(R.string.room_list_catchup_empty_body)) } - RoomListDisplayMode.PEOPLE -> + 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 -> + RoomListDisplayMode.ROOMS -> StateView.State.Empty( title = getString(R.string.room_list_rooms_empty_title), image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room), diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 2a214e6e90..423a950591 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -17,10 +17,7 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import androidx.paging.PagedList import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -34,27 +31,26 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch 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.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.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.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import timber.log.Timber import javax.inject.Inject -class RoomListViewModel @Inject constructor(initialState: RoomListViewState, - private val session: Session, - private val stringProvider: StringProvider) - : VectorViewModel(initialState) { +class RoomListViewModel @Inject constructor( + initialState: RoomListViewState, + private val session: Session, + private val stringProvider: StringProvider +) : VectorViewModel(initialState) { interface Factory { fun create(initialState: RoomListViewState): RoomListViewModel @@ -84,19 +80,9 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - data class RoomsSection( - val sectionName: String, - val livePages: LiveData>, - val isExpanded: MutableLiveData = MutableLiveData(true), - val notificationCount: MutableLiveData = - MutableLiveData(RoomAggregateNotificationCount(0, 0)), - val notifyOfLocalEcho: Boolean = false - ) - val sections: List by lazy { val sections = mutableListOf() if (initialState.displayMode == RoomListDisplayMode.PEOPLE) { - addSection(sections, R.string.invitations_header, true) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -113,7 +99,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, 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 @@ -143,18 +128,20 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, 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 { livePagedList -> - updatableQuery = livePagedList - sections.add(RoomsSection(name, livePagedList.livePagedList)) - } - } + 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 @@ -171,14 +158,14 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, override fun handle(action: RoomListAction) { when (action) { - is RoomListAction.SelectRoom -> handleSelectRoom(action) - is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action) - is RoomListAction.RejectInvitation -> handleRejectInvitation(action) - is RoomListAction.FilterWith -> handleFilter(action) - is RoomListAction.LeaveRoom -> handleLeaveRoom(action) + is RoomListAction.SelectRoom -> handleSelectRoom(action) + is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action) + is RoomListAction.RejectInvitation -> handleRejectInvitation(action) + is RoomListAction.FilterWith -> handleFilter(action) + is RoomListAction.LeaveRoom -> handleLeaveRoom(action) is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) - is RoomListAction.ToggleTag -> handleToggleTag(action) - is RoomListAction.ToggleSection -> handleToggleSection(action.section) + is RoomListAction.ToggleTag -> handleToggleTag(action) + is RoomListAction.ToggleSection -> handleToggleSection(action.section) }.exhaustive } @@ -186,40 +173,41 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, @StringRes nameRes: Int, notifyOfLocalEcho: Boolean = false, query: (RoomSummaryQueryParams.Builder) -> Unit) { - withQueryParams({ - query.invoke(it) - }) { roomQueryParams -> + withQueryParams( + { query.invoke(it) }, + { roomQueryParams -> - val name = stringProvider.getString(nameRes) - session.getPagedRoomSummariesLive(roomQueryParams) - .let { livePagedList -> + 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() + // 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 + 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) - } + RoomSummaryQueryParams.Builder() + .apply { builder.invoke(this) } + .build() + .let { block(it) } } fun isPublicRoom(roomId: String): Boolean { @@ -233,10 +221,13 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } 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) { @@ -247,8 +238,8 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } updatableQuery?.updateQuery( roomSummaryQueryParams { - this.memberships = Membership.activeMemberships() - this.displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) + memberships = Membership.activeMemberships() + displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) } ) } @@ -351,7 +342,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private fun String.otherTag(): String? { return when (this) { - RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY + RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY RoomTag.ROOM_TAG_LOW_PRIORITY -> RoomTag.ROOM_TAG_FAVOURITE else -> null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt index 38137e0033..20386d739a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt @@ -23,16 +23,19 @@ 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) { +class RoomSummaryPagedControllerFactory @Inject constructor( + private val roomSummaryItemFactory: RoomSummaryItemFactory +) { fun createRoomSummaryPagedController(): RoomSummaryPagedController { return RoomSummaryPagedController(roomSummaryItemFactory) } } -class RoomSummaryPagedController constructor(private val roomSummaryItemFactory: RoomSummaryItemFactory) - : PagedListEpoxyController( -// Important it must match the PageList builder notify Looper +class RoomSummaryPagedController( + private val roomSummaryItemFactory: RoomSummaryItemFactory +) : PagedListEpoxyController( + // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() ) { @@ -46,28 +49,20 @@ class RoomSummaryPagedController constructor(private val roomSummaryItemFactory: } override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { - val unwrappedItem = item // for place holder if enabled - ?: return roomSummaryItemFactory.createRoomItem( - RoomSummary( - roomId = "null_item_pos_$currentPosition", - name = "", - encryptionEventTs = null, - isEncrypted = false, - typingUsers = emptyList() - ), emptySet(), null, null) + item ?: return roomSummaryItemFactory.createRoomItem( + roomSummary = RoomSummary( + roomId = "null_item_pos_$currentPosition", + name = "", + encryptionEventTs = null, + isEncrypted = false, + typingUsers = emptyList() + ), + selectedRoomIds = emptySet(), + onClick = null, + onLongClick = null + ) -// GenericItem_().apply { id("null_item_pos_$currentPosition") } - - return roomSummaryItemFactory.create(unwrappedItem, roomChangeMembershipStates ?: emptyMap(), emptySet(), listener) + return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), listener) } - -// override fun onModelBound(holder: EpoxyViewHolder, boundModel: EpoxyModel<*>, position: Int, previouslyBoundModel: EpoxyModel<*>?) { -// Timber.w("VAL: Will load around $position") -// super.onModelBound(holder, boundModel, position, previouslyBoundModel) -// } -// fun onRoomLongClicked() { -// userPreferencesProvider.neverShowLongClickOnRoomHelpAgain() -// requestModelBuild() -// } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt new file mode 100644 index 0000000000..71b7169814 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt @@ -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>, + val isExpanded: MutableLiveData = MutableLiveData(true), + val notificationCount: MutableLiveData = MutableLiveData(RoomAggregateNotificationCount(0, 0)), + val notifyOfLocalEcho: Boolean = false +) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt index b137947ef7..f9c5766821 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt @@ -30,7 +30,7 @@ class SectionHeaderAdapter constructor( private val onClickAction: (() -> Unit) ) : RecyclerView.Adapter() { - data class SectionViewModel( + data class RoomsSectionData( val name: String, val isExpanded: Boolean = true, val notificationCount: Int = 0, @@ -38,12 +38,12 @@ class SectionHeaderAdapter constructor( val isHidden: Boolean = true ) - lateinit var section: SectionViewModel + lateinit var roomsSectionData: RoomsSectionData private set - fun updateSection(newSection: SectionViewModel) { - if (!::section.isInitialized || newSection != section) { - section = newSection + fun updateSection(newRoomsSectionData: RoomsSectionData) { + if (!::roomsSectionData.isInitialized || newRoomsSectionData != roomsSectionData) { + roomsSectionData = newRoomsSectionData notifyDataSetChanged() } } @@ -52,7 +52,7 @@ class SectionHeaderAdapter constructor( setHasStableIds(true) } - override fun getItemId(position: Int) = section.hashCode().toLong() + override fun getItemId(position: Int) = roomsSectionData.hashCode().toLong() override fun getItemViewType(position: Int) = R.layout.item_room_category @@ -61,10 +61,10 @@ class SectionHeaderAdapter constructor( } override fun onBindViewHolder(holder: VH, position: Int) { - holder.bind(section) + holder.bind(roomsSectionData) } - override fun getItemCount(): Int = if (section.isHidden) 0 else 1 + override fun getItemCount(): Int = if (roomsSectionData.isHidden) 0 else 1 class VH constructor( private val binding: ItemRoomCategoryBinding, @@ -77,14 +77,14 @@ class SectionHeaderAdapter constructor( })) } - fun bind(section: SectionViewModel) { - binding.roomCategoryTitleView.text = section.name + fun bind(roomsSectionData: RoomsSectionData) { + binding.roomCategoryTitleView.text = roomsSectionData.name val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.riotx_text_secondary) - val expandedArrowDrawableRes = if (section.isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white + val 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(section.notificationCount, section.isHighlighted)) + binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted)) binding.roomCategoryTitleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) } From 100b187be37ba8cefe49d2cca2feb7f3f014df8e Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 2 Apr 2021 11:43:56 +0200 Subject: [PATCH 16/18] Fix / loading initial state, duplicate sections and footer on empty --- .../app/features/home/room/list/RoomListFragment.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 8e837e85c6..ede3df2c18 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -95,11 +95,12 @@ class RoomListFragment @Inject constructor( ) private val adapterInfosList = mutableListOf() - private val concatAdapter = ConcatAdapter() + private var concatAdapter : ConcatAdapter? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupCreateRoomButton() + views.stateView.contentView = views.roomListView + views.stateView.state = StateView.State.Loading setupRecyclerView() sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents { @@ -134,10 +135,10 @@ class RoomListFragment @Inject constructor( val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue() if (actualBlock.section.isExpanded && !isRoomSectionExpanded) { // we have to remove the content adapter - concatAdapter.removeAdapter(actualBlock.contentAdapter.adapter) + concatAdapter?.removeAdapter(actualBlock.contentAdapter.adapter) } else if (!actualBlock.section.isExpanded && isRoomSectionExpanded) { // we must add it back! - concatAdapter.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) + concatAdapter?.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) } contentInsertIndex = if (isRoomSectionExpanded) { contentInsertIndex + 2 @@ -166,6 +167,7 @@ class RoomListFragment @Inject constructor( // TODO Cleanup listener on the ConcatAdapter's adapters? stateRestorer.clear() views.createChatFabMenu.listener = null + concatAdapter = null super.onDestroyView() } @@ -236,6 +238,8 @@ class RoomListFragment @Inject constructor( modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } + val concatAdapter = ConcatAdapter() + roomListViewModel.sections.forEach { section -> val sectionAdapter = SectionHeaderAdapter { roomListViewModel.handle(RoomListAction.ToggleSection(section)) @@ -280,6 +284,7 @@ class RoomListFragment @Inject constructor( footerController.listener = this concatAdapter.addAdapter(footerController.adapter) + this.concatAdapter = concatAdapter views.roomListView.adapter = concatAdapter } From 48292982555a8f84bae42ac6b45d6c88419c6a48 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 2 Apr 2021 11:44:10 +0200 Subject: [PATCH 17/18] ktlint --- .../android/sdk/internal/database/model/RoomSummaryEntity.kt | 1 - .../android/sdk/internal/session/room/DefaultRoomService.kt | 1 - vector/src/main/java/im/vector/app/AppStateHandler.kt | 3 +-- .../main/java/im/vector/app/features/home/ShortcutsHandler.kt | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 669074ffd2..c87ac15a78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -25,7 +25,6 @@ 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 org.matrix.android.sdk.api.session.room.model.tag.RoomTag -import timber.log.Timber internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index f1d4bfca3f..bd63ba480e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -195,4 +195,3 @@ internal class DefaultRoomService @Inject constructor( .executeBy(taskExecutor) } } - diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index b3b18d4adc..edec704f18 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -29,8 +29,7 @@ import javax.inject.Singleton */ // TODO Keep this class for now, will maybe be used fro Space @Singleton -class AppStateHandler @Inject constructor( -) : LifecycleObserver { +class AppStateHandler @Inject constructor() : LifecycleObserver { private val compositeDisposable = CompositeDisposable() diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 00ec8b43f9..4a2d001e1d 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -22,7 +22,6 @@ import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat import im.vector.app.core.di.ActiveSessionHolder -import io.reactivex.Observable import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposables import org.matrix.android.sdk.api.query.RoomTagQueryFilter From d49ed63f1d2e17beb8bb53b189d7cad71223d268 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 2 Apr 2021 11:56:38 +0200 Subject: [PATCH 18/18] fix / put back setup button --- .../im/vector/app/features/home/room/list/RoomListFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index ede3df2c18..aaa5bbcde5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -101,6 +101,7 @@ class RoomListFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) views.stateView.contentView = views.roomListView views.stateView.state = StateView.State.Loading + setupCreateRoomButton() setupRecyclerView() sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents {