From 7ea2d0a86da9da63a9bab8ce8d33a66416649e94 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 12 May 2022 11:32:31 +0200 Subject: [PATCH] Delete the local rooms when the room list is shown --- .../sdk/api/session/room/RoomService.kt | 5 ++ .../query/CurrentStateEventEntityQueries.kt | 11 ++- .../session/room/DefaultRoomService.kt | 6 ++ .../sdk/internal/session/room/RoomModule.kt | 5 ++ .../room/delete/DeleteLocalRoomTask.kt | 77 +++++++++++++++++++ .../home/room/detail/RoomDetailViewState.kt | 3 + .../home/room/detail/TimelineFragment.kt | 61 +++++++++------ .../home/room/detail/TimelineViewModel.kt | 42 +++++----- .../home/room/list/RoomListFragment.kt | 4 + .../home/room/list/RoomListViewModel.kt | 29 +++++++ .../home/room/list/RoomListViewState.kt | 3 +- 11 files changed, 201 insertions(+), 45 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.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 41a215636d..90f3f323b2 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 @@ -47,6 +47,11 @@ interface RoomService { */ suspend fun createLocalRoom(createRoomParams: CreateRoomParams): String + /** + * Delete a local room with all its related events. + */ + suspend fun deleteLocalRoom(roomId: String) + /** * Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt index e0dbf2eee8..e17d07c584 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/CurrentStateEventEntityQueries.kt @@ -23,13 +23,20 @@ import io.realm.kotlin.createObject import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields +internal fun CurrentStateEventEntity.Companion.whereRoomId( + realm: Realm, + roomId: String +): RealmQuery { + return realm.where(CurrentStateEventEntity::class.java) + .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) +} + internal fun CurrentStateEventEntity.Companion.whereType( realm: Realm, roomId: String, type: String ): RealmQuery { - return realm.where(CurrentStateEventEntity::class.java) - .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) + return whereRoomId(realm = realm, roomId = roomId) .equalTo(CurrentStateEventEntityFields.TYPE, type) } 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 cc5e935f87..6fd4f752a8 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 @@ -45,6 +45,7 @@ import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask +import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask @@ -62,6 +63,7 @@ internal class DefaultRoomService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val createRoomTask: CreateRoomTask, private val createLocalRoomTask: CreateLocalRoomTask, + private val deleteLocalRoomTask: DeleteLocalRoomTask, private val joinRoomTask: JoinRoomTask, private val markAllRoomsReadTask: MarkAllRoomsReadTask, private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, @@ -84,6 +86,10 @@ internal class DefaultRoomService @Inject constructor( return createLocalRoomTask.execute(createRoomParams) } + override suspend fun deleteLocalRoom(roomId: String) { + deleteLocalRoomTask.execute(DeleteLocalRoomTask.Params(roomId)) + } + override fun getRoom(roomId: String): Room? { return roomGetter.getRoom(roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index bb92287ead..556e31356e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -47,6 +47,8 @@ import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask +import org.matrix.android.sdk.internal.session.room.delete.DefaultDeleteLocalRoomTask +import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDirectoryVisibilityTask @@ -197,6 +199,9 @@ internal abstract class RoomModule { @Binds abstract fun bindCreateLocalRoomTask(task: DefaultCreateLocalRoomTask): CreateLocalRoomTask + @Binds + abstract fun bindDeleteLocalRoomTask(task: DefaultDeleteLocalRoomTask): DeleteLocalRoomTask + @Binds abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt new file mode 100644 index 0000000000..689fd24a97 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2022 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.internal.session.room.delete + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho +import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask.Params +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import timber.log.Timber +import javax.inject.Inject + +internal interface DeleteLocalRoomTask : Task { + data class Params(val roomId: String) +} + +internal class DefaultDeleteLocalRoomTask @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, +) : DeleteLocalRoomTask { + + override suspend fun execute(params: Params) { + val roomId = params.roomId + + if (RoomLocalEcho.isLocalEchoId(roomId)) { + monarchy.awaitTransaction { realm -> + Timber.i("## DeleteLocalRoomTask - delete local room id $roomId") + RoomMemberSummaryEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - RoomMemberSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + CurrentStateEventEntity.whereRoomId(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - CurrentStateEventEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + EventEntity.whereRoomId(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - EventEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + TimelineEventEntity.whereRoomId(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - TimelineEventEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + ChunkEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - RoomMemberSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + RoomSummaryEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - RoomSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + RoomEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - RoomEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() + } + } else { + Timber.i("## DeleteLocalRoomTask - Failed to remove room id $roomId: not a local room") + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 47db50d0d4..3ec7166739 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.sync.SyncRequestState import org.matrix.android.sdk.api.session.sync.SyncState @@ -103,4 +104,6 @@ data class RoomDetailViewState( fun isDm() = asyncRoomSummary()?.isDirect == true fun isThreadTimeline() = rootThreadEventId != null + + fun isLocalRoom() = RoomLocalEcho.isLocalEchoId(roomId) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 2b99bc67ef..0e818acec3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1695,31 +1695,41 @@ class TimelineFragment @Inject constructor( } private fun renderToolbar(roomSummary: RoomSummary?) { - if (!isThreadTimeLine()) { - views.includeRoomToolbar.roomToolbarContentView.isVisible = true - views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false - if (roomSummary == null) { - views.includeRoomToolbar.roomToolbarContentView.isClickable = false - } else { - views.includeRoomToolbar.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN - views.includeRoomToolbar.roomToolbarTitleView.text = roomSummary.displayName - avatarRenderer.render(roomSummary.toMatrixItem(), views.includeRoomToolbar.roomToolbarAvatarImageView) - val showPresence = roomSummary.isDirect - views.includeRoomToolbar.roomToolbarPresenceImageView.render(showPresence, roomSummary.directUserPresence) - val shieldView = if (showPresence) views.includeRoomToolbar.roomToolbarTitleShield else views.includeRoomToolbar.roomToolbarAvatarShield - shieldView.render(roomSummary.roomEncryptionTrustLevel) - views.includeRoomToolbar.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect + when { + isLocalRoom() -> { + views.includeRoomToolbar.roomToolbarContentView.isVisible = false + views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false + setupToolbar(views.roomToolbar) + .setTitle(R.string.fab_menu_create_chat) + .allowBack(useCross = true) } - } else { - views.includeRoomToolbar.roomToolbarContentView.isVisible = false - views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = true - timelineArgs.threadTimelineArgs?.let { - val matrixItem = MatrixItem.RoomItem(it.roomId, it.displayName, it.avatarUrl) - avatarRenderer.render(matrixItem, views.includeThreadToolbar.roomToolbarThreadImageView) - views.includeThreadToolbar.roomToolbarThreadShieldImageView.render(it.roomEncryptionTrustLevel) - views.includeThreadToolbar.roomToolbarThreadSubtitleTextView.text = it.displayName + isThreadTimeLine() -> { + views.includeRoomToolbar.roomToolbarContentView.isVisible = false + views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = true + timelineArgs.threadTimelineArgs?.let { + val matrixItem = MatrixItem.RoomItem(it.roomId, it.displayName, it.avatarUrl) + avatarRenderer.render(matrixItem, views.includeThreadToolbar.roomToolbarThreadImageView) + views.includeThreadToolbar.roomToolbarThreadShieldImageView.render(it.roomEncryptionTrustLevel) + views.includeThreadToolbar.roomToolbarThreadSubtitleTextView.text = it.displayName + } + views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_timeline_title) + } + else -> { + views.includeRoomToolbar.roomToolbarContentView.isVisible = true + views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false + if (roomSummary == null) { + views.includeRoomToolbar.roomToolbarContentView.isClickable = false + } else { + views.includeRoomToolbar.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN + views.includeRoomToolbar.roomToolbarTitleView.text = roomSummary.displayName + avatarRenderer.render(roomSummary.toMatrixItem(), views.includeRoomToolbar.roomToolbarAvatarImageView) + val showPresence = roomSummary.isDirect + views.includeRoomToolbar.roomToolbarPresenceImageView.render(showPresence, roomSummary.directUserPresence) + val shieldView = if (showPresence) views.includeRoomToolbar.roomToolbarTitleShield else views.includeRoomToolbar.roomToolbarAvatarShield + shieldView.render(roomSummary.roomEncryptionTrustLevel) + views.includeRoomToolbar.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect + } } - views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_timeline_title) } } @@ -2658,6 +2668,11 @@ class TimelineFragment @Inject constructor( */ private fun isThreadTimeLine(): Boolean = timelineArgs.threadTimelineArgs?.rootThreadEventId != null + /** + * Returns true if the current room is a local room, false otherwise. + */ + private fun isLocalRoom(): Boolean = withState(timelineViewModel) { it.isLocalRoom() } + /** * Returns the root thread event if we are in a thread room, otherwise returns null. */ diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 07b20b4914..6ad356e82d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -732,26 +732,30 @@ class TimelineViewModel @AssistedInject constructor( return@withState false } - if (initialState.isThreadTimeline()) { - when (itemId) { - R.id.menu_thread_timeline_view_in_room, - R.id.menu_thread_timeline_copy_link, - R.id.menu_thread_timeline_share -> true - else -> false + when { + initialState.isLocalRoom() -> false + initialState.isThreadTimeline() -> { + when (itemId) { + R.id.menu_thread_timeline_view_in_room, + R.id.menu_thread_timeline_copy_link, + R.id.menu_thread_timeline_share -> true + else -> false + } } - } else { - when (itemId) { - R.id.timeline_setting -> true - R.id.invite -> state.canInvite - R.id.open_matrix_apps -> true - R.id.voice_call -> state.isCallOptionAvailable() - R.id.video_call -> state.isCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined - // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ - R.id.join_conference -> !state.isCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined - R.id.search -> state.isSearchAvailable() - R.id.menu_timeline_thread_list -> vectorPreferences.areThreadMessagesEnabled() - R.id.dev_tools -> vectorPreferences.developerMode() - else -> false + else -> { + when (itemId) { + R.id.timeline_setting -> true + R.id.invite -> state.canInvite + R.id.open_matrix_apps -> true + R.id.voice_call -> state.isCallOptionAvailable() + R.id.video_call -> state.isCallOptionAvailable() || state.jitsiState.confId == null || state.jitsiState.hasJoined + // Show Join conference button only if there is an active conf id not joined. Otherwise fallback to default video disabled. ^ + R.id.join_conference -> !state.isCallOptionAvailable() && state.jitsiState.confId != null && !state.jitsiState.hasJoined + R.id.search -> state.isSearchAvailable() + R.id.menu_timeline_thread_list -> vectorPreferences.areThreadMessagesEnabled() + R.id.dev_tools -> vectorPreferences.developerMode() + else -> false + } } } } 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 5539986118..c25fe546c3 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 @@ -146,6 +146,10 @@ class RoomListFragment @Inject constructor( (it.contentEpoxyController as? RoomSummaryPagedController)?.roomChangeMembershipStates = ms } } + roomListViewModel.onEach(RoomListViewState::localRoomIds) { + // Local rooms should not exist anymore when the room list is shown + roomListViewModel.deleteLocalRooms(it) + } } private fun refreshCollapseStates() { 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 49467c0531..cd4bb06949 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 @@ -48,7 +48,10 @@ import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult 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.localecho.RoomLocalEcho 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.util.toMatrixItem import org.matrix.android.sdk.flow.flow @@ -96,6 +99,7 @@ class RoomListViewModel @AssistedInject constructor( init { observeMembershipChanges() + observeLocalRooms() appStateHandler.selectedRoomGroupingFlow .distinctUntilChanged() @@ -123,6 +127,23 @@ class RoomListViewModel @AssistedInject constructor( } } + private fun observeLocalRooms() { + val queryParams = roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + } + session + .flow() + .liveRoomSummaries(queryParams) + .map { roomSummaries -> + roomSummaries.mapNotNull { summary -> + summary.roomId.takeIf { RoomLocalEcho.isLocalEchoId(it) } + }.toSet() + } + .setOnEach { roomIds -> + copy(localRoomIds = roomIds) + } + } + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() private val roomListSectionBuilder = if (appStateHandler.getCurrentRoomGroupingMethod() is RoomGroupingMethod.BySpace) { @@ -173,6 +194,14 @@ class RoomListViewModel @AssistedInject constructor( return session.getRoom(roomId)?.stateService()?.isPublic().orFalse() } + fun deleteLocalRooms(roomsIds: Set) { + viewModelScope.launch { + roomsIds.forEach { + session.roomService().deleteLocalRoom(it) + } + } + } + // PRIVATE METHODS ***************************************************************************** private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState { 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 46ff6c242b..9d386b679a 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 @@ -30,7 +30,8 @@ data class RoomListViewState( val roomMembershipChanges: Map = emptyMap(), val asyncSuggestedRooms: Async> = Uninitialized, val currentUserName: String? = null, - val currentRoomGrouping: Async = Uninitialized + val currentRoomGrouping: Async = Uninitialized, + val localRoomIds: Set = emptySet() ) : MavericksState { constructor(args: RoomListParams) : this(displayMode = args.displayMode)