Delete the local rooms when the room list is shown

This commit is contained in:
Florian Renaud 2022-05-12 11:32:31 +02:00
parent 10d683ad5d
commit 7ea2d0a86d
11 changed files with 201 additions and 45 deletions

View file

@ -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.
*/

View file

@ -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<CurrentStateEventEntity> {
return realm.where(CurrentStateEventEntity::class.java)
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
}
internal fun CurrentStateEventEntity.Companion.whereType(
realm: Realm,
roomId: String,
type: String
): RealmQuery<CurrentStateEventEntity> {
return realm.where(CurrentStateEventEntity::class.java)
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
return whereRoomId(realm = realm, roomId = roomId)
.equalTo(CurrentStateEventEntityFields.TYPE, type)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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<Params, Unit> {
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")
}
}
}

View file

@ -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)
}

View file

@ -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.
*/

View file

@ -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
}
}
}
}

View file

@ -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() {

View file

@ -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<RoomListViewModel, RoomListViewState> 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<String>) {
viewModelScope.launch {
roomsIds.forEach {
session.roomService().deleteLocalRoom(it)
}
}
}
// PRIVATE METHODS *****************************************************************************
private fun handleSelectRoom(action: RoomListAction.SelectRoom) = withState {

View file

@ -30,7 +30,8 @@ data class RoomListViewState(
val roomMembershipChanges: Map<String, ChangeMembershipState> = emptyMap(),
val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized,
val currentUserName: String? = null,
val currentRoomGrouping: Async<RoomGroupingMethod> = Uninitialized
val currentRoomGrouping: Async<RoomGroupingMethod> = Uninitialized,
val localRoomIds: Set<String> = emptySet()
) : MavericksState {
constructor(args: RoomListParams) : this(displayMode = args.displayMode)