From c9ab0927f06309f56f71b460321c54abadbd9c47 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 11 May 2022 16:19:33 +0200 Subject: [PATCH 01/26] Start DM - Add feature flag --- .../debug/features/DebugFeaturesStateFactory.kt | 5 +++++ .../features/debug/features/DebugVectorFeatures.kt | 4 ++++ .../java/im/vector/app/features/VectorFeatures.kt | 2 ++ .../createdirect/CreateDirectRoomViewModel.kt | 11 +++++++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt index 248d9d232b..5506c2497c 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt @@ -70,6 +70,11 @@ class DebugFeaturesStateFactory @Inject constructor( key = DebugFeatureKeys.allowExternalUnifiedPushDistributors, factory = VectorFeatures::allowExternalUnifiedPushDistributors ), + createBooleanFeature( + label = "Start DM on first message", + key = DebugFeatureKeys.startDmOnFirstMsg, + factory = VectorFeatures::shouldStartDmOnFirstMessage + ), ) ) } diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 919cc6635e..617284d9e4 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -66,6 +66,9 @@ class DebugVectorFeatures( override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing) ?: vectorFeatures.isScreenSharingEnabled() + override fun shouldStartDmOnFirstMessage(): Boolean = read(DebugFeatureKeys.startDmOnFirstMsg) + ?: vectorFeatures.shouldStartDmOnFirstMessage() + fun override(value: T?, key: Preferences.Key) = updatePreferences { if (value == null) { it.remove(key) @@ -123,4 +126,5 @@ object DebugFeatureKeys { val allowExternalUnifiedPushDistributors = booleanPreferencesKey("allow-external-unified-push-distributors") val liveLocationSharing = booleanPreferencesKey("live-location-sharing") val screenSharing = booleanPreferencesKey("screen-sharing") + val startDmOnFirstMsg = booleanPreferencesKey("start-dm-on-first-msg") } diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt index 6fe4beff95..ad49916a07 100644 --- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt +++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt @@ -30,6 +30,7 @@ interface VectorFeatures { fun isOnboardingCombinedLoginEnabled(): Boolean fun allowExternalUnifiedPushDistributors(): Boolean fun isScreenSharingEnabled(): Boolean + fun shouldStartDmOnFirstMessage(): Boolean enum class OnboardingVariant { LEGACY, @@ -48,4 +49,5 @@ class DefaultVectorFeatures : VectorFeatures { override fun isOnboardingCombinedLoginEnabled() = false override fun allowExternalUnifiedPushDistributors(): Boolean = Config.ALLOW_EXTERNAL_UNIFIED_PUSH_DISTRIBUTORS override fun isScreenSharingEnabled(): Boolean = true + override fun shouldStartDmOnFirstMessage(): Boolean = false } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index 8374f9d513..5574ce3e63 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -26,6 +26,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.VectorFeatures import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.CreatedRoom import im.vector.app.features.raw.wellknown.getElementWellknown @@ -46,7 +47,8 @@ class CreateDirectRoomViewModel @AssistedInject constructor( @Assisted initialState: CreateDirectRoomViewState, private val rawService: RawService, val session: Session, - val analyticsTracker: AnalyticsTracker + val analyticsTracker: AnalyticsTracker, + val vectorFeatures: VectorFeatures ) : VectorViewModel(initialState) { @@ -124,7 +126,12 @@ class CreateDirectRoomViewModel @AssistedInject constructor( } val result = runCatchingToAsync { - session.roomService().createRoom(roomParams) + if (vectorFeatures.shouldStartDmOnFirstMessage()) { + // Todo: Prepare direct room creation + throw Throwable("Start DM on first message is not implemented yet.") + } else { + session.roomService().createRoom(roomParams) + } } analyticsTracker.capture(CreatedRoom(isDM = roomParams.isDirect.orFalse())) From ba3d3501274bd7c2f372f54a87ee04f3527a4f81 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 11 May 2022 16:20:27 +0200 Subject: [PATCH 02/26] Fix VectorFeature pref key --- .../vector/app/features/debug/features/DebugVectorFeatures.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt index 617284d9e4..7f74737719 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt @@ -119,7 +119,7 @@ private fun > enumPreferencesKey(type: KClass) = stringPreference object DebugFeatureKeys { val onboardingAlreadyHaveAnAccount = booleanPreferencesKey("onboarding-already-have-an-account") val onboardingSplashCarousel = booleanPreferencesKey("onboarding-splash-carousel") - val onboardingUseCase = booleanPreferencesKey("onboarding-splash-carousel") + val onboardingUseCase = booleanPreferencesKey("onboarding-use-case") val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize") val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register") val onboardingCombinedLogin = booleanPreferencesKey("onboarding-combined-login") From 26d1a12b74955df839ef513b6987d5d520662ec9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 16 Mar 2022 17:43:29 +0100 Subject: [PATCH 03/26] Start DM - Rename action button to "go" --- vector/src/main/res/menu/vector_create_direct_room.xml | 2 +- vector/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/menu/vector_create_direct_room.xml b/vector/src/main/res/menu/vector_create_direct_room.xml index 8c6eab1c52..10b2adf23d 100755 --- a/vector/src/main/res/menu/vector_create_direct_room.xml +++ b/vector/src/main/res/menu/vector_create_direct_room.xml @@ -4,7 +4,7 @@ diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index f60da53c09..b507ed473f 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1623,6 +1623,7 @@ "CREATE" + Go "Room name" "Name" "Room topic (optional)" From 10d683ad5d1bf931b7578cfa558108826c7fb407 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 17 Mar 2022 10:37:04 +0100 Subject: [PATCH 04/26] Start DM - display a local room before creating the real one Add CreateLocalRoomTask interface and remove DI annotations --- .../sdk/api/session/room/RoomService.kt | 7 + .../room/model/localecho/RoomLocalEcho.kt | 28 ++ .../session/room/DefaultRoomService.kt | 6 + .../sdk/internal/session/room/RoomModule.kt | 5 + .../room/create/CreateLocalRoomTask.kt | 267 ++++++++++++++++++ .../session/room/create/CreateRoomBody.kt | 17 ++ .../session/room/create/CreateRoomTask.kt | 29 +- .../session/room/timeline/DefaultTimeline.kt | 2 + .../createdirect/CreateDirectRoomAction.kt | 4 +- .../createdirect/CreateDirectRoomActivity.kt | 2 +- .../createdirect/CreateDirectRoomViewModel.kt | 20 +- .../createdirect/CreateDirectRoomViewState.kt | 2 + 12 files changed, 352 insertions(+), 37 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.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 5dfb8961e3..41a215636d 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 @@ -40,6 +40,13 @@ interface RoomService { */ suspend fun createRoom(createRoomParams: CreateRoomParams): String + /** + * Create a room locally. + * This room will not be synchronized with the server and will not come back from the sync, so all the events related to this room will be generated + * locally. + */ + suspend fun createLocalRoom(createRoomParams: CreateRoomParams): 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/api/session/room/model/localecho/RoomLocalEcho.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt new file mode 100644 index 0000000000..a9804c6dac --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.localecho + +import java.util.UUID + +object RoomLocalEcho { + + private const val PREFIX = "!local." + + fun isLocalEchoId(roomId: String) = roomId.startsWith(PREFIX) + + fun createLocalEchoId() = "${PREFIX}${UUID.randomUUID()}" +} 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 5e6d052443..cc5e935f87 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 @@ -43,6 +43,7 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie import org.matrix.android.sdk.internal.di.SessionDatabase 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.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper @@ -60,6 +61,7 @@ import javax.inject.Inject internal class DefaultRoomService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val createRoomTask: CreateRoomTask, + private val createLocalRoomTask: CreateLocalRoomTask, private val joinRoomTask: JoinRoomTask, private val markAllRoomsReadTask: MarkAllRoomsReadTask, private val updateBreadcrumbsTask: UpdateBreadcrumbsTask, @@ -78,6 +80,10 @@ internal class DefaultRoomService @Inject constructor( return createRoomTask.executeRetry(createRoomParams, 3) } + override suspend fun createLocalRoom(createRoomParams: CreateRoomParams): String { + return createLocalRoomTask.execute(createRoomParams) + } + 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 f3845f1f15..bb92287ead 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 @@ -43,7 +43,9 @@ import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAli 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.alias.GetRoomLocalAliasesTask +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.directory.DefaultGetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.DefaultGetRoomDirectoryVisibilityTask @@ -192,6 +194,9 @@ internal abstract class RoomModule { @Binds abstract fun bindCreateRoomTask(task: DefaultCreateRoomTask): CreateRoomTask + @Binds + abstract fun bindCreateLocalRoomTask(task: DefaultCreateLocalRoomTask): CreateLocalRoomTask + @Binds abstract fun bindGetPublicRoomTask(task: DefaultGetPublicRoomTask): GetPublicRoomTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt new file mode 100644 index 0000000000..e650039d75 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -0,0 +1,267 @@ +/* + * 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.create + +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmConfiguration +import io.realm.kotlin.createObject +import kotlinx.coroutines.TimeoutCancellationException +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.helper.addTimelineEvent +import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.mapper.toEntity +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.EventInsertType +import org.matrix.android.sdk.internal.database.model.RoomEntity +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.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.getOrNull +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent +import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater +import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock +import java.util.UUID +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +internal interface CreateLocalRoomTask : Task + +internal class DefaultCreateLocalRoomTask @Inject constructor( + @UserId private val userId: String, + @SessionDatabase private val monarchy: Monarchy, + private val roomMemberEventHandler: RoomMemberEventHandler, + private val roomSummaryUpdater: RoomSummaryUpdater, + @SessionDatabase private val realmConfiguration: RealmConfiguration, + private val createRoomBodyBuilder: CreateRoomBodyBuilder, + private val userService: UserService, + private val clock: Clock, +) : CreateLocalRoomTask { + + override suspend fun execute(params: CreateRoomParams): String { + val createRoomBody = createRoomBodyBuilder.build(params.withDefault()) + val roomId = RoomLocalEcho.createLocalEchoId() + monarchy.awaitTransaction { realm -> + createLocalRoomEntity(realm, roomId, createRoomBody) + createLocalRoomSummaryEntity(realm, roomId, createRoomBody) + } + + // Wait for room to be created in DB + try { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) + } + } catch (exception: TimeoutCancellationException) { + throw CreateRoomFailure.CreatedWithTimeout(roomId) + } + + return roomId + } + + /** + * Create a local room entity from the given room creation params. + * This will also generate and store in database the chunk and the events related to the room params in order to retrieve and display the local room. + */ + private suspend fun createLocalRoomEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { + RoomEntity.getOrCreate(realm, roomId).apply { + membership = Membership.JOIN + chunks.add(createLocalRoomChunk(realm, roomId, createRoomBody)) + } + } + + private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { + val otherUserId = createRoomBody.getDirectUserId() + if (otherUserId != null) { + RoomSummaryEntity.getOrCreate(realm, roomId).apply { + isDirect = true + directUserId = otherUserId + } + } + roomSummaryUpdater.update( + realm = realm, + roomId = roomId, + membership = Membership.JOIN, + roomSummary = RoomSyncSummary( + heroes = createRoomBody.invitedUserIds.orEmpty().take(5), + joinedMembersCount = 1, + invitedMembersCount = createRoomBody.invitedUserIds?.size ?: 0 + ), + updateMembers = !createRoomBody.invitedUserIds.isNullOrEmpty() + ) + } + + /** + * Create a single chunk containing the necessary events to display the local room. + * + * @param realm the current instance of realm + * @param roomId the id of the local room + * @param createRoomBody the room creation params + * + * @return a chunk entity + */ + private suspend fun createLocalRoomChunk(realm: Realm, roomId: String, createRoomBody: CreateRoomBody): ChunkEntity { + val chunkEntity = realm.createObject().apply { + isLastBackward = true + isLastForward = true + } + + val eventList = createLocalRoomEvents(createRoomBody) + val eventIds = ArrayList(eventList.size) + val roomMemberContentsByUser = HashMap() + + for (event in eventList) { + if (event.eventId == null || event.senderId == null || event.type == null) { + continue + } + + eventIds.add(event.eventId) + + val eventEntity = event.toEntity(roomId, SendState.SYNCED, null).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) + if (event.stateKey != null) { + CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { + eventId = event.eventId + root = eventEntity + } + if (event.type == EventType.STATE_ROOM_MEMBER) { + roomMemberContentsByUser[event.stateKey] = event.getFixedRoomMemberContent() + roomMemberEventHandler.handle(realm, roomId, event, false) + } + } + + roomMemberContentsByUser.getOrPut(event.senderId) { + // If we don't have any new state on this user, get it from db + val rootStateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, event.senderId, EventType.STATE_ROOM_MEMBER)?.root + rootStateEvent?.asDomain()?.getFixedRoomMemberContent() + } + + chunkEntity.addTimelineEvent( + roomId = roomId, + eventEntity = eventEntity, + direction = PaginationDirection.FORWARDS, + roomMemberContentsByUser = roomMemberContentsByUser + ) + } + + return chunkEntity + } + + /** + * Build the list of the events related to the room creation params. + * + * @param createRoomBody the room creation params + * + * @return the list of events + */ + private suspend fun createLocalRoomEvents(createRoomBody: CreateRoomBody): List { + val myUser = userService.getUser(userId) ?: User(userId) + val invitedUsers = createRoomBody.invitedUserIds.orEmpty() + .mapNotNull { tryOrNull { userService.resolveUser(it) } } + + val createRoomEvent = createFakeEvent( + type = EventType.STATE_ROOM_CREATE, + content = RoomCreateContent( + creator = userId + ).toContent() + ) + val myRoomMemberEvent = createFakeEvent( + type = EventType.STATE_ROOM_MEMBER, + content = RoomMemberContent( + membership = Membership.JOIN, + displayName = myUser.displayName, + avatarUrl = myUser.avatarUrl + ).toContent(), + stateKey = userId + ) + val roomMemberEvents = invitedUsers.map { + createFakeEvent( + type = EventType.STATE_ROOM_MEMBER, + content = RoomMemberContent( + isDirect = createRoomBody.isDirect.orFalse(), + membership = Membership.INVITE, + displayName = it.displayName, + avatarUrl = it.avatarUrl + ).toContent(), + stateKey = it.userId + ) + } + + return buildList { + add(createRoomEvent) + add(myRoomMemberEvent) + addAll(createRoomBody.initialStates.orEmpty().map { createFakeEvent(it.type, it.content, it.stateKey) }) + addAll(roomMemberEvents) + } + } + + /** + * Generate a local event from the given parameters. + * + * @param type the event type, see [EventType] + * @param content the content of the Event + * @param stateKey the stateKey, if any + * + * @return a fake event + */ + private fun createFakeEvent(type: String?, content: Content?, stateKey: String? = ""): Event { + return Event( + type = type, + senderId = userId, + stateKey = stateKey, + content = content, + originServerTs = clock.epochMillis(), + eventId = UUID.randomUUID().toString() + ) + } + + /** + * Setup default values to the CreateRoomParams as the room is created locally (the default values will not be defined by the server). + */ + private fun CreateRoomParams.withDefault() = this.apply { + if (visibility == null) visibility = RoomDirectoryVisibility.PRIVATE + if (historyVisibility == null) historyVisibility = RoomHistoryVisibility.SHARED + if (guestAccess == null) guestAccess = GuestAccess.Forbidden + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt index cffa632768..b326c3618c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt @@ -120,3 +120,20 @@ internal data class CreateRoomBody( @Json(name = "room_version") val roomVersion: String? ) + +/** + * Tells if the created room can be a direct chat one. + * + * @return true if it is a direct chat + */ +private fun CreateRoomBody.isDirect(): Boolean { + return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT && isDirect == true +} + +internal fun CreateRoomBody.getDirectUserId(): String? { + return if (isDirect()) { + invitedUserIds?.firstOrNull() + ?: invite3pids?.firstOrNull()?.address + ?: throw IllegalStateException("You can't create a direct room without an invitedUser") + } else null +} 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 6dd2c91048..d76640573f 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 @@ -62,11 +62,6 @@ internal class DefaultCreateRoomTask @Inject constructor( ) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val otherUserId = if (params.isDirect()) { - params.getFirstInvitedUserId() - ?: throw IllegalStateException("You can't create a direct room without an invitedUser") - } else null - if (params.preset == CreateRoomPreset.PRESET_PUBLIC_CHAT) { try { aliasAvailabilityChecker.check(params.roomAliasName) @@ -111,14 +106,13 @@ internal class DefaultCreateRoomTask @Inject constructor( RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis() } - if (otherUserId != null) { - handleDirectChatCreation(roomId, otherUserId) - } + handleDirectChatCreation(roomId, createRoomBody.getDirectUserId()) setReadMarkers(roomId) return roomId } - private suspend fun handleDirectChatCreation(roomId: String, otherUserId: String) { + private suspend fun handleDirectChatCreation(roomId: String, otherUserId: String?) { + otherUserId ?: return // This is not a direct room monarchy.awaitTransaction { realm -> RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { this.directUserId = otherUserId @@ -133,21 +127,4 @@ internal class DefaultCreateRoomTask @Inject constructor( val setReadMarkerParams = SetReadMarkersTask.Params(roomId, forceReadReceipt = true, forceReadMarker = true) return readMarkersTask.execute(setReadMarkerParams) } - - /** - * Tells if the created room can be a direct chat one. - * - * @return true if it is a direct chat - */ - private fun CreateRoomParams.isDirect(): Boolean { - return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT && - isDirect == true - } - - /** - * @return the first invited user id - */ - private fun CreateRoomParams.getFirstInvitedUserId(): String? { - return invitedUserIds.firstOrNull() ?: invite3pids.firstOrNull()?.value - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 44a786a95d..05379a1a7b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -383,6 +384,7 @@ internal class DefaultTimeline( } private suspend fun loadRoomMembersIfNeeded() { + if (RoomLocalEcho.isLocalEchoId(roomId)) return val loadRoomMembersParam = LoadRoomMembersTask.Params(roomId) try { loadRoomMembersTask.execute(loadRoomMembersParam) diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt index 83c7f0a13b..b5657598ee 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomAction.kt @@ -20,10 +20,12 @@ import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.userdirectory.PendingSelection sealed class CreateDirectRoomAction : VectorViewModelAction { - data class CreateRoomAndInviteSelectedUsers( + data class PrepareRoomWithSelectedUsers( val selections: Set ) : CreateDirectRoomAction() + object CreateRoomAndInviteSelectedUsers : CreateDirectRoomAction() + data class QrScannedAction( val result: String ) : CreateDirectRoomAction() diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt index 6292217b67..377acd2271 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt @@ -161,7 +161,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_create_direct_room) { - viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(action.selections)) + viewModel.handle(CreateDirectRoomAction.PrepareRoomWithSelectedUsers(action.selections)) } } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt index 5574ce3e63..b306cb6e03 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt @@ -61,7 +61,8 @@ class CreateDirectRoomViewModel @AssistedInject constructor( override fun handle(action: CreateDirectRoomAction) { when (action) { - is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onSubmitInvitees(action.selections) + is CreateDirectRoomAction.PrepareRoomWithSelectedUsers -> onSubmitInvitees(action.selections) + is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> onCreateRoomWithInvitees() is CreateDirectRoomAction.QrScannedAction -> onCodeParsed(action) } } @@ -96,16 +97,18 @@ class CreateDirectRoomViewModel @AssistedInject constructor( } if (existingRoomId != null) { // Do not create a new DM, just tell that the creation is successful by passing the existing roomId - setState { - copy(createAndInviteState = Success(existingRoomId)) - } + setState { copy(createAndInviteState = Success(existingRoomId)) } } else { - // Create the DM - createRoomAndInviteSelectedUsers(selections) + createLocalRoomWithSelectedUsers(selections) } } - private fun createRoomAndInviteSelectedUsers(selections: Set) { + private fun onCreateRoomWithInvitees() { + // Create the DM + withState { createLocalRoomWithSelectedUsers(it.pendingSelections) } + } + + private fun createLocalRoomWithSelectedUsers(selections: Set) { setState { copy(createAndInviteState = Loading()) } viewModelScope.launch(Dispatchers.IO) { @@ -127,8 +130,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor( val result = runCatchingToAsync { if (vectorFeatures.shouldStartDmOnFirstMessage()) { - // Todo: Prepare direct room creation - throw Throwable("Start DM on first message is not implemented yet.") + session.roomService().createLocalRoom(roomParams) } else { session.roomService().createRoom(roomParams) } diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt index 41366a7110..33360ac20c 100644 --- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt +++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewState.kt @@ -19,7 +19,9 @@ package im.vector.app.features.createdirect import com.airbnb.mvrx.Async import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.Uninitialized +import im.vector.app.features.userdirectory.PendingSelection data class CreateDirectRoomViewState( + val pendingSelections: Set = emptySet(), val createAndInviteState: Async = Uninitialized ) : MavericksState From 7ea2d0a86da9da63a9bab8ce8d33a66416649e94 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 12 May 2022 11:32:31 +0200 Subject: [PATCH 05/26] 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) From e86f9193dd61c08c80dffb8aea354bbeba2a1029 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 13 May 2022 18:09:39 +0200 Subject: [PATCH 06/26] Display timeline from the top of the screen for local rooms --- .../app/features/home/room/detail/TimelineFragment.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 0e818acec3..a3d342ef48 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 @@ -1426,6 +1426,9 @@ class TimelineFragment @Inject constructor( updateJumpToReadMarkerViewVisibility() jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() } + }.apply { + // For local rooms, pin the view's content to the top edge (the layout is reversed) + stackFromEnd = isLocalRoom() } val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController) @@ -1696,14 +1699,14 @@ class TimelineFragment @Inject constructor( private fun renderToolbar(roomSummary: RoomSummary?) { when { - isLocalRoom() -> { + isLocalRoom() -> { views.includeRoomToolbar.roomToolbarContentView.isVisible = false views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false setupToolbar(views.roomToolbar) .setTitle(R.string.fab_menu_create_chat) .allowBack(useCross = true) } - isThreadTimeLine() -> { + isThreadTimeLine() -> { views.includeRoomToolbar.roomToolbarContentView.isVisible = false views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = true timelineArgs.threadTimelineArgs?.let { @@ -1714,7 +1717,7 @@ class TimelineFragment @Inject constructor( } views.includeThreadToolbar.roomToolbarThreadTitleTextView.text = resources.getText(R.string.thread_timeline_title) } - else -> { + else -> { views.includeRoomToolbar.roomToolbarContentView.isVisible = true views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false if (roomSummary == null) { From b144bac578c721e2593d6f02556657d7fad7503c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 13 May 2022 13:56:56 +0200 Subject: [PATCH 07/26] Update wordings for local room timeline --- .../timeline/factory/EncryptionItemFactory.kt | 16 +++++-- .../factory/MergedHeaderItemFactory.kt | 2 + .../detail/timeline/item/BasedMergedItem.kt | 1 + .../timeline/item/MergedRoomCreationItem.kt | 45 +++++++++++++------ vector/src/main/res/values/strings.xml | 4 +- 5 files changed, 50 insertions(+), 18 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index dd058197b4..5f32696334 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoomSummary +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import javax.inject.Inject class EncryptionItemFactory @Inject constructor( @@ -55,12 +56,19 @@ class EncryptionItemFactory @Inject constructor( val description: String val shield: StatusTileTimelineItem.ShieldUIState if (isSafeAlgorithm) { + val isDirect = session.getRoomSummary(event.root.roomId.orEmpty())?.isDirect.orFalse() title = stringProvider.getString(R.string.encryption_enabled) description = stringProvider.getString( - if (session.getRoomSummary(event.root.roomId ?: "")?.isDirect.orFalse()) { - R.string.direct_room_encryption_enabled_tile_description - } else { - R.string.encryption_enabled_tile_description + when { + isDirect && RoomLocalEcho.isLocalEchoId(event.root.roomId.orEmpty()) -> { + R.string.direct_room_encryption_enabled_tile_description_future + } + isDirect -> { + R.string.direct_room_encryption_enabled_tile_description + } + else -> { + R.string.encryption_enabled_tile_description + } } ) shield = StatusTileTimelineItem.ShieldUIState.BLACK diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 771e42b63c..800fc27e77 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -117,6 +117,7 @@ class MergedHeaderItemFactory @Inject constructor( highlighted = true } val data = BasedMergedItem.Data( + roomId = mergedEvent.root.roomId, userId = mergedEvent.root.senderId ?: "", avatarUrl = mergedEvent.senderInfo.avatarUrl, memberName = mergedEvent.senderInfo.disambiguatedDisplayName, @@ -199,6 +200,7 @@ class MergedHeaderItemFactory @Inject constructor( highlighted = true } val data = BasedMergedItem.Data( + roomId = mergedEvent.root.roomId, userId = mergedEvent.root.senderId ?: "", avatarUrl = mergedEvent.senderInfo.avatarUrl, memberName = mergedEvent.senderInfo.disambiguatedDisplayName, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt index 1c56a0809e..4999036e9d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt @@ -54,6 +54,7 @@ abstract class BasedMergedItem : BaseEventItem() } data class Data( + val roomId: String?, val localId: Long, val eventId: String, val userId: String, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index f41c17d9e7..5b8d973a04 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -40,6 +40,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.tools.linkify import me.gujun.android.span.span 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.util.toMatrixItem @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) @@ -102,10 +103,16 @@ abstract class MergedRoomCreationItem : BasedMergedItem { + holder.expandView.resources.getString(R.string.direct_room_encryption_enabled_tile_description_future) + } + data?.isDirectRoom == true -> { + holder.expandView.resources.getString(R.string.direct_room_encryption_enabled_tile_description) + } + else -> { + holder.expandView.resources.getString(R.string.encryption_enabled_tile_description) + } } holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( @@ -130,17 +137,29 @@ abstract class MergedRoomCreationItem : BasedMergedItem { + if (isLocalRoom) { + holder.roomDescriptionText.text = holder.view.resources.getString( + R.string.send_your_first_msg_to_invite, + roomSummary?.displayName.orEmpty() + ) + } else { + holder.roomDescriptionText.text = holder.view.resources.getString( + R.string.this_is_the_beginning_of_dm, + roomSummary?.displayName.orEmpty() + ) + } + } + roomDisplayName.isNullOrBlank() || roomSummary.name.isBlank() -> { + holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room_no_name) + } + else -> { + holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room, roomDisplayName) + } } val topic = roomSummary?.topic diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index b507ed473f..f684ccadad 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2399,7 +2399,8 @@ Encryption enabled Messages in this room are end-to-end encrypted. Learn more & verify users in their profile. - Messages in this room are end-to-end encrypted. + Messages in this chat are end-to-end encrypted. + Messages in this chat will be end-to-end encrypted. Encryption not enabled Encryption is misconfigured The encryption used by this room is not supported @@ -2411,6 +2412,7 @@ This is the beginning of %s. This is the beginning of this conversation. This is the beginning of your direct message history with %s. + Send your first message to invite %s to chat %s to let people know what this room is about. Add a topic From a46d7ed8dd17064abde7e78ec1a16baddfd90f5b Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 13 May 2022 17:48:30 +0200 Subject: [PATCH 08/26] Hide unwanted events from local room timeline --- .../detail/timeline/TimelineEventController.kt | 15 +++++++++------ .../helper/TimelineEventVisibilityHelper.kt | 8 ++++++++ .../timeline/item/MergedRoomCreationItem.kt | 6 ++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index a02eaa3202..6ef4b21387 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -69,6 +69,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.RoomMemberContent 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.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent @@ -603,12 +604,14 @@ class TimelineEventController @Inject constructor( } private fun wantsDateSeparator(event: TimelineEvent, nextEvent: TimelineEvent?): Boolean { - return if (hasReachedInvite && hasUTD) { - true - } else { - val date = event.root.localDateTime() - val nextDate = nextEvent?.root?.localDateTime() - date.toLocalDate() != nextDate?.toLocalDate() + return when { + RoomLocalEcho.isLocalEchoId(partialState.roomSummary?.roomId.orEmpty()) -> false + hasReachedInvite && hasUTD -> true + else -> { + val date = event.root.localDateTime() + val nextDate = nextEvent?.root?.localDateTime() + date.toLocalDate() != nextDate?.toLocalDate() + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index e4e0ea8352..488aebde13 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @@ -176,6 +177,13 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen return true } + // Hide fake events for local rooms + if (RoomLocalEcho.isLocalEchoId(roomId) && + root.getClearType() == EventType.STATE_ROOM_MEMBER || + root.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY) { + return true + } + // Allow only the the threads within the rootThreadEventId along with the root event if (userPreferencesProvider.areThreadMessagesEnabled() && isFromThreadTimeline) { return if (root.getRootThreadEventId() == rootThreadEventId) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index 5b8d973a04..5b7c4efc7f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -58,7 +58,12 @@ abstract class MergedRoomCreationItem : BasedMergedItem(R.id.mergedSumContainer) val summaryView by bind(R.id.itemNoticeTextView) val avatarView by bind(R.id.itemNoticeAvatarView) val encryptionTile by bind(R.id.creationEncryptionTile) From 554d35fe96712e0faa64f5e83e66ff7a649cbf10 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Sat, 21 May 2022 00:18:27 +0200 Subject: [PATCH 09/26] Add changelog --- changelog.d/5525.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5525.wip diff --git a/changelog.d/5525.wip b/changelog.d/5525.wip new file mode 100644 index 0000000000..c4ac0ff008 --- /dev/null +++ b/changelog.d/5525.wip @@ -0,0 +1 @@ +Create DM room only on first message - Design implementation & debug feature flag From 398e98ae85a387eead479b695bf800fa65352fa3 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 24 May 2022 00:32:16 +0200 Subject: [PATCH 10/26] Remove useless variable --- .../sdk/internal/session/room/create/CreateLocalRoomTask.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index e650039d75..1fa2cfe35b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -149,7 +149,6 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } val eventList = createLocalRoomEvents(createRoomBody) - val eventIds = ArrayList(eventList.size) val roomMemberContentsByUser = HashMap() for (event in eventList) { @@ -157,8 +156,6 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( continue } - eventIds.add(event.eventId) - val eventEntity = event.toEntity(roomId, SendState.SYNCED, null).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { From d42a3da5b7b5dc664fd0f01f847fe5f0d013f07c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 14 Jun 2022 15:16:17 +0200 Subject: [PATCH 11/26] Reduce code smell --- .../timeline/item/MergedRoomCreationItem.kt | 82 +++++++++++-------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index 5b7c4efc7f..cd60057379 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.item +import android.content.res.Resources import android.text.SpannableString import android.text.method.MovementMethod import android.text.style.ClickableSpan @@ -25,7 +26,6 @@ import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat -import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute @@ -39,6 +39,7 @@ import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.tools.linkify import me.gujun.android.span.span +import org.matrix.android.sdk.api.extensions.orFalse 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.util.toMatrixItem @@ -62,44 +63,45 @@ abstract class MergedRoomCreationItem : BasedMergedItem { - holder.expandView.resources.getString(R.string.direct_room_encryption_enabled_tile_description_future) - } - data?.isDirectRoom == true -> { - holder.expandView.resources.getString(R.string.direct_room_encryption_enabled_tile_description) - } - else -> { - holder.expandView.resources.getString(R.string.encryption_enabled_tile_description) - } - } + holder.e2eTitleDescriptionView.text = getE2ESecureDescriptionText(holder.expandView.resources, data) holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black), @@ -137,17 +129,32 @@ abstract class MergedRoomCreationItem : BasedMergedItem { + if (attributes.isLocalRoom) { + resources.getString(R.string.direct_room_encryption_enabled_tile_description_future) + } else { + resources.getString(R.string.direct_room_encryption_enabled_tile_description) + } + } + else -> { + resources.getString(R.string.encryption_enabled_tile_description) + } + } + } + private fun bindCreationSummaryTile(holder: Holder) { val roomSummary = attributes.roomSummary val roomDisplayName = roomSummary?.displayName holder.roomNameText.setTextOrHide(roomDisplayName) val isDirect = roomSummary?.isDirect == true - val isLocalRoom = RoomLocalEcho.isLocalEchoId(roomSummary?.roomId.orEmpty()) val membersCount = roomSummary?.otherMemberIds?.size ?: 0 when { isDirect -> { - if (isLocalRoom) { + if (attributes.isLocalRoom) { holder.roomDescriptionText.text = holder.view.resources.getString( R.string.send_your_first_msg_to_invite, roomSummary?.displayName.orEmpty() @@ -261,5 +268,8 @@ abstract class MergedRoomCreationItem : BasedMergedItem Date: Fri, 1 Jul 2022 09:33:21 +0200 Subject: [PATCH 12/26] Show date separator in local room timeline --- .../home/room/detail/timeline/TimelineEventController.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 6ef4b21387..5b81b327aa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -605,7 +605,6 @@ class TimelineEventController @Inject constructor( private fun wantsDateSeparator(event: TimelineEvent, nextEvent: TimelineEvent?): Boolean { return when { - RoomLocalEcho.isLocalEchoId(partialState.roomSummary?.roomId.orEmpty()) -> false hasReachedInvite && hasUTD -> true else -> { val date = event.root.localDateTime() From c7db89613c40add99d8b88242e5817e158997582 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 30 Jun 2022 15:24:42 +0200 Subject: [PATCH 13/26] Split code in MergedRoomCreationItem --- .../timeline/item/MergedRoomCreationItem.kt | 168 ++++++++++-------- 1 file changed, 93 insertions(+), 75 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index 6371440d1a..b5c24a4480 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home.room.detail.timeline.item -import android.content.res.Resources import android.text.SpannableString import android.text.method.MovementMethod import android.text.style.ClickableSpan @@ -39,7 +38,6 @@ import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.tools.linkify import me.gujun.android.span.span -import org.matrix.android.sdk.api.extensions.orFalse 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.util.toMatrixItem @@ -53,6 +51,14 @@ abstract class MergedRoomCreationItem : BasedMergedItem { this.marginEnd = leftGuideline } if (attributes.isEncryptionAlgorithmSecure) { - holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_enabled) - holder.e2eTitleDescriptionView.text = getE2ESecureDescriptionText(holder.expandView.resources, data) - holder.e2eTitleDescriptionView.textAlignment = View.TEXT_ALIGNMENT_CENTER - holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( - ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_black), - null, null, null - ) + renderE2ESecureTile(holder) } else { - holder.e2eTitleTextView.text = holder.expandView.resources.getString(R.string.encryption_not_enabled) - holder.e2eTitleDescriptionView.text = holder.expandView.resources.getString(R.string.encryption_unknown_algorithm_tile_description) - holder.e2eTitleTextView.setCompoundDrawablesWithIntrinsicBounds( - ContextCompat.getDrawable(holder.view.context, R.drawable.ic_shield_warning), - null, null, null - ) + renderE2EUnsecureTile(holder) } } else { holder.encryptionTile.isVisible = false } } - private fun getE2ESecureDescriptionText(resources: Resources, data: Data?): String { - val isDirectRoom = data?.isDirectRoom.orFalse() - return when { + private fun renderE2ESecureTile(holder: Holder) { + val resources = holder.expandView.resources + val description = when { isDirectRoom -> { if (attributes.isLocalRoom) { resources.getString(R.string.direct_room_encryption_enabled_tile_description_future) @@ -143,65 +138,36 @@ abstract class MergedRoomCreationItem : BasedMergedItem { - if (attributes.isLocalRoom) { - holder.roomDescriptionText.text = holder.view.resources.getString( - R.string.send_your_first_msg_to_invite, - roomSummary?.displayName.orEmpty() - ) - } else { - holder.roomDescriptionText.text = holder.view.resources.getString( - R.string.this_is_the_beginning_of_dm, - roomSummary?.displayName.orEmpty() - ) - } - } - roomDisplayName.isNullOrBlank() || roomSummary.name.isBlank() -> { - holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room_no_name) - } - else -> { - holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room, roomDisplayName) - } - } - - val topic = roomSummary?.topic - if (topic.isNullOrBlank()) { - // do not show hint for DMs or group DMs - val canSetTopic = attributes.canChangeTopic && !isDirect - if (canSetTopic) { - val addTopicLink = holder.view.resources.getString(R.string.add_a_topic_link_text) - val styledText = SpannableString(holder.view.resources.getString(R.string.room_created_summary_no_topic_creation_text, addTopicLink)) - holder.roomTopicText.setTextOrHide(styledText.tappableMatchingText(addTopicLink, object : ClickableSpan() { - override fun onClick(widget: View) { - attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetTopic) - } - })) - } - } else { - holder.roomTopicText.setTextOrHide( - span { - span(holder.view.resources.getString(R.string.topic_prefix)) { - textStyle = "bold" - } - +topic.linkify(attributes.callback) - } - ) - } - holder.roomTopicText.movementMethod = movementMethod + holder.roomNameText.setTextOrHide(roomDisplayName) + renderRoomDescription(holder) + renderRoomTopic(holder) val roomItem = roomSummary?.toMatrixItem() val shouldSetAvatar = attributes.canChangeAvatar && - (roomSummary?.isDirect == false || (isDirect && membersCount >= 2)) && + (roomSummary?.isDirect == false || (isDirectRoom && membersCount >= 2)) && roomItem?.avatarUrl.isNullOrBlank() holder.roomAvatarImageView.isVisible = roomItem != null @@ -224,7 +190,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem { + if (attributes.isLocalRoom) { + holder.roomDescriptionText.text = holder.view.resources.getString( + R.string.send_your_first_msg_to_invite, + roomSummary?.displayName.orEmpty() + ) + } else { + holder.roomDescriptionText.text = holder.view.resources.getString( + R.string.this_is_the_beginning_of_dm, + roomSummary?.displayName.orEmpty() + ) + } + } + roomDisplayName.isNullOrBlank() || roomSummary?.name.isNullOrBlank() -> { + holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room_no_name) + } + else -> { + holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room, roomDisplayName) + } + } + } + + private fun renderRoomTopic(holder: Holder) { + val topic = roomSummary?.topic + if (topic.isNullOrBlank()) { + // do not show hint for DMs or group DMs + val canSetTopic = attributes.canChangeTopic && !isDirectRoom + if (canSetTopic) { + val addTopicLink = holder.view.resources.getString(R.string.add_a_topic_link_text) + val styledText = SpannableString(holder.view.resources.getString(R.string.room_created_summary_no_topic_creation_text, addTopicLink)) + holder.roomTopicText.setTextOrHide(styledText.tappableMatchingText(addTopicLink, object : ClickableSpan() { + override fun onClick(widget: View) { + attributes.callback?.onTimelineItemAction(RoomDetailAction.QuickActionSetTopic) + } + })) + } + } else { + holder.roomTopicText.setTextOrHide( + span { + span(holder.view.resources.getString(R.string.topic_prefix)) { + textStyle = "bold" + } + +topic.linkify(attributes.callback) + } + ) + } + holder.roomTopicText.movementMethod = movementMethod + } + class Holder : BasedMergedItem.Holder(STUB_ID) { val mergedView by bind(R.id.mergedSumContainer) val summaryView by bind(R.id.itemNoticeTextView) From 7415623c2fae0fce46e4f6c6c9616dadc5ae7513 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 1 Jul 2022 00:19:41 +0200 Subject: [PATCH 14/26] Update room description style --- .../timeline/item/MergedRoomCreationItem.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index b5c24a4480..47401c34e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -27,6 +27,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams +import androidx.core.widget.TextViewCompat import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -37,6 +38,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.tools.linkify +import im.vector.app.features.themes.ThemeUtils import me.gujun.android.span.span import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho @@ -201,27 +203,27 @@ abstract class MergedRoomCreationItem : BasedMergedItem { if (attributes.isLocalRoom) { - holder.roomDescriptionText.text = holder.view.resources.getString( - R.string.send_your_first_msg_to_invite, - roomSummary?.displayName.orEmpty() - ) + resources.getString(R.string.send_your_first_msg_to_invite, roomSummary?.displayName.orEmpty()) } else { - holder.roomDescriptionText.text = holder.view.resources.getString( - R.string.this_is_the_beginning_of_dm, - roomSummary?.displayName.orEmpty() - ) + resources.getString(R.string.this_is_the_beginning_of_dm, roomSummary?.displayName.orEmpty()) } } roomDisplayName.isNullOrBlank() || roomSummary?.name.isNullOrBlank() -> { - holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room_no_name) + holder.view.resources.getString(R.string.this_is_the_beginning_of_room_no_name) } else -> { - holder.roomDescriptionText.text = holder.view.resources.getString(R.string.this_is_the_beginning_of_room, roomDisplayName) + holder.view.resources.getString(R.string.this_is_the_beginning_of_room, roomDisplayName) } } + holder.roomDescriptionText.text = description + if (isDirectRoom && attributes.isLocalRoom) { + TextViewCompat.setTextAppearance(holder.roomDescriptionText, R.style.TextAppearance_Vector_Subtitle) + holder.roomDescriptionText.setTextColor(ThemeUtils.getColor(holder.roomDescriptionText.context, R.attr.vctr_content_primary)) + } } private fun renderRoomTopic(holder: Holder) { From 0dad4cb02a111de71f9970b9103f00fd80b73394 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 1 Jul 2022 09:57:45 +0200 Subject: [PATCH 15/26] Set current ts for local events age --- .../sdk/internal/session/room/create/CreateLocalRoomTask.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 1fa2cfe35b..811d7ef5b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -156,7 +156,8 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( continue } - val eventEntity = event.toEntity(roomId, SendState.SYNCED, null).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) + val now = clock.epochMillis() + val eventEntity = event.toEntity(roomId, SendState.SYNCED, now).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = event.eventId From c25edfada40ec2f7ae8358a2437f158497df7739 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 1 Jul 2022 10:09:26 +0200 Subject: [PATCH 16/26] Remove unused imports --- .../home/room/detail/timeline/TimelineEventController.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 5b81b327aa..18c626bda8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -69,7 +69,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.RoomMemberContent 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.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent @@ -605,8 +604,8 @@ class TimelineEventController @Inject constructor( private fun wantsDateSeparator(event: TimelineEvent, nextEvent: TimelineEvent?): Boolean { return when { - hasReachedInvite && hasUTD -> true - else -> { + hasReachedInvite && hasUTD -> true + else -> { val date = event.root.localDateTime() val nextDate = nextEvent?.root?.localDateTime() date.toLocalDate() != nextDate?.toLocalDate() From 8a5a47c6a57863b87f62ca2e123fe619d1d9b612 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 4 Jul 2022 17:38:32 +0200 Subject: [PATCH 17/26] Ensure that Realm is up to date before returning the roomId --- .../room/create/CreateLocalRoomTask.kt | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 811d7ef5b6..7f0d3a63e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -18,16 +18,13 @@ package org.matrix.android.sdk.internal.session.room.create import com.zhuinden.monarchy.Monarchy import io.realm.Realm -import io.realm.RealmConfiguration import io.realm.kotlin.createObject -import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility @@ -40,7 +37,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity @@ -49,7 +45,6 @@ import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity 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.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull @@ -63,7 +58,6 @@ import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.time.Clock import java.util.UUID -import java.util.concurrent.TimeUnit import javax.inject.Inject internal interface CreateLocalRoomTask : Task @@ -73,7 +67,6 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val roomMemberEventHandler: RoomMemberEventHandler, private val roomSummaryUpdater: RoomSummaryUpdater, - @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, private val userService: UserService, private val clock: Clock, @@ -87,16 +80,8 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( createLocalRoomSummaryEntity(realm, roomId, createRoomBody) } - // Wait for room to be created in DB - try { - awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> - realm.where(RoomSummaryEntity::class.java) - .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) - .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) - } - } catch (exception: TimeoutCancellationException) { - throw CreateRoomFailure.CreatedWithTimeout(roomId) - } + // Ensure that Realm is up to date before returning the roomId. + monarchy.doWithRealm { it.refresh() } return roomId } From f4b50f1e0f7823550d708cba70ddf3b868ae1ad8 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 4 Jul 2022 17:41:00 +0200 Subject: [PATCH 18/26] Fix logs when deleting local room --- .../sdk/internal/session/room/delete/DeleteLocalRoomTask.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 689fd24a97..a15cb4393c 100644 --- 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 @@ -61,7 +61,7 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor( ?.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") } + ?.also { Timber.i("## DeleteLocalRoomTask - ChunkEntity - delete ${it.size} entries") } ?.deleteAllFromRealm() RoomSummaryEntity.where(realm, roomId = roomId).findAll() ?.also { Timber.i("## DeleteLocalRoomTask - RoomSummaryEntity - delete ${it.size} entries") } @@ -71,7 +71,7 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor( ?.deleteAllFromRealm() } } else { - Timber.i("## DeleteLocalRoomTask - Failed to remove room id $roomId: not a local room") + Timber.i("## DeleteLocalRoomTask - Failed to remove room with id $roomId: not a local room") } } } From 0d9cd2b3a3bbf4e2b49fc3ff01102a548bd16e81 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 4 Jul 2022 17:47:48 +0200 Subject: [PATCH 19/26] Delete local room related entities with cascade --- .../sdk/internal/session/room/delete/DeleteLocalRoomTask.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 index a15cb4393c..936c94e520 100644 --- 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 @@ -25,6 +25,7 @@ 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.model.deleteOnCascade 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 @@ -59,10 +60,10 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor( ?.deleteAllFromRealm() TimelineEventEntity.whereRoomId(realm, roomId = roomId).findAll() ?.also { Timber.i("## DeleteLocalRoomTask - TimelineEventEntity - delete ${it.size} entries") } - ?.deleteAllFromRealm() + ?.forEach { it.deleteOnCascade(true) } ChunkEntity.where(realm, roomId = roomId).findAll() ?.also { Timber.i("## DeleteLocalRoomTask - ChunkEntity - delete ${it.size} entries") } - ?.deleteAllFromRealm() + ?.forEach { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } RoomSummaryEntity.where(realm, roomId = roomId).findAll() ?.also { Timber.i("## DeleteLocalRoomTask - RoomSummaryEntity - delete ${it.size} entries") } ?.deleteAllFromRealm() From e89bb0eea7c3c41828147fecc89384b7a32e1821 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 4 Jul 2022 17:53:21 +0200 Subject: [PATCH 20/26] Set local room members as loaded --- .../sdk/internal/session/room/create/CreateLocalRoomTask.kt | 2 ++ .../sdk/internal/session/room/timeline/DefaultTimeline.kt | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 7f0d3a63e3..eb35dd2387 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -44,6 +44,7 @@ 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.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate @@ -94,6 +95,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( RoomEntity.getOrCreate(realm, roomId).apply { membership = Membership.JOIN chunks.add(createLocalRoomChunk(realm, roomId, createRoomBody)) + membersLoadStatus = RoomMembersLoadStatusType.LOADED } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 6e72cbdb8f..d1eb8794bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -34,7 +34,6 @@ import okhttp3.internal.closeQuietly import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull 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.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -390,7 +389,6 @@ internal class DefaultTimeline( } private suspend fun loadRoomMembersIfNeeded() { - if (RoomLocalEcho.isLocalEchoId(roomId)) return val loadRoomMembersParam = LoadRoomMembersTask.Params(roomId, excludeMembership = Membership.LEAVE) try { loadRoomMembersTask.execute(loadRoomMembersParam) From 2b6bfc1ebc8a3ae89d68d23fde5c5cba7ff847bf Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 4 Jul 2022 18:00:30 +0200 Subject: [PATCH 21/26] Create local events using local echo --- .../session/room/create/CreateLocalRoomTask.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index eb35dd2387..94c300c3c3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.Membership @@ -58,7 +59,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.time.Clock -import java.util.UUID import javax.inject.Inject internal interface CreateLocalRoomTask : Task @@ -185,13 +185,13 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( val invitedUsers = createRoomBody.invitedUserIds.orEmpty() .mapNotNull { tryOrNull { userService.resolveUser(it) } } - val createRoomEvent = createFakeEvent( + val createRoomEvent = createLocalEvent( type = EventType.STATE_ROOM_CREATE, content = RoomCreateContent( creator = userId ).toContent() ) - val myRoomMemberEvent = createFakeEvent( + val myRoomMemberEvent = createLocalEvent( type = EventType.STATE_ROOM_MEMBER, content = RoomMemberContent( membership = Membership.JOIN, @@ -201,7 +201,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( stateKey = userId ) val roomMemberEvents = invitedUsers.map { - createFakeEvent( + createLocalEvent( type = EventType.STATE_ROOM_MEMBER, content = RoomMemberContent( isDirect = createRoomBody.isDirect.orFalse(), @@ -216,7 +216,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( return buildList { add(createRoomEvent) add(myRoomMemberEvent) - addAll(createRoomBody.initialStates.orEmpty().map { createFakeEvent(it.type, it.content, it.stateKey) }) + addAll(createRoomBody.initialStates.orEmpty().map { createLocalEvent(it.type, it.content, it.stateKey) }) addAll(roomMemberEvents) } } @@ -230,14 +230,14 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( * * @return a fake event */ - private fun createFakeEvent(type: String?, content: Content?, stateKey: String? = ""): Event { + private fun createLocalEvent(type: String?, content: Content?, stateKey: String? = ""): Event { return Event( type = type, senderId = userId, stateKey = stateKey, content = content, originServerTs = clock.epochMillis(), - eventId = UUID.randomUUID().toString() + eventId = LocalEcho.createLocalEchoId() ) } From 414dc52f7d56aee57dc4a380a8590a2229e01ae4 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 4 Jul 2022 18:03:43 +0200 Subject: [PATCH 22/26] Fix copyright date --- .../sdk/api/session/room/model/localecho/RoomLocalEcho.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt index a9804c6dac..e48baff5a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. From fb87d31ce479124a8cfcbeba109ae42d9e56fe1c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 11 Jul 2022 15:24:48 +0200 Subject: [PATCH 23/26] Update the title of the local room timeline --- .../im/vector/app/features/home/room/detail/TimelineFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d327bb48d4..25947cc22b 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 @@ -1709,7 +1709,7 @@ class TimelineFragment @Inject constructor( views.includeRoomToolbar.roomToolbarContentView.isVisible = false views.includeThreadToolbar.roomToolbarThreadConstraintLayout.isVisible = false setupToolbar(views.roomToolbar) - .setTitle(R.string.fab_menu_create_chat) + .setTitle(R.string.room_member_open_or_create_dm) .allowBack(useCross = true) } isThreadTimeLine() -> { From a10a8ce5ca8571e04fd11cef97b86560a06a0ab2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 11 Jul 2022 16:19:27 +0200 Subject: [PATCH 24/26] Add margin after the action button of the user list toolbar --- vector/src/main/res/layout/fragment_user_list.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/layout/fragment_user_list.xml b/vector/src/main/res/layout/fragment_user_list.xml index b3149f05c5..989cb2feef 100644 --- a/vector/src/main/res/layout/fragment_user_list.xml +++ b/vector/src/main/res/layout/fragment_user_list.xml @@ -15,7 +15,9 @@ android:id="@+id/userListToolbar" android:layout_width="match_parent" android:layout_height="?actionBarSize" - app:title="@string/fab_menu_create_chat"/> + android:paddingEnd="@dimen/layout_horizontal_margin" + app:title="@string/fab_menu_create_chat" + tools:ignore="RtlSymmetry" /> @@ -94,4 +96,4 @@ app:layout_constraintTop_toBottomOf="@id/userListE2EbyDefaultDisabled" tools:listitem="@layout/item_known_user" /> - \ No newline at end of file + From fdb9ed80d4955a28b35aa708e39258edcbf5b588 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 11 Jul 2022 16:22:49 +0200 Subject: [PATCH 25/26] Change method visibility --- .../sdk/api/session/room/model/localecho/RoomLocalEcho.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt index e48baff5a2..7ef0d63924 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/RoomLocalEcho.kt @@ -22,7 +22,10 @@ object RoomLocalEcho { private const val PREFIX = "!local." + /** + * Tell whether the provider room id is a local id. + */ fun isLocalEchoId(roomId: String) = roomId.startsWith(PREFIX) - fun createLocalEchoId() = "${PREFIX}${UUID.randomUUID()}" + internal fun createLocalEchoId() = "${PREFIX}${UUID.randomUUID()}" } From fca4df352253a8feddc5d590771038c293ad9b10 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 11 Jul 2022 16:44:32 +0200 Subject: [PATCH 26/26] Revert "Ensure that Realm is up to date before returning the roomId" This reverts commit 8a5a47c6a57863b87f62ca2e123fe619d1d9b612. --- .../room/create/CreateLocalRoomTask.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 94c300c3c3..d57491a4c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -18,7 +18,9 @@ package org.matrix.android.sdk.internal.session.room.create import com.zhuinden.monarchy.Monarchy import io.realm.Realm +import io.realm.RealmConfiguration import io.realm.kotlin.createObject +import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Content @@ -26,6 +28,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility @@ -38,6 +41,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity @@ -47,6 +51,7 @@ import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType 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.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull @@ -59,6 +64,7 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.time.Clock +import java.util.concurrent.TimeUnit import javax.inject.Inject internal interface CreateLocalRoomTask : Task @@ -68,6 +74,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val roomMemberEventHandler: RoomMemberEventHandler, private val roomSummaryUpdater: RoomSummaryUpdater, + @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, private val userService: UserService, private val clock: Clock, @@ -81,8 +88,16 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( createLocalRoomSummaryEntity(realm, roomId, createRoomBody) } - // Ensure that Realm is up to date before returning the roomId. - monarchy.doWithRealm { it.refresh() } + // Wait for room to be created in DB + try { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) + } + } catch (exception: TimeoutCancellationException) { + throw CreateRoomFailure.CreatedWithTimeout(roomId) + } return roomId }