From 10e4d0190fee94218d274004696814ff7484a9cf Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 11 Jul 2019 18:55:13 +0200 Subject: [PATCH 01/37] Try to insert users directly to see if perfs are better [WIP] --- .../session/room/timeline/TimelineEvent.kt | 2 +- .../database/helper/ChunkEntityHelper.kt | 1 - .../database/helper/RoomEntityHelper.kt | 45 ++++++++--- .../parsing/GetRoomMembersResponseHandler.kt | 59 +++++++++++++++ .../internal/network/parsing/JsonReader.kt | 40 ++++++++++ .../android/internal/session/SessionModule.kt | 5 -- .../session/room/RoomAvatarResolver.kt | 34 ++++----- .../session/room/RoomSummaryUpdater.kt | 13 +++- .../room/membership/LoadRoomMembersTask.kt | 22 ++++-- .../membership/RoomDisplayNameResolver.kt | 69 ++++++++++------- .../RoomMemberDisplayNameResolver.kt | 54 -------------- .../session/room/membership/RoomMembers.kt | 23 +++--- .../room/timeline/TokenChunkEventPersistor.kt | 20 ++++- .../internal/session/sync/RoomSyncHandler.kt | 74 +++++++++++-------- .../internal/session/user/UpdateUserTask.kt | 18 +---- .../session/user/UserEntityFactory.kt | 39 ++++++++++ .../session/user/UserEntityUpdater.kt | 27 ++++--- 17 files changed, 343 insertions(+), 202 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/GetRoomMembersResponseHandler.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/JsonReader.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberDisplayNameResolver.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index f626e3a7..62943de6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -33,7 +33,7 @@ data class TimelineEvent( val localId: Long, val displayIndex: Int, val senderName: String?, - val isUniqueDisplayName: Boolean, + val isUniqueDisplayName: Boolean = false, val senderAvatar: String?, val sendState: SendState, val annotations: EventAnnotationsSummary? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index 70615293..b0b88631 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -103,7 +103,6 @@ internal fun ChunkEntity.updateSenderDataFor(eventIds: List) { } } -@VisibleForTesting internal fun ChunkEntity.add(roomId: String, event: Event, direction: PaginationDirection, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt index 01d95eb2..05a8fd2d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.database.helper +import com.squareup.moshi.JsonReader import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.mapper.toEntity @@ -24,7 +25,10 @@ import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.fastContains import im.vector.matrix.android.internal.extensions.assertIsManaged +import im.vector.matrix.android.internal.network.parsing.GetRoomMembersResponseHandler import im.vector.matrix.android.internal.session.room.membership.RoomMembers +import okhttp3.ResponseBody +import okio.Okio internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) { chunks.remove(chunkEntity) @@ -37,25 +41,42 @@ internal fun RoomEntity.addOrUpdate(chunkEntity: ChunkEntity) { } } -internal fun RoomEntity.addStateEvents(stateEvents: List, - stateIndex: Int = Int.MIN_VALUE, - filterDuplicates: Boolean = false, - isUnlinked: Boolean = false) { +internal fun RoomEntity.addStateEvent(stateEvent: Event, + stateIndex: Int = Int.MIN_VALUE, + filterDuplicates: Boolean = false, + isUnlinked: Boolean = false) { assertIsManaged() - - stateEvents.forEach { event -> - if (event.eventId == null || (filterDuplicates && fastContains(event.eventId))) { - return@forEach - } - val eventEntity = event.toEntity(roomId).apply { + if (stateEvent.eventId == null || (filterDuplicates && fastContains(stateEvent.eventId))) { + return + } else { + val entity = stateEvent.toEntity(roomId).apply { this.stateIndex = stateIndex this.isUnlinked = isUnlinked this.sendState = SendState.SYNCED } - untimelinedStateEvents.add(0, eventEntity) + untimelinedStateEvents.add(entity) } } +internal fun RoomEntity.addStateEvents(stateEvents: List, + stateIndex: Int = Int.MIN_VALUE, + filterDuplicates: Boolean = false, + isUnlinked: Boolean = false) { + stateEvents.forEach { event -> + addStateEvent(event, stateIndex, filterDuplicates, isUnlinked) + } +} + +internal fun RoomEntity.addStateEvents(response: ResponseBody, + stateIndex: Int = Int.MIN_VALUE, + isUnlinked: Boolean = false) { + val manualParser = GetRoomMembersResponseHandler() + val bufferedSource = Okio.buffer(Okio.source(response.byteStream())) + val inputReader = JsonReader.of(bufferedSource) + manualParser.handle(inputReader, this, stateIndex, isUnlinked) +} + + internal fun RoomEntity.addSendingEvent(event: Event) { assertIsManaged() val senderId = event.senderId ?: return @@ -64,7 +85,7 @@ internal fun RoomEntity.addSendingEvent(event: Event) { } val roomMembers = RoomMembers(realm, roomId) val myUser = roomMembers.get(senderId) - val localId = TimelineEventEntity.nextId(realm) + val localId = TimelineEventEntity.nextId(realm) val timelineEventEntity = TimelineEventEntity(localId).also { it.root = eventEntity it.eventId = event.eventId ?: "" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/GetRoomMembersResponseHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/GetRoomMembersResponseHandler.kt new file mode 100644 index 00000000..05433513 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/GetRoomMembersResponseHandler.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.network.parsing + +import com.squareup.moshi.JsonReader +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.RoomEntity + +internal class GetRoomMembersResponseHandler { + + companion object { + private val NAMES = JsonReader.Options.of("event_id", "content", "prev_content", "origin_server_ts", "sender", "state_key") + } + + internal fun handle(reader: JsonReader, roomEntity: RoomEntity, stateIndex: Int = Int.MIN_VALUE, isUnlinked: Boolean = false) { + reader.readObject { + reader.nextName() + val eventEntity = EventEntity().apply { + this.roomId = roomEntity.roomId + this.stateIndex = stateIndex + this.isUnlinked = isUnlinked + this.sendState = SendState.SYNCED + this.type = EventType.STATE_ROOM_MEMBER + } + reader.readArray { + reader.readObject { + when + (reader.selectName(NAMES)) { + 0 -> eventEntity.eventId = reader.nextString() + 1 -> eventEntity.content = reader.readJsonValue()?.toString() + 2 -> eventEntity.prevContent = reader.readJsonValue()?.toString() + 3 -> eventEntity.originServerTs = reader.nextLong() + 4 -> eventEntity.sender = reader.nextString() + 5 -> eventEntity.stateKey = reader.nextString() + else -> reader.skipNameAndValue() + } + } + roomEntity.untimelinedStateEvents.add(eventEntity) + } + } + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/JsonReader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/JsonReader.kt new file mode 100644 index 00000000..7d7afa7f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/JsonReader.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.network.parsing + +import com.squareup.moshi.JsonReader + +fun JsonReader.skipNameAndValue() { + skipName() + skipValue() +} + +inline fun JsonReader.readObject(body: () -> Unit) { + beginObject() + while (hasNext()) { + body() + } + endObject() +} + +inline fun JsonReader.readArray(body: () -> Unit) { + beginArray() + while (hasNext()) { + body() + } + endArray() +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index d1673bfe..f2e61e8c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -38,7 +38,6 @@ import im.vector.matrix.android.internal.network.RetrofitFactory import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater import im.vector.matrix.android.internal.session.room.prune.EventsPruner -import im.vector.matrix.android.internal.session.user.UserEntityUpdater import im.vector.matrix.android.internal.util.md5 import io.realm.RealmConfiguration import okhttp3.OkHttpClient @@ -129,10 +128,6 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventRelationsAggregationUpdater(groupSummaryUpdater: EventRelationsAggregationUpdater): LiveEntityObserver - @Binds - @IntoSet - abstract fun bindUserEntityUpdater(groupSummaryUpdater: UserEntityUpdater): LiveEntityObserver - @Binds abstract fun bindInitialSyncProgressService(initialSyncProgressService: DefaultInitialSyncProgressService): InitialSyncProgressService diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt index 3ed2fa43..9161fb25 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAvatarResolver.kt @@ -20,14 +20,13 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomAvatarContent +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.room.membership.RoomMembers import javax.inject.Inject @@ -42,32 +41,25 @@ internal class RoomAvatarResolver @Inject constructor(private val monarchy: Mona fun resolve(roomId: String): String? { var res: String? = null monarchy.doWithRealm { realm -> - val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomName = EventEntity.where(realm, roomId, EventType.STATE_ROOM_AVATAR).prev()?.asDomain() res = roomName?.content.toModel()?.avatarUrl if (!res.isNullOrEmpty()) { return@doWithRealm } val roomMembers = RoomMembers(realm, roomId) - val members = roomMembers.getLoaded() - if (roomEntity?.membership == Membership.INVITE) { - if (members.size == 1) { - res = members.entries.first().value.avatarUrl - } else if (members.size > 1) { - val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull() - res = firstOtherMember?.avatarUrl - } - } else { - // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) - if (members.size == 1) { - res = members.entries.first().value.avatarUrl - } else if (members.size == 2) { - val firstOtherMember = members.filterKeys { it != credentials.userId }.values.firstOrNull() - res = firstOtherMember?.avatarUrl - } + val members = roomMembers.queryRoomMembersEvent().findAll() + // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) + if (members.size == 1) { + res = members.firstOrNull()?.toRoomMember()?.avatarUrl + } else if (members.size == 2) { + val firstOtherMember = members.where().notEqualTo(EventEntityFields.STATE_KEY, credentials.userId).findFirst() + res = firstOtherMember?.toRoomMember()?.avatarUrl } - } return res } + + private fun EventEntity?.toRoomMember(): RoomMember? { + return this?.asDomain()?.content?.toModel() + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt index 8fdb5fe9..6bcac9b8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomSummaryUpdater.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.latestEvent @@ -86,12 +87,20 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C val latestEvent = TimelineEventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES) val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain() - val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId } + + val otherRoomMembers = RoomMembers(realm, roomId) + .queryRoomMembersEvent() + .notEqualTo(EventEntityFields.STATE_KEY, credentials.userId) + .findAll() + .asSequence() + .map { it.stateKey } + roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString() roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId) roomSummaryEntity.topic = lastTopicEvent?.content.toModel()?.topic roomSummaryEntity.latestEvent = latestEvent roomSummaryEntity.otherMemberIds.clear() - roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys) + roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index d98436a7..a059404e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -18,19 +18,25 @@ package im.vector.matrix.android.internal.session.room.membership import arrow.core.Try import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.internal.database.helper.addStateEvent import im.vector.matrix.android.internal.database.helper.addStateEvents import im.vector.matrix.android.internal.database.helper.updateSenderData import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.sync.SyncTokenStore +import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm import io.realm.kotlin.createObject +import okhttp3.ResponseBody import javax.inject.Inject internal interface LoadRoomMembersTask : Task { @@ -60,23 +66,27 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP } } - private fun insertInDb(response: RoomMembersResponse, roomId: String): Try { + private fun insertInDb(response: RoomMembersResponse, roomId: String): Try { return monarchy .tryTransactionSync { realm -> // We ignore all the already known members val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) - val roomMembers = RoomMembers(realm, roomId).getLoaded() - val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } - roomEntity.addStateEvents(eventsToInsert) + val userEntities = ArrayList(response.roomMemberEvents.size) + for (roomMemberEvent in response.roomMemberEvents) { + roomEntity.addStateEvent(roomMemberEvent) + UserEntityFactory.create(roomMemberEvent)?.also { + userEntities.add(it) + } + } + realm.insertOrUpdate(userEntities) roomEntity.chunks.flatMap { it.timelineEvents }.forEach { it.updateSenderData() } roomEntity.areAllMembersLoaded = true roomSummaryUpdater.update(realm, roomId) } - .map { response } } private fun areAllMembersAlreadyLoaded(roomId: String): Boolean { @@ -85,4 +95,4 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP } } -} \ No newline at end of file +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt index 01ae4394..c27b0a74 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -25,13 +25,16 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomAliasesContent import im.vector.matrix.android.api.session.room.model.RoomCanonicalAliasContent +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomNameContent import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.where +import io.realm.RealmResults import javax.inject.Inject /** @@ -39,7 +42,6 @@ import javax.inject.Inject */ internal class RoomDisplayNameResolver @Inject constructor(private val context: Context, private val monarchy: Monarchy, - private val roomMemberDisplayNameResolver: RoomMemberDisplayNameResolver, private val credentials: Credentials ) { @@ -78,48 +80,59 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: } val roomMembers = RoomMembers(realm, roomId) - val loadedMembers = roomMembers.getLoaded() - val otherRoomMembers = loadedMembers.filterKeys { it != credentials.userId } + val loadedMembers = roomMembers.queryRoomMembersEvent().findAll() + val otherMembersSubset = loadedMembers.where() + .notEqualTo(EventEntityFields.STATE_KEY, credentials.userId) + .limit(3) + .findAll() + if (roomEntity?.membership == Membership.INVITE) { val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst() val inviterId = inviteMeEvent?.sender - name = if (inviterId != null && otherRoomMembers.containsKey(inviterId)) { - roomMemberDisplayNameResolver.resolve(inviterId, otherRoomMembers) + name = if (inviterId != null) { + val inviterMemberEvent = loadedMembers.where().equalTo(EventEntityFields.STATE_KEY, inviterId).findFirst() + inviterMemberEvent?.toRoomMember()?.displayName } else { context.getString(R.string.room_displayname_room_invite) } } else { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() - val memberIds = if (roomSummary?.heroes?.isNotEmpty() == true) { + val memberIds: List = if (roomSummary?.heroes?.isNotEmpty() == true) { roomSummary.heroes } else { - otherRoomMembers.keys.toList() + otherMembersSubset.mapNotNull { it.stateKey } } - - val nbOfOtherMembers = memberIds.size - - when (nbOfOtherMembers) { - 0 -> name = context.getString(R.string.room_displayname_empty_room) - 1 -> name = roomMemberDisplayNameResolver.resolve(memberIds[0], otherRoomMembers) - 2 -> { - val member1 = memberIds[0] - val member2 = memberIds[1] - name = context.getString(R.string.room_displayname_two_members, - roomMemberDisplayNameResolver.resolve(member1, otherRoomMembers), - roomMemberDisplayNameResolver.resolve(member2, otherRoomMembers) - ) - } - else -> { - val member = memberIds[0] - name = context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members, - roomMembers.getNumberOfJoinedMembers() - 1, - roomMemberDisplayNameResolver.resolve(member, otherRoomMembers), - roomMembers.getNumberOfJoinedMembers() - 1) - } + name = when (memberIds.size) { + 0 -> context.getString(R.string.room_displayname_empty_room) + 1 -> resolveRoomMember(otherMembersSubset[0], roomMembers) + 2 -> context.getString(R.string.room_displayname_two_members, + resolveRoomMember(otherMembersSubset[0], roomMembers), + resolveRoomMember(otherMembersSubset[1], roomMembers) + ) + else -> context.resources.getQuantityString(R.plurals.room_displayname_three_and_more_members, + roomMembers.getNumberOfJoinedMembers() - 1, + resolveRoomMember(otherMembersSubset[0], roomMembers), + roomMembers.getNumberOfJoinedMembers() - 1) } } return@doWithRealm } return name ?: roomId } + + private fun resolveRoomMember(eventEntity: EventEntity?, + roomMembers: RoomMembers): String? { + if (eventEntity == null) return null + val roomMember = eventEntity.toRoomMember() ?: return null + val isUnique = roomMembers.isUniqueDisplayName(roomMember.displayName) + return if (isUnique) { + roomMember.displayName + } else { + "${roomMember.displayName} ( ${eventEntity.stateKey} )" + } + } + + private fun EventEntity?.toRoomMember(): RoomMember? { + return this?.asDomain()?.content?.toModel() + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberDisplayNameResolver.kt deleted file mode 100644 index 5d64a632..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMemberDisplayNameResolver.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.session.room.membership - -import im.vector.matrix.android.api.session.room.model.RoomMember -import javax.inject.Inject - -internal class RoomMemberDisplayNameResolver @Inject constructor() { - - fun resolve(userId: String, members: Map): String? { - val currentMember = members[userId] - var displayName = currentMember?.displayName - // Get the user display name from the member list of the room - // Do not consider null display name - - if (currentMember != null && !currentMember.displayName.isNullOrEmpty()) { - val hasNameCollision = members - .filterValues { it != currentMember && it.displayName == currentMember.displayName } - .isNotEmpty() - if (hasNameCollision) { - displayName = "${currentMember.displayName} ( $userId )" - } - } - - // TODO handle invited users - /*else if (null != member && TextUtils.equals(member!!.membership, RoomMember.MEMBERSHIP_INVITE)) { - val user = (mDataHandler as MXDataHandler).getUser(userId) - if (null != user) { - displayName = user!!.displayname - } - } - */ - if (displayName == null) { - // By default, use the user ID - displayName = userId - } - return displayName - } - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt index 72b2695e..fb8326f2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomMembers.kt @@ -33,6 +33,7 @@ import io.realm.Sort * This class is an helper around STATE_ROOM_MEMBER events. * It allows to get the live membership of a user. */ + internal class RoomMembers(private val realm: Realm, private val roomId: String ) { @@ -72,27 +73,27 @@ internal class RoomMembers(private val realm: Realm, .isNotNull(EventEntityFields.CONTENT) } + fun queryJoinedRoomMembersEvent(): RealmQuery { + return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"join\"") + } + + fun queryInvitedRoomMembersEvent(): RealmQuery { + return queryRoomMembersEvent().contains(EventEntityFields.CONTENT, "\"membership\":\"invite\"") + } + fun queryRoomMemberEvent(userId: String): RealmQuery { return queryRoomMembersEvent() .equalTo(EventEntityFields.STATE_KEY, userId) } - fun getLoaded(): Map { - return queryRoomMembersEvent() - .findAll() - .map { it.asDomain() } - .associateBy { it.stateKey!! } - .mapValues { it.value.content.toModel()!! } - } - fun getNumberOfJoinedMembers(): Int { return roomSummary?.joinedMembersCount - ?: getLoaded().filterValues { it.membership == Membership.JOIN }.size + ?: queryJoinedRoomMembersEvent().findAll().size } fun getNumberOfInvitedMembers(): Int { return roomSummary?.invitedMembersCount - ?: getLoaded().filterValues { it.membership == Membership.INVITE }.size + ?: queryInvitedRoomMembersEvent().findAll().size } fun getNumberOfMembers(): Int { @@ -133,4 +134,4 @@ internal class RoomMembers(private val realm: Realm, .toList() } -} \ No newline at end of file +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index ae60969b..dda776d2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -20,16 +20,19 @@ import arrow.core.Try import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.addOrUpdate +import im.vector.matrix.android.internal.database.helper.addStateEvent import im.vector.matrix.android.internal.database.helper.addStateEvents import im.vector.matrix.android.internal.database.helper.deleteOnCascade import im.vector.matrix.android.internal.database.helper.isUnlinked import im.vector.matrix.android.internal.database.helper.merge import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.kotlin.createObject import timber.log.Timber @@ -153,8 +156,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy currentChunk.isLastBackward = true } else { Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") - currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) - + val userEntities = ArrayList(receivedChunk.events.size + receivedChunk.stateEvents.size) + for (event in receivedChunk.events) { + currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) + UserEntityFactory.create(event)?.also { + userEntities.add(it) + } + } // Then we merge chunks if needed if (currentChunk != prevChunk && prevChunk != null) { currentChunk = handleMerge(roomEntity, direction, currentChunk, prevChunk) @@ -170,7 +178,13 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy } } roomEntity.addOrUpdate(currentChunk) - roomEntity.addStateEvents(receivedChunk.stateEvents, isUnlinked = currentChunk.isUnlinked()) + for (stateEvent in receivedChunk.stateEvents) { + roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked()) + UserEntityFactory.create(stateEvent)?.also { + userEntities.add(it) + } + } + realm.insertOrUpdate(userEntities) } } .map { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index bdf81064..4a14dce3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -22,14 +22,18 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent import im.vector.matrix.android.internal.crypto.CryptoManager +import im.vector.matrix.android.internal.database.helper.add import im.vector.matrix.android.internal.database.helper.addAll import im.vector.matrix.android.internal.database.helper.addOrUpdate +import im.vector.matrix.android.internal.database.helper.addStateEvent import im.vector.matrix.android.internal.database.helper.addStateEvents import im.vector.matrix.android.internal.database.helper.lastStateIndex import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity +import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.where @@ -40,6 +44,7 @@ import im.vector.matrix.android.internal.session.notification.ProcessEventForPus import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.sync.model.* +import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import io.realm.Realm @@ -118,7 +123,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch Timber.v("Handle join sync for room $roomId") val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) if (roomEntity.membership == Membership.INVITE) { roomEntity.chunks.deleteAllFromRealm() @@ -133,43 +138,30 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch // State event if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { + val userEntities = ArrayList(roomSync.state.events.size) val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset - roomEntity.addStateEvents(roomSync.state.events, filterDuplicates = true, stateIndex = untimelinedStateIndex) - - // Give info to crypto module - roomSync.state.events.forEach { - cryptoManager.onStateEvent(roomId, it) + roomSync.state.events.forEach { event -> + roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex) + // Give info to crypto module + cryptoManager.onStateEvent(roomId, event) + UserEntityFactory.create(event)?.also { + userEntities.add(it) + } } + realm.insertOrUpdate(userEntities) } if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { val timelineStateOffset = if (isInitialSync || roomSync.timeline.limited.not()) 0 else stateIndexOffset val chunkEntity = handleTimelineEvents( realm, - roomId, + roomEntity, roomSync.timeline.events, roomSync.timeline.prevToken, roomSync.timeline.limited, timelineStateOffset ) roomEntity.addOrUpdate(chunkEntity) - - // Give info to crypto module - roomSync.timeline.events.forEach { - cryptoManager.onLiveEvent(roomId, it) - } - - // Try to remove local echo - val transactionIds = roomSync.timeline.events.mapNotNull { it.unsignedData?.transactionId } - transactionIds.forEach { - val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it) - if (sendingEventEntity != null) { - Timber.v("Remove local echo for tx:$it") - roomEntity.sendingTimelineEvents.remove(sendingEventEntity) - } else { - Timber.v("Can't find corresponding local echo for tx:$it") - } - } } roomSummaryUpdater.update(realm, roomId, Membership.JOIN, roomSync.summary, roomSync.unreadNotifications) @@ -189,10 +181,10 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch InvitedRoomSync): RoomEntity { Timber.v("Handle invited sync for room $roomId") val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) roomEntity.membership = Membership.INVITE if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { - val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.inviteState.events) + val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events) roomEntity.addOrUpdate(chunkEntity) } roomSummaryUpdater.update(realm, roomId, Membership.INVITE) @@ -203,7 +195,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch roomId: String, roomSync: RoomSync): RoomEntity { val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) roomEntity.membership = Membership.LEAVE roomEntity.chunks.deleteAllFromRealm() @@ -212,13 +204,13 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch } private fun handleTimelineEvents(realm: Realm, - roomId: String, + roomEntity: RoomEntity, eventList: List, prevToken: String? = null, isLimited: Boolean = true, stateIndexOffset: Int = 0): ChunkEntity { - val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) + val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomEntity.roomId) val chunkEntity = if (!isLimited && lastChunk != null) { lastChunk } else { @@ -226,13 +218,31 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch } lastChunk?.isLastForward = false chunkEntity.isLastForward = true - chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS, stateIndexOffset) - - //update eventAnnotationSummary here? + val userEntities = ArrayList(eventList.size) + for (event in eventList) { + chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset) + // Give info to crypto module + cryptoManager.onLiveEvent(roomEntity.roomId, event) + // Try to remove local echo + event.unsignedData?.transactionId?.also { + val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it) + if (sendingEventEntity != null) { + Timber.v("Remove local echo for tx:$it") + roomEntity.sendingTimelineEvents.remove(sendingEventEntity) + } else { + Timber.v("Can't find corresponding local echo for tx:$it") + } + } + UserEntityFactory.create(event)?.also { + userEntities.add(it) + } + } + realm.insertOrUpdate(userEntities) return chunkEntity } + private fun handleEphemeral(realm: Realm, roomId: String, ephemeral: RoomSyncEphemeral) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UpdateUserTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UpdateUserTask.kt index eb331e41..616fcfff 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UpdateUserTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UpdateUserTask.kt @@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.EventEntity +import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.session.SessionScope @@ -38,21 +39,6 @@ internal interface UpdateUserTask : Task { internal class DefaultUpdateUserTask @Inject constructor(private val monarchy: Monarchy) : UpdateUserTask { override suspend fun execute(params: UpdateUserTask.Params): Try { - return monarchy.tryTransactionSync { realm -> - params.eventIds.forEach { eventId -> - val event = EventEntity.where(realm, eventId).findFirst()?.asDomain() - ?: return@forEach - val roomId = event.roomId ?: return@forEach - val userId = event.stateKey ?: return@forEach - val roomMember = RoomMembers(realm, roomId).get(userId) ?: return@forEach - if (roomMember.membership != Membership.JOIN) return@forEach - - val userEntity = UserEntity.where(realm, userId).findFirst() - ?: realm.createObject(UserEntity::class.java, userId) - userEntity.displayName = roomMember.displayName ?: "" - userEntity.avatarUrl = roomMember.avatarUrl ?: "" - } - } + return Try.just(Unit) } - } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt new file mode 100644 index 00000000..45bbca78 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.session.user + +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.RoomMember +import im.vector.matrix.android.internal.database.model.UserEntity + +internal object UserEntityFactory { + + fun create(event: Event): UserEntity? { + if (event.type != EventType.STATE_ROOM_MEMBER) { + return null + } + val roomMember = event.content.toModel() ?: return null + return UserEntity(event.stateKey ?: "", + roomMember.displayName ?: "", + roomMember.avatarUrl ?: "" + ) + } + + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt index 5c722863..92581af1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt @@ -21,11 +21,14 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.database.RealmLiveEntityObserver import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntityFields +import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.types import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.tryTransactionAsync +import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.OrderedCollectionChangeSet import io.realm.RealmConfiguration import io.realm.RealmResults @@ -33,6 +36,7 @@ import io.realm.Sort import javax.inject.Inject internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, + private val monarchy: Monarchy, private val updateUserTask: UpdateUserTask, private val taskExecutor: TaskExecutor) : RealmLiveEntityObserver(realmConfiguration) { @@ -45,16 +49,19 @@ internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfi } override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - val roomMembersEvents = changeSet.insertions - .asSequence() - .mapNotNull { results[it]?.eventId } - .toList() - - val taskParams = UpdateUserTask.Params(roomMembersEvents) - updateUserTask - .configureWith(taskParams) - .executeOn(TaskThread.IO) - .executeBy(taskExecutor) + monarchy.tryTransactionSync { realm -> + val userEntities = ArrayList(changeSet.insertions.size) + for (insertion in changeSet.insertions) { + val roomMemberEvent = results[insertion] ?: continue + val roomMemberTimelineEvent = roomMemberEvent.timelineEventEntity?.firstOrNull() + ?: continue + val userEntity = UserEntity(roomMemberEvent.stateKey + ?: "", roomMemberTimelineEvent.senderName ?: "", + roomMemberTimelineEvent.senderAvatar ?: "") + userEntities.add(userEntity) + } + realm.insertOrUpdate(userEntities) + } } } \ No newline at end of file From 9182f2ce4ef59c833da531dbb11653b8703cef6a Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 12 Jul 2019 13:59:37 +0200 Subject: [PATCH 02/37] RoomMembers/User : get a better and faster handling (still need to fix one small issue) --- .../database/helper/ChunkEntityHelper.kt | 3 +- .../database/helper/RoomEntityHelper.kt | 24 -------- .../database/model/TimelineEventEntity.kt | 2 - .../parsing/GetRoomMembersResponseHandler.kt | 59 ------------------- .../internal/network/parsing/JsonReader.kt | 40 ------------- .../room/membership/LoadRoomMembersTask.kt | 13 ++-- .../room/timeline/TokenChunkEventPersistor.kt | 24 +++----- .../internal/session/sync/RoomSyncHandler.kt | 25 +++----- 8 files changed, 24 insertions(+), 166 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/GetRoomMembersResponseHandler.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/JsonReader.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt index b0b88631..3bda568d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/ChunkEntityHelper.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.database.helper -import androidx.annotation.VisibleForTesting import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.room.send.SendState @@ -133,7 +132,7 @@ internal fun ChunkEntity.add(roomId: String, } } - val localId = TimelineEventEntity.nextId(realm) + val localId = TimelineEventEntity.nextId(realm) val eventEntity = TimelineEventEntity(localId).also { it.root = event.toEntity(roomId).apply { this.stateIndex = currentStateIndex diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt index 05a8fd2d..948af2af 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/helper/RoomEntityHelper.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.database.helper -import com.squareup.moshi.JsonReader import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.database.mapper.toEntity @@ -25,10 +24,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.query.fastContains import im.vector.matrix.android.internal.extensions.assertIsManaged -import im.vector.matrix.android.internal.network.parsing.GetRoomMembersResponseHandler import im.vector.matrix.android.internal.session.room.membership.RoomMembers -import okhttp3.ResponseBody -import okio.Okio internal fun RoomEntity.deleteOnCascade(chunkEntity: ChunkEntity) { chunks.remove(chunkEntity) @@ -57,26 +53,6 @@ internal fun RoomEntity.addStateEvent(stateEvent: Event, untimelinedStateEvents.add(entity) } } - -internal fun RoomEntity.addStateEvents(stateEvents: List, - stateIndex: Int = Int.MIN_VALUE, - filterDuplicates: Boolean = false, - isUnlinked: Boolean = false) { - stateEvents.forEach { event -> - addStateEvent(event, stateIndex, filterDuplicates, isUnlinked) - } -} - -internal fun RoomEntity.addStateEvents(response: ResponseBody, - stateIndex: Int = Int.MIN_VALUE, - isUnlinked: Boolean = false) { - val manualParser = GetRoomMembersResponseHandler() - val bufferedSource = Okio.buffer(Okio.source(response.byteStream())) - val inputReader = JsonReader.of(bufferedSource) - manualParser.handle(inputReader, this, stateIndex, isUnlinked) -} - - internal fun RoomEntity.addSendingEvent(event: Event) { assertIsManaged() val senderId = event.senderId ?: return diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt index c811ece1..a1e58c90 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/TimelineEventEntity.kt @@ -20,8 +20,6 @@ import io.realm.RealmObject import io.realm.RealmResults import io.realm.annotations.Index import io.realm.annotations.LinkingObjects -import io.realm.annotations.PrimaryKey -import java.util.* internal open class TimelineEventEntity(var localId: Long = 0, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/GetRoomMembersResponseHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/GetRoomMembersResponseHandler.kt deleted file mode 100644 index 05433513..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/GetRoomMembersResponseHandler.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.network.parsing - -import com.squareup.moshi.JsonReader -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.room.send.SendState -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.RoomEntity - -internal class GetRoomMembersResponseHandler { - - companion object { - private val NAMES = JsonReader.Options.of("event_id", "content", "prev_content", "origin_server_ts", "sender", "state_key") - } - - internal fun handle(reader: JsonReader, roomEntity: RoomEntity, stateIndex: Int = Int.MIN_VALUE, isUnlinked: Boolean = false) { - reader.readObject { - reader.nextName() - val eventEntity = EventEntity().apply { - this.roomId = roomEntity.roomId - this.stateIndex = stateIndex - this.isUnlinked = isUnlinked - this.sendState = SendState.SYNCED - this.type = EventType.STATE_ROOM_MEMBER - } - reader.readArray { - reader.readObject { - when - (reader.selectName(NAMES)) { - 0 -> eventEntity.eventId = reader.nextString() - 1 -> eventEntity.content = reader.readJsonValue()?.toString() - 2 -> eventEntity.prevContent = reader.readJsonValue()?.toString() - 3 -> eventEntity.originServerTs = reader.nextLong() - 4 -> eventEntity.sender = reader.nextString() - 5 -> eventEntity.stateKey = reader.nextString() - else -> reader.skipNameAndValue() - } - } - roomEntity.untimelinedStateEvents.add(eventEntity) - } - } - } - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/JsonReader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/JsonReader.kt deleted file mode 100644 index 7d7afa7f..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/JsonReader.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.network.parsing - -import com.squareup.moshi.JsonReader - -fun JsonReader.skipNameAndValue() { - skipName() - skipValue() -} - -inline fun JsonReader.readObject(body: () -> Unit) { - beginObject() - while (hasNext()) { - body() - } - endObject() -} - -inline fun JsonReader.readArray(body: () -> Unit) { - beginArray() - while (hasNext()) { - body() - } - endArray() -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index a059404e..3364a8e6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -17,15 +17,12 @@ package im.vector.matrix.android.internal.session.room.membership import arrow.core.Try +import com.squareup.moshi.JsonReader import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.internal.database.helper.addStateEvent -import im.vector.matrix.android.internal.database.helper.addStateEvents import im.vector.matrix.android.internal.database.helper.updateSenderData import im.vector.matrix.android.internal.database.model.RoomEntity -import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI @@ -37,6 +34,7 @@ import im.vector.matrix.android.internal.util.tryTransactionSync import io.realm.Realm import io.realm.kotlin.createObject import okhttp3.ResponseBody +import okio.Okio import javax.inject.Inject internal interface LoadRoomMembersTask : Task { @@ -71,16 +69,15 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP .tryTransactionSync { realm -> // We ignore all the already known members val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) + - val userEntities = ArrayList(response.roomMemberEvents.size) for (roomMemberEvent in response.roomMemberEvents) { roomEntity.addStateEvent(roomMemberEvent) UserEntityFactory.create(roomMemberEvent)?.also { - userEntities.add(it) + realm.insertOrUpdate(it) } } - realm.insertOrUpdate(userEntities) roomEntity.chunks.flatMap { it.timelineEvents }.forEach { it.updateSenderData() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index dda776d2..e9a34699 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -18,16 +18,9 @@ package im.vector.matrix.android.internal.session.room.timeline import arrow.core.Try import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.internal.database.helper.addAll -import im.vector.matrix.android.internal.database.helper.addOrUpdate -import im.vector.matrix.android.internal.database.helper.addStateEvent -import im.vector.matrix.android.internal.database.helper.addStateEvents -import im.vector.matrix.android.internal.database.helper.deleteOnCascade -import im.vector.matrix.android.internal.database.helper.isUnlinked -import im.vector.matrix.android.internal.database.helper.merge +import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity -import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.create import im.vector.matrix.android.internal.database.query.find import im.vector.matrix.android.internal.database.query.findAllIncludingEvents @@ -120,7 +113,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction") val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) val nextToken: String? val prevToken: String? @@ -149,18 +142,19 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy } else { nextChunk?.apply { this.prevToken = prevToken } } - ?: ChunkEntity.create(realm, prevToken, nextToken) + ?: ChunkEntity.create(realm, prevToken, nextToken) if (receivedChunk.events.isEmpty() && receivedChunk.end == receivedChunk.start) { Timber.v("Reach end of $roomId") currentChunk.isLastBackward = true } else { Timber.v("Add ${receivedChunk.events.size} events in chunk(${currentChunk.nextToken} | ${currentChunk.prevToken}") - val userEntities = ArrayList(receivedChunk.events.size + receivedChunk.stateEvents.size) + val eventIds = ArrayList(receivedChunk.events.size) for (event in receivedChunk.events) { - currentChunk.addAll(roomId, receivedChunk.events, direction, isUnlinked = currentChunk.isUnlinked()) + event.eventId?.also { eventIds.add(it) } + currentChunk.add(roomId, event, direction, isUnlinked = currentChunk.isUnlinked()) UserEntityFactory.create(event)?.also { - userEntities.add(it) + realm.insertOrUpdate(it) } } // Then we merge chunks if needed @@ -181,10 +175,10 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy for (stateEvent in receivedChunk.stateEvents) { roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked()) UserEntityFactory.create(stateEvent)?.also { - userEntities.add(it) + realm.insertOrUpdate(it) } } - realm.insertOrUpdate(userEntities) + currentChunk.updateSenderDataFor(eventIds) } } .map { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index 4a14dce3..a50e7ca8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -22,15 +22,9 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent import im.vector.matrix.android.internal.crypto.CryptoManager -import im.vector.matrix.android.internal.database.helper.add -import im.vector.matrix.android.internal.database.helper.addAll -import im.vector.matrix.android.internal.database.helper.addOrUpdate -import im.vector.matrix.android.internal.database.helper.addStateEvent -import im.vector.matrix.android.internal.database.helper.addStateEvents -import im.vector.matrix.android.internal.database.helper.lastStateIndex +import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.UserEntity @@ -123,7 +117,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch Timber.v("Handle join sync for room $roomId") val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) if (roomEntity.membership == Membership.INVITE) { roomEntity.chunks.deleteAllFromRealm() @@ -138,17 +132,15 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch // State event if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { - val userEntities = ArrayList(roomSync.state.events.size) val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset roomSync.state.events.forEach { event -> roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex) // Give info to crypto module cryptoManager.onStateEvent(roomId, event) UserEntityFactory.create(event)?.also { - userEntities.add(it) + realm.insertOrUpdate(it) } } - realm.insertOrUpdate(userEntities) } if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { @@ -181,7 +173,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch InvitedRoomSync): RoomEntity { Timber.v("Handle invited sync for room $roomId") val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) roomEntity.membership = Membership.INVITE if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events) @@ -195,7 +187,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch roomId: String, roomSync: RoomSync): RoomEntity { val roomEntity = RoomEntity.where(realm, roomId).findFirst() - ?: realm.createObject(roomId) + ?: realm.createObject(roomId) roomEntity.membership = Membership.LEAVE roomEntity.chunks.deleteAllFromRealm() @@ -219,8 +211,9 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch lastChunk?.isLastForward = false chunkEntity.isLastForward = true - val userEntities = ArrayList(eventList.size) + val eventIds = ArrayList(eventList.size) for (event in eventList) { + event.eventId?.also { eventIds.add(it) } chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset) // Give info to crypto module cryptoManager.onLiveEvent(roomEntity.roomId, event) @@ -235,10 +228,10 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch } } UserEntityFactory.create(event)?.also { - userEntities.add(it) + realm.insertOrUpdate(it) } } - realm.insertOrUpdate(userEntities) + chunkEntity.updateSenderDataFor(eventIds) return chunkEntity } From e6dd1fbfecfe84e70c5f18560f2645d63431b85e Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 13 Jul 2019 15:18:16 +0100 Subject: [PATCH 03/37] Use GlobalScope instead of temp scope Signed-off-by: Dominic Fischer --- .../android/internal/crypto/CryptoManager.kt | 19 +++++++++---------- .../algorithms/megolm/MXMegolmDecryption.kt | 5 ++--- .../DefaultSasVerificationService.kt | 4 ++-- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index d52a457c..9f92ff36 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -21,7 +21,6 @@ package im.vector.matrix.android.internal.crypto import android.content.Context import android.os.Handler import android.os.Looper -import android.text.TextUtils import arrow.core.Try import com.squareup.moshi.Types import com.zhuinden.monarchy.Monarchy @@ -83,7 +82,7 @@ import timber.log.Timber import java.util.* import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject -import kotlin.coroutines.EmptyCoroutineContext +import kotlin.math.max /** * A `CryptoService` class instance manages the end-to-end crypto for a session. @@ -248,7 +247,7 @@ internal class CryptoManager @Inject constructor( return } isStarting.set(true) - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { internalStart(isInitialSync) } } @@ -315,7 +314,7 @@ internal class CryptoManager @Inject constructor( * @param syncResponse the syncResponse */ fun onSyncCompleted(syncResponse: SyncResponse) { - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { if (syncResponse.deviceLists != null) { deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left) } @@ -535,7 +534,7 @@ internal class CryptoManager @Inject constructor( eventType: String, roomId: String, callback: MatrixCallback) { - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { if (!isStarted()) { Timber.v("## encryptEventContent() : wait after e2e init") internalStart(false) @@ -601,7 +600,7 @@ internal class CryptoManager @Inject constructor( * @param callback the callback to return data or null */ override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback) { - GlobalScope.launch(EmptyCoroutineContext) { + GlobalScope.launch { val result = withContext(coroutineDispatchers.crypto) { internalDecryptEvent(event, timeline) } @@ -649,7 +648,7 @@ internal class CryptoManager @Inject constructor( * @param event the event */ fun onToDeviceEvent(event: Event) { - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { when (event.getClearType()) { EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> { onRoomKeyEvent(event) @@ -689,7 +688,7 @@ internal class CryptoManager @Inject constructor( * @param event the encryption event. */ private fun onRoomEncryptionEvent(roomId: String, event: Event) { - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { val params = LoadRoomMembersTask.Params(roomId) loadRoomMembersTask .execute(params) @@ -879,7 +878,7 @@ internal class CryptoManager @Inject constructor( */ fun checkUnknownDevices(userIds: List, callback: MatrixCallback) { // force the refresh to ensure that the devices list is up-to-date - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { deviceListManager .downloadKeys(userIds, true) .fold( @@ -1047,7 +1046,7 @@ internal class CryptoManager @Inject constructor( } override fun downloadKeys(userIds: List, forceDownload: Boolean, callback: MatrixCallback>) { - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { deviceListManager .downloadKeys(userIds, forceDownload) .foldToCallback(callback) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 180b7aa6..dfea1f86 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -29,7 +29,6 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup -import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent @@ -38,7 +37,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber import java.util.* @@ -312,7 +311,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials, return } val userId = request.userId ?: return - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { deviceListManager .downloadKeys(listOf(userId), false) .flatMap { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 33e33ee0..54a4e14d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -40,7 +40,7 @@ import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber import java.util.* @@ -71,7 +71,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre // Event received from the sync fun onToDeviceEvent(event: Event) { - CoroutineScope(coroutineDispatchers.crypto).launch { + GlobalScope.launch(coroutineDispatchers.crypto) { when (event.getClearType()) { EventType.KEY_VERIFICATION_START -> { onStartRequestReceived(event) From 1822fc4fbbecda71485177a9c604f4955488f7ab Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 13 Jul 2019 15:35:10 +0100 Subject: [PATCH 04/37] Some more kotlinification Signed-off-by: Dominic Fischer --- .../android/internal/crypto/CryptoManager.kt | 54 ++++++++----------- .../algorithms/megolm/MXMegolmDecryption.kt | 9 ++-- 2 files changed, 24 insertions(+), 39 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index 9f92ff36..3fadb09b 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -79,7 +79,6 @@ import im.vector.matrix.android.internal.util.fetchCopied import kotlinx.coroutines.* import org.matrix.olm.OlmManager import timber.log.Timber -import java.util.* import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import kotlin.math.max @@ -339,7 +338,7 @@ internal class CryptoManager @Inject constructor( * @return the device info, or null if not found / unsupported algorithm / crypto released */ override fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo? { - return if (!TextUtils.equals(algorithm, MXCRYPTO_ALGORITHM_MEGOLM) && !TextUtils.equals(algorithm, MXCRYPTO_ALGORITHM_OLM)) { + return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) { // We only deal in olm keys null } else cryptoStore.deviceWithIdentityKey(senderKey) @@ -352,8 +351,8 @@ internal class CryptoManager @Inject constructor( * @param deviceId the device id */ override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? { - return if (!TextUtils.isEmpty(userId) && !TextUtils.isEmpty(deviceId)) { - cryptoStore.getUserDevice(deviceId!!, userId) + return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) { + cryptoStore.getUserDevice(deviceId, userId) } else { null } @@ -438,7 +437,7 @@ internal class CryptoManager @Inject constructor( // (for now at least. Maybe we should alert the user somehow?) val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId) - if (!TextUtils.isEmpty(existingAlgorithm) && !TextUtils.equals(existingAlgorithm, algorithm)) { + if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) { Timber.e("## setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId") return false } @@ -670,7 +669,7 @@ internal class CryptoManager @Inject constructor( */ private fun onRoomKeyEvent(event: Event) { val roomKeyContent = event.getClearContent().toModel() ?: return - if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.algorithm)) { + if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) { Timber.e("## onRoomKeyEvent() : missing fields") return } @@ -737,7 +736,7 @@ internal class CryptoManager @Inject constructor( val membership = roomMember?.membership if (membership == Membership.JOIN) { // make sure we are tracking the deviceList for this user. - deviceListManager.startTrackingDeviceList(Arrays.asList(userId)) + deviceListManager.startTrackingDeviceList(listOf(userId)) } else if (membership == Membership.INVITE && shouldEncryptForInvitedMembers(roomId) && cryptoConfig.enableEncryptionForInvitedMembers) { @@ -746,7 +745,7 @@ internal class CryptoManager @Inject constructor( // know what other servers are in the room at the time they've been invited. // They therefore will not send device updates if a user logs in whilst // their state is invite. - deviceListManager.startTrackingDeviceList(Arrays.asList(userId)) + deviceListManager.startTrackingDeviceList(listOf(userId)) } } } @@ -781,7 +780,11 @@ internal class CryptoManager @Inject constructor( * @param callback the exported keys */ override fun exportRoomKeys(password: String, callback: MatrixCallback) { - exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT, callback) + GlobalScope.launch(coroutineDispatchers.main) { + runCatching { + exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT) + }.fold(callback::onSuccess, callback::onFailure) + } } /** @@ -791,30 +794,16 @@ internal class CryptoManager @Inject constructor( * @param anIterationCount the encryption iteration count (0 means no encryption) * @param callback the exported keys */ - private fun exportRoomKeys(password: String, anIterationCount: Int, callback: MatrixCallback) { - GlobalScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.crypto) { - Try { - val iterationCount = Math.max(0, anIterationCount) + private suspend fun exportRoomKeys(password: String, anIterationCount: Int): ByteArray { + return withContext(coroutineDispatchers.crypto) { + val iterationCount = max(0, anIterationCount) - val exportedSessions = ArrayList() + val exportedSessions = cryptoStore.getInboundGroupSessions().mapNotNull { it.exportKeys() } - val inboundGroupSessions = cryptoStore.getInboundGroupSessions() + val adapter = MoshiProvider.providesMoshi() + .adapter(List::class.java) - for (session in inboundGroupSessions) { - val megolmSessionData = session.exportKeys() - - if (null != megolmSessionData) { - exportedSessions.add(megolmSessionData) - } - } - - val adapter = MoshiProvider.providesMoshi() - .adapter(List::class.java) - - MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount) - } - }.foldToCallback(callback) + MXMegolmExportEncryption.encryptMegolmKeyFile(adapter.toJson(exportedSessions), password, iterationCount) } } @@ -943,7 +932,7 @@ internal class CryptoManager @Inject constructor( val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList() if (add) { - if (!roomIds.contains(roomId)) { + if (roomId !in roomIds) { roomIds.add(roomId) } } else { @@ -1032,8 +1021,7 @@ internal class CryptoManager @Inject constructor( val unknownDevices = MXUsersDevicesMap() val userIds = devicesInRoom.userIds for (userId in userIds) { - val deviceIds = devicesInRoom.getUserDeviceIds(userId) - deviceIds?.forEach { deviceId -> + devicesInRoom.getUserDeviceIds(userId)?.forEach { deviceId -> devicesInRoom.getObject(userId, deviceId) ?.takeIf { it.isUnknown } ?.let { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index dfea1f86..b30176b2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -40,7 +40,6 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import timber.log.Timber -import java.util.* import kotlin.collections.HashMap internal class MXMegolmDecryption(private val credentials: Credentials, @@ -320,8 +319,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials, if (deviceInfo == null) { throw RuntimeException() } else { - val devicesByUser = HashMap>() - devicesByUser[userId] = ArrayList(Arrays.asList(deviceInfo)) + val devicesByUser = mapOf(userId to listOf(deviceInfo)) ensureOlmSessionsForDevicesAction .handle(devicesByUser) .flatMap { @@ -335,8 +333,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials, Timber.v("## shareKeysWithDevice() : sharing keys for session" + " ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId") - val payloadJson = HashMap() - payloadJson["type"] = EventType.FORWARDED_ROOM_KEY + val payloadJson = mutableMapOf("type" to EventType.FORWARDED_ROOM_KEY) olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) .fold( @@ -349,7 +346,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials, } ) - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo)) + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) val sendToDeviceMap = MXUsersDevicesMap() sendToDeviceMap.setObject(userId, deviceId, encodedPayload) Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId") From 6effb90361810ff0f41ace6f1ea059c5f4581c16 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 15 Jul 2019 16:42:17 +0200 Subject: [PATCH 05/37] Fix / edit of reply and edit of edit of reply --- .../room/model/relation/RelationService.kt | 14 +++ .../session/room/timeline/TimelineEvent.kt | 13 +++ .../room/relation/DefaultRelationService.kt | 19 ++++ .../room/send/LocalEchoEventFactory.kt | 98 ++++++++++++++----- .../internal/session/room/send/TextContent.kt | 4 +- .../home/room/detail/RoomDetailFragment.kt | 3 +- .../home/room/detail/RoomDetailViewModel.kt | 27 ++--- 7 files changed, 139 insertions(+), 39 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt index 7ffbd5f1..bfc569e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt @@ -80,6 +80,20 @@ interface RelationService { newBodyAutoMarkdown: Boolean, compatibilityBodyText: String = "* $newBodyText"): Cancelable + + /** + * Edit a reply. This is a special case because replies contains fallback text as a prefix. + * This method will take the new body (stripped from fallbacks) and re-add them before sending. + * @param targetEventId The event to edit + * @param newBodyText The edited body (stripped from in reply to content) + * @param compatibilityBodyText The text that will appear on clients that don't support yet edition + */ + fun editReply(replyToEdit: TimelineEvent, + originalSenderId: String?, + originalEventId : String, + newBodyText: String, + compatibilityBodyText: String = "* $newBodyText"): Cancelable + /** * Get's the edit history of the given event */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index f626e3a7..251fe3d3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.session.room.send.extractUsefulTextFromReply /** * This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline. @@ -88,3 +89,15 @@ data class TimelineEvent( */ fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSummary?.aggregatedContent?.toModel() ?: root.getClearContent().toModel() + + +fun TimelineEvent.getTextEditableContent(): String? { + val originalContent = root.getClearContent().toModel() ?: return null + val isReply = originalContent.relatesTo?.inReplyTo != null + val lastContent = getLastMessageContent() + return if (isReply) { + return extractUsefulTextFromReply(lastContent?.body ?: "") + } else { + lastContent?.body ?: "" + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt index 1b487d96..37a7f094 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary +import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.api.session.room.model.relation.RelationService import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.Cancelable @@ -132,6 +133,24 @@ internal class DefaultRelationService @Inject constructor(private val context: C } + override fun editReply(replyToEdit: TimelineEvent, + originalSenderId: String?, + originalEventId: String, + newBodyText: String, + compatibilityBodyText: String): Cancelable { + val event = eventFactory + .createReplaceTextOfReply(roomId, + replyToEdit, + originalSenderId, originalEventId, + newBodyText, true, MessageType.MSGTYPE_TEXT, compatibilityBodyText) + .also { + saveLocalEcho(it) + } + val workRequest = createSendEventWork(event) + TimelineSendEventWorkCommon.postWork(context, roomId, workRequest) + return CancelableWork(context, workRequest.id) + } + override fun fetchEditHistory(eventId: String, callback: MatrixCallback>) { val params = FetchEditHistoryTask.Params(roomId, eventId) fetchEditHistoryTask.configureWith(params) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 67d1eabc..fe039341 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -104,6 +104,45 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials )) } + fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent, + originalSenderId: String?, + originalEventId: String, + newBodyText: String, + newBodyAutoMarkdown: Boolean, + msgType: String, + compatibilityText: String): Event { + val permalink = PermalinkFactory.createPermalink(roomId, originalEventId) + val userLink = originalSenderId?.let { PermalinkFactory.createPermalink(it) } ?: "" + + val body = bodyForReply(eventReplaced.getLastMessageContent(), eventReplaced.root.getClearContent().toModel()) + val replyFormatted = REPLY_PATTERN.format( + permalink, + stringProvider.getString(R.string.message_reply_to_prefix), + userLink, + originalSenderId, + body.takeFormatted(), + createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted() + ) + // + // > <@alice:example.org> This is the original body + // + val replyFallback = buildReplyFallback(body, originalSenderId, newBodyText) + + return createEvent(roomId, + MessageTextContent( + type = msgType, + body = compatibilityText, + relatesTo = RelationDefaultContent(RelationType.REPLACE, eventReplaced.root.eventId), + newContent = MessageTextContent( + type = msgType, + format = MessageType.FORMAT_MATRIX_HTML, + body = replyFallback, + formattedBody = replyFormatted + ) + .toContent() + )) + } + fun createMediaEvent(roomId: String, attachment: ContentAttachmentData): Event { return when (attachment.type) { ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment) @@ -239,16 +278,8 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials val permalink = PermalinkFactory.createPermalink(eventReplied.root) ?: return null val userId = eventReplied.root.senderId ?: return null val userLink = PermalinkFactory.createPermalink(userId) ?: return null - // - //
- // In reply to - // @alice:example.org - //
- // - //
- //
- // This is where the reply goes. - val body = bodyForReply(eventReplied.getLastMessageContent()) + + val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.root.getClearContent().toModel()) val replyFormatted = REPLY_PATTERN.format( permalink, stringProvider.getString(R.string.message_reply_to_prefix), @@ -260,8 +291,22 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials // // > <@alice:example.org> This is the original body // + val replyFallback = buildReplyFallback(body, userId, replyText) + + val eventId = eventReplied.root.eventId ?: return null + val content = MessageTextContent( + type = MessageType.MSGTYPE_TEXT, + format = MessageType.FORMAT_MATRIX_HTML, + body = replyFallback, + formattedBody = replyFormatted, + relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId)) + ) + return createEvent(roomId, content) + } + + private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String { val lines = body.text.split("\n") - val replyFallback = StringBuffer("><$userId>") + val replyFallback = StringBuffer("><$originalSenderId>") lines.forEachIndexed { index, s -> if (index == 0) { replyFallback.append(" $s") @@ -269,23 +314,16 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials replyFallback.append("\n>$s") } } - replyFallback.append("\n\n").append(replyText) - - val eventId = eventReplied.root.eventId ?: return null - val content = MessageTextContent( - type = MessageType.MSGTYPE_TEXT, - format = MessageType.FORMAT_MATRIX_HTML, - body = replyFallback.toString(), - formattedBody = replyFormatted, - relatesTo = RelationDefaultContent(null, null, ReplyToContent(eventId)) - ) - return createEvent(roomId, content) + replyFallback.append("\n\n").append(newBodyText) + return replyFallback.toString() } /** * Returns a TextContent used for the fallback event representation in a reply message. + * We also pass the original content, because in case of an edit of a reply the last content is not + * himself a reply, but it will contain the fallbacks, so we have to trim them. */ - private fun bodyForReply(content: MessageContent?): TextContent { + private fun bodyForReply(content: MessageContent?, originalContent: MessageContent?): TextContent { when (content?.type) { MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_TEXT, @@ -296,7 +334,8 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials formattedText = content.formattedBody } } - val isReply = content.relatesTo?.inReplyTo?.eventId != null + val isReply = content.relatesTo?.inReplyTo?.eventId != null || + originalContent?.relatesTo?.inReplyTo?.eventId != null return if (isReply) TextContent(content.body, formattedText).removeInReplyFallbacks() else @@ -353,7 +392,16 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials companion object { const val LOCAL_ID_PREFIX = "local." - // No whitespace + + // + //
+ // In reply to + // @alice:example.org + //
+ // + //
+ //
+ // No whitespace because currently breaks temporary formatted text to Span const val REPLY_PATTERN = """
%s%s
%s
%s""" fun isLocalEchoId(eventId: String): Boolean = eventId.startsWith(LOCAL_ID_PREFIX) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/TextContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/TextContent.kt index 3061bd83..435efa6e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/TextContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/TextContent.kt @@ -47,7 +47,7 @@ fun TextContent.removeInReplyFallbacks(): TextContent { ) } -private fun extractUsefulTextFromReply(repliedBody: String): String { +fun extractUsefulTextFromReply(repliedBody: String): String { val lines = repliedBody.lines() var wellFormed = repliedBody.startsWith(">") var endOfPreviousFound = false @@ -66,7 +66,7 @@ private fun extractUsefulTextFromReply(repliedBody: String): String { return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody } -private fun extractUsefulTextFromHtmlReply(repliedBody: String): String { +fun extractUsefulTextFromHtmlReply(repliedBody: String): String { if (repliedBody.startsWith("")) { return repliedBody.substring(repliedBody.lastIndexOf("") + "".length).trim() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 943b6165..2ae5d951 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -59,6 +59,7 @@ import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent +import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent @@ -258,7 +259,7 @@ class RoomDetailFragment : composerLayout.composerRelatedMessageContent.text = formattedBody ?: nonFormattedBody - composerLayout.composerEditText.setText(if (useText) nonFormattedBody else "") + composerLayout.composerEditText.setText(if (useText) event.getTextEditableContent() else "") composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) avatarRenderer.render(event.senderAvatar, event.root.senderId diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 36b989fe..364b31a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -39,7 +39,6 @@ import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.rx.rx -import im.vector.riotx.R import im.vector.riotx.core.intent.getFilenameFromUri import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.UserPreferencesProvider @@ -52,8 +51,6 @@ import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import timber.log.Timber import java.io.File -import java.text.SimpleDateFormat -import java.util.* import java.util.concurrent.TimeUnit @@ -229,16 +226,24 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } } is SendMode.EDIT -> { - val messageContent: MessageContent? = - state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() - ?: state.sendMode.timelineEvent.root.getClearContent().toModel() - val nonFormattedBody = messageContent?.body ?: "" - if (nonFormattedBody != action.text) { - room.editTextMessage(state.sendMode.timelineEvent.root.eventId - ?: "", messageContent?.type ?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown) + //is original event a reply? + val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId + if (inReplyTo != null) { + //TODO check if same content? + room.editReply(state.sendMode.timelineEvent, room.getTimeLineEvent(inReplyTo)?.root?.senderId, inReplyTo, action.text) } else { - Timber.w("Same message content, do not send edition") + val messageContent: MessageContent? = + state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() + ?: state.sendMode.timelineEvent.root.getClearContent().toModel() + val existingBody = messageContent?.body ?: "" + if (existingBody != action.text) { + room.editTextMessage(state.sendMode.timelineEvent.root.eventId + ?: "", messageContent?.type + ?: MessageType.MSGTYPE_TEXT, action.text, action.autoMarkdown) + } else { + Timber.w("Same message content, do not send edition") + } } setState { copy( From d8092abc4e55917ef3cecd7516be5caf3e1c6890 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 15 Jul 2019 17:18:31 +0200 Subject: [PATCH 06/37] fix / strip reply prefix on history --- CHANGES.md | 3 +- .../matrix/android/api/util/ContentUtils.kt | 45 +++++++++++++++++++ .../internal/session/room/send/TextContent.kt | 26 +---------- .../action/ViewEditHistoryEpoxyController.kt | 14 +++--- .../action/ViewEditHistoryViewModel.kt | 10 ++++- 5 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/ContentUtils.kt diff --git a/CHANGES.md b/CHANGES.md index ee6d2c8c..2b666e88 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,11 +2,12 @@ Changes in RiotX 0.2.1 (2019-XX-XX) =================================================== Features: - - Message Editing: View edit history + - Message Editing: View edit history (#121) - Rooms filtering (#304) Improvements: - Handle click on redacted events: view source and create permalink + - Improve edit of replies Other changes: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/ContentUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/ContentUtils.kt new file mode 100644 index 00000000..0a395dee --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/ContentUtils.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.matrix.android.api.util + + +object ContentUtils { + fun extractUsefulTextFromReply(repliedBody: String): String { + val lines = repliedBody.lines() + var wellFormed = repliedBody.startsWith(">") + var endOfPreviousFound = false + val usefullines = ArrayList() + lines.forEach { + if (it == "") { + endOfPreviousFound = true + return@forEach + } + if (!endOfPreviousFound) { + wellFormed = wellFormed && it.startsWith(">") + } else { + usefullines.add(it) + } + } + return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody + } + + fun extractUsefulTextFromHtmlReply(repliedBody: String): String { + if (repliedBody.startsWith("")) { + return repliedBody.substring(repliedBody.lastIndexOf("") + "".length).trim() + } + return repliedBody + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/TextContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/TextContent.kt index 435efa6e..bf7cb361 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/TextContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/TextContent.kt @@ -18,6 +18,8 @@ package im.vector.matrix.android.internal.session.room.send import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromHtmlReply +import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply /** * Contains a text and eventually a formatted text @@ -47,28 +49,4 @@ fun TextContent.removeInReplyFallbacks(): TextContent { ) } -fun extractUsefulTextFromReply(repliedBody: String): String { - val lines = repliedBody.lines() - var wellFormed = repliedBody.startsWith(">") - var endOfPreviousFound = false - val usefullines = ArrayList() - lines.forEach { - if (it == "") { - endOfPreviousFound = true - return@forEach - } - if (!endOfPreviousFound) { - wellFormed = wellFormed && it.startsWith(">") - } else { - usefullines.add(it) - } - } - return usefullines.joinToString("\n").takeIf { wellFormed } ?: repliedBody -} -fun extractUsefulTextFromHtmlReply(repliedBody: String): String { - if (repliedBody.startsWith("")) { - return repliedBody.substring(repliedBody.lastIndexOf("") + "".length).trim() - } - return repliedBody -} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt index 4ae62fbd..34e34073 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt @@ -26,6 +26,7 @@ import com.airbnb.mvrx.Success import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply import im.vector.riotx.R import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.ui.list.genericFooterItem @@ -60,13 +61,13 @@ class ViewEditHistoryEpoxyController(private val context: Context, } } is Success -> { - state.editList()?.let { renderEvents(it) } + state.editList()?.let { renderEvents(it, state.isOriginalAReply) } } } } - private fun renderEvents(sourceEvents: List) { + private fun renderEvents(sourceEvents: List, isOriginalReply: Boolean) { if (sourceEvents.isEmpty()) { genericItem { id("footer") @@ -92,7 +93,7 @@ class ViewEditHistoryEpoxyController(private val context: Context, } } lastDate = evDate - val cContent = getCorrectContent(timelineEvent) + val cContent = getCorrectContent(timelineEvent, isOriginalReply) val body = cContent.second?.let { eventHtmlRenderer.render(it) } ?: cContent.first @@ -101,7 +102,7 @@ class ViewEditHistoryEpoxyController(private val context: Context, var spannedDiff: Spannable? = null if (nextEvent != null && cContent.second == null /*No diff for html*/) { //compares the body - val nContent = getCorrectContent(nextEvent) + val nContent = getCorrectContent(nextEvent, isOriginalReply) val nextBody = nContent.second?.let { eventHtmlRenderer.render(it) } ?: nContent.first val dmp = diff_match_patch() @@ -144,11 +145,14 @@ class ViewEditHistoryEpoxyController(private val context: Context, } } - private fun getCorrectContent(event: Event): Pair { + private fun getCorrectContent(event: Event, isOriginalReply: Boolean): Pair { val clearContent = event.getClearContent().toModel() val newContent = clearContent ?.newContent ?.toModel() + if (isOriginalReply) { + return extractUsefulTextFromReply(newContent?.body ?: clearContent?.body ?: "") to null + } return (newContent?.body ?: clearContent?.body ?: "") to (newContent?.formattedBody ?: clearContent?.formattedBody) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt index 64005c3f..bda46d46 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt @@ -21,6 +21,8 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter @@ -28,6 +30,7 @@ import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFor data class ViewEditHistoryViewState( val eventId: String, val roomId: String, + val isOriginalAReply: Boolean = false, val editList: Async> = Uninitialized) : MvRxState { @@ -77,11 +80,16 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted override fun onSuccess(data: List) { //TODO until supported by API Add original event manually val withOriginal = data.toMutableList() + var originalIsReply = false room.getTimeLineEvent(eventId)?.let { withOriginal.add(it.root) + originalIsReply = it.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId != null } setState { - copy(editList = Success(withOriginal)) + copy( + editList = Success(withOriginal), + isOriginalAReply = originalIsReply + ) } } }) From c6fd625761464d8f35fc69b8ffebdb9b485a76af Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Jul 2019 14:38:49 +0200 Subject: [PATCH 07/37] code review --- .../android/api/session/room/model/message/MessageContent.kt | 5 +++++ .../api/session/room/model/relation/RelationService.kt | 4 +++- .../android/api/session/room/timeline/TimelineEvent.kt | 5 +++-- .../java/im/vector/matrix/android/api/util/ContentUtils.kt | 4 +++- .../internal/session/room/send/LocalEchoEventFactory.kt | 4 ++-- .../room/detail/timeline/action/ViewEditHistoryViewModel.kt | 3 ++- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt index c45e47fc..bd32a75a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt @@ -25,4 +25,9 @@ interface MessageContent { val body: String val relatesTo: RelationDefaultContent? val newContent: Content? +} + + +fun MessageContent?.isReply(): Boolean { + return this?.relatesTo?.inReplyTo != null } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt index bfc569e1..da91ee70 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt @@ -84,7 +84,9 @@ interface RelationService { /** * Edit a reply. This is a special case because replies contains fallback text as a prefix. * This method will take the new body (stripped from fallbacks) and re-add them before sending. - * @param targetEventId The event to edit + * @param replyToEdit The event to edit + * @param originalSenderId the sender of the message that this reply (being edited) is relating to + * @param originalEventId the event id that this reply (being edited) is relating to * @param newBodyText The edited body (stripped from in reply to content) * @param compatibilityBodyText The text that will appear on clients that don't support yet edition */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 251fe3d3..761c3961 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -21,8 +21,9 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.api.session.room.send.SendState -import im.vector.matrix.android.internal.session.room.send.extractUsefulTextFromReply +import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply /** * This data class is a wrapper around an Event. It allows to get useful data in the context of a timeline. @@ -93,7 +94,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? = annotations?.editSu fun TimelineEvent.getTextEditableContent(): String? { val originalContent = root.getClearContent().toModel() ?: return null - val isReply = originalContent.relatesTo?.inReplyTo != null + val isReply = originalContent.isReply() val lastContent = getLastMessageContent() return if (isReply) { return extractUsefulTextFromReply(lastContent?.body ?: "") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/ContentUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/ContentUtils.kt index 0a395dee..ad17d26b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/ContentUtils.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/util/ContentUtils.kt @@ -38,7 +38,9 @@ object ContentUtils { fun extractUsefulTextFromHtmlReply(repliedBody: String): String { if (repliedBody.startsWith("")) { - return repliedBody.substring(repliedBody.lastIndexOf("") + "".length).trim() + val closingTagIndex = repliedBody.lastIndexOf("") + if (closingTagIndex != -1) + return repliedBody.substring(closingTagIndex + "".length).trim() } return repliedBody } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index fe039341..51aed8a7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -334,8 +334,8 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials formattedText = content.formattedBody } } - val isReply = content.relatesTo?.inReplyTo?.eventId != null || - originalContent?.relatesTo?.inReplyTo?.eventId != null + val isReply = content.isReply() || + originalContent.isReply() return if (isReply) TextContent(content.body, formattedText).removeInReplyFallbacks() else diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt index bda46d46..576ef5e9 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter @@ -83,7 +84,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted var originalIsReply = false room.getTimeLineEvent(eventId)?.let { withOriginal.add(it.root) - originalIsReply = it.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId != null + originalIsReply = it.root.getClearContent().toModel().isReply() } setState { copy( From 6bbc784c2955feb2e64ae090088ba7d14ab90e51 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2019 15:42:02 +0200 Subject: [PATCH 08/37] Fix crash (from Steve's rageshake) --- .../features/notifications/NotificationDrawerManager.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt index 72a9e1e3..67eef393 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt @@ -182,7 +182,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context val session = activeSessionHolder.getSafeActiveSession() ?: return val user = session.getUser(session.sessionParams.credentials.userId) - val myUserDisplayName = user?.displayName ?: session.sessionParams.credentials.userId + // myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash + val myUserDisplayName = user?.displayName?.takeIf { it.isNotBlank() } ?: session.sessionParams.credentials.userId val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE) synchronized(eventList) { @@ -343,7 +344,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context for (event in simpleEvents) { //We build a simple event if (firstTime || !event.hasBeenDisplayed) { - NotificationUtils.buildSimpleEventNotification(context, event, null, myUserDisplayName)?.let { + NotificationUtils.buildSimpleEventNotification(context, event, null, session.myUserId)?.let { notifications.add(it) NotificationUtils.showNotificationMessage(context, event.eventId, ROOM_EVENT_NOTIFICATION_ID, it) event.hasBeenDisplayed = true //we can consider it as displayed From 63d2861bc8bd1c612c4aeee371abc929ff36b562 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Jul 2019 15:44:08 +0200 Subject: [PATCH 09/37] Fix / SyncThread was started in background Upon reception of a push, is the session is instantiated the sync thread was starting to loop --- .../java/im/vector/matrix/android/api/session/Session.kt | 2 +- .../matrix/android/internal/session/DefaultSession.kt | 4 +++- .../android/internal/session/sync/job/SyncThread.kt | 9 +++++++-- .../main/java/im/vector/riotx/core/extensions/Session.kt | 8 +++++++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 08e706c3..d06764c4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -84,7 +84,7 @@ interface Session : /** * This method start the sync thread. */ - fun startSync() + fun startSync(fromForeground : Boolean) /** * This method stop the sync thread. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 6a1cda53..69e35965 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -105,8 +105,10 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se SyncWorker.stopAnyBackgroundSync(context) } - override fun startSync() { + override fun startSync(fromForeground : Boolean) { + Timber.i("Starting sync thread") assert(isOpen) + syncThread.setInitialForeground(fromForeground) if (!syncThread.isAlive) { syncThread.start() } else { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index b6d236ed..65c79bd8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -54,6 +54,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, updateStateTo(SyncState.IDLE) } + fun setInitialForeground(initialForground : Boolean) { + updateStateTo(if (initialForground) SyncState.IDLE else SyncState.PAUSED) + } + fun restart() = synchronized(lock) { if (state is SyncState.PAUSED) { Timber.v("Resume sync...") @@ -84,7 +88,6 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, Timber.v("Start syncing...") networkConnectivityChecker.register(this) backgroundDetectionObserver.register(this) - updateStateTo(SyncState.RUNNING(catchingUp = true)) while (state != SyncState.KILLING) { if (!networkConnectivityChecker.isConnected() || state == SyncState.PAUSED) { @@ -93,7 +96,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, lock.wait() } } else { - Timber.v("Execute sync request with timeout $DEFAULT_LONG_POOL_TIMEOUT") + updateStateTo(SyncState.RUNNING(catchingUp = true)) + Timber.v("[$this] Execute sync request with timeout $DEFAULT_LONG_POOL_TIMEOUT") val latch = CountDownLatch(1) val params = SyncTask.Params(DEFAULT_LONG_POOL_TIMEOUT) cancelableTask = syncTask.configureWith(params) @@ -148,6 +152,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, } private fun updateStateTo(newState: SyncState) { + Timber.v("Update state to $newState") state = newState liveState.postValue(newState) } diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt index b03686d4..95e17a4b 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt @@ -16,14 +16,20 @@ package im.vector.riotx.core.extensions +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ProcessLifecycleOwner import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.sync.FilterService import im.vector.riotx.features.notifications.PushRuleTriggerListener +import timber.log.Timber fun Session.configureAndStart(pushRuleTriggerListener: PushRuleTriggerListener) { open() setFilter(FilterService.FilterPreset.RiotFilter) - startSync() + Timber.i("Configure and start session for ${this.myUserId}") + val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) + Timber.v("--> is at least started? $isAtLeastStarted") + startSync(isAtLeastStarted) refreshPushers() pushRuleTriggerListener.startWithSession(this) fetchPushRules() From 0f7a56d005b10e4221b79d6e947038db857e843e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2019 15:50:07 +0200 Subject: [PATCH 10/37] Use Session.myUserId whereas it's possible --- .../java/im/vector/matrix/android/api/session/Session.kt | 3 +++ .../im/vector/matrix/android/internal/SessionManager.kt | 2 +- .../matrix/android/internal/session/DefaultSession.kt | 4 ++-- .../gplay/push/fcm/VectorFirebaseMessagingService.kt | 4 ++-- .../vector/riotx/core/preference/UserAvatarPreference.kt | 4 ++-- .../restore/KeysBackupRestoreFromKeyViewModel.kt | 4 ++-- .../restore/KeysBackupRestoreFromPassphraseViewModel.kt | 4 ++-- .../im/vector/riotx/features/home/HomeDrawerFragment.kt | 2 +- .../riotx/features/home/group/GroupListViewModel.kt | 2 +- .../riotx/features/home/room/detail/RoomDetailFragment.kt | 4 ++-- .../features/home/room/detail/RoomDetailViewModel.kt | 7 ++----- .../room/detail/timeline/action/MessageMenuViewModel.kt | 6 +++--- .../features/notifications/NotifiableEventResolver.kt | 8 ++++---- .../notifications/NotificationBroadcastReceiver.kt | 4 ++-- .../features/notifications/NotificationDrawerManager.kt | 4 ++-- .../im/vector/riotx/features/rageshake/BugReporter.kt | 2 +- .../riotx/features/settings/VectorSettingsActivity.kt | 4 ++-- .../features/settings/VectorSettingsGeneralFragment.kt | 4 ++-- .../settings/VectorSettingsSecurityPrivacyFragment.kt | 2 +- .../riotx/features/workers/signout/SignOutUiWorker.kt | 2 +- 20 files changed, 38 insertions(+), 38 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 08e706c3..54ae24c7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -57,6 +57,9 @@ interface Session : */ val sessionParams: SessionParams + /** + * Useful shortcut to get access to the userId + */ val myUserId: String get() = sessionParams.credentials.userId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt index 7cc73ceb..0a87a7ce 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt @@ -58,7 +58,7 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M .factory() .create(matrixComponent, sessionParams) .also { - sessionComponents[sessionParams.credentials.userId] = it + sessionComponents[userId] = it } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 6a1cda53..a84e0193 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -94,11 +94,11 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se } override fun requireBackgroundSync() { - SyncWorker.requireBackgroundSync(context, sessionParams.credentials.userId) + SyncWorker.requireBackgroundSync(context, myUserId) } override fun startAutomaticBackgroundSync(repeatDelay: Long) { - SyncWorker.automaticallyBackgroundSync(context, sessionParams.credentials.userId, 0, repeatDelay) + SyncWorker.automaticallyBackgroundSync(context, myUserId, 0, repeatDelay) } override fun stopAnyBackgroundSync() { diff --git a/vector/src/gplay/java/im/vector/riotx/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/riotx/gplay/push/fcm/VectorFirebaseMessagingService.kt index 6c9f16c6..5a4aa36c 100755 --- a/vector/src/gplay/java/im/vector/riotx/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/riotx/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -199,7 +199,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { if (eventType == null) { //Just add a generic unknown event val simpleNotifiableEvent = SimpleNotifiableEvent( - session.sessionParams.credentials.userId, + session.myUserId, eventId, true, //It's an issue in this case, all event will bing even if expected to be silent. title = getString(R.string.notification_unknown_new_event), @@ -238,7 +238,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { } notifiableEvent.isPushGatewayEvent = true - notifiableEvent.matrixID = session.sessionParams.credentials.userId + notifiableEvent.matrixID = session.myUserId notificationDrawerManager.onNotifiableEventReceived(notifiableEvent) notificationDrawerManager.refreshNotificationDrawer() } diff --git a/vector/src/main/java/im/vector/riotx/core/preference/UserAvatarPreference.kt b/vector/src/main/java/im/vector/riotx/core/preference/UserAvatarPreference.kt index e5e84514..b0747e7e 100755 --- a/vector/src/main/java/im/vector/riotx/core/preference/UserAvatarPreference.kt +++ b/vector/src/main/java/im/vector/riotx/core/preference/UserAvatarPreference.kt @@ -58,10 +58,10 @@ open class UserAvatarPreference : Preference { open fun refreshAvatar() { val session = mSession ?: return val view = mAvatarView ?: return - session.getUser(session.sessionParams.credentials.userId)?.let { + session.getUser(session.myUserId)?.let { avatarRenderer.render(it, view) } ?: run { - avatarRenderer.render(null, session.sessionParams.credentials.userId, null, view) + avatarRenderer.render(null, session.myUserId, null, view) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt index c4da3038..1d7a6a35 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromKeyViewModel.kt @@ -21,8 +21,8 @@ import androidx.lifecycle.ViewModel import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService -import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.riotx.R import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.ui.views.KeysBackupBanner @@ -57,7 +57,7 @@ class KeysBackupRestoreFromKeyViewModel @Inject constructor() : ViewModel() { keysBackup.restoreKeysWithRecoveryKey(keysVersionResult, recoveryKey, null, - session.sessionParams.credentials.userId, + session.myUserId, object : StepProgressListener { override fun onStepProgress(step: StepProgressListener.Step) { when (step) { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt index 3a4a9528..45995434 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/restore/KeysBackupRestoreFromPassphraseViewModel.kt @@ -21,8 +21,8 @@ import androidx.lifecycle.ViewModel import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.listeners.StepProgressListener import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService -import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult +import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.riotx.R import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.ui.views.KeysBackupBanner @@ -58,7 +58,7 @@ class KeysBackupRestoreFromPassphraseViewModel @Inject constructor() : ViewModel keysBackup.restoreKeyBackupWithPassword(keysVersionResult, passphrase.value!!, null, - sharedViewModel.session.sessionParams.credentials.userId, + sharedViewModel.session.myUserId, object : StepProgressListener { override fun onStepProgress(step: StepProgressListener.Step) { when (step) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt index 832e8a5e..ac4cc08d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDrawerFragment.kt @@ -52,7 +52,7 @@ class HomeDrawerFragment : VectorBaseFragment() { replaceChildFragment(groupListFragment, R.id.homeDrawerGroupListContainer) } - session.observeUser(session.sessionParams.credentials.userId).observeK(this) { user -> + session.observeUser(session.myUserId).observeK(this) { user -> if (user != null) { avatarRenderer.render(user.avatarUrl, user.userId, user.displayName, homeDrawerHeaderAvatarView) homeDrawerUsernameView.text = user.displayName diff --git a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt index 229652b0..513379bd 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/group/GroupListViewModel.kt @@ -93,7 +93,7 @@ class GroupListViewModel @AssistedInject constructor(@Assisted initialState: Gro .rx() .liveGroupSummaries() .map { - val myUser = session.getUser(session.sessionParams.credentials.userId) + val myUser = session.getUser(session.myUserId) val allCommunityGroup = GroupSummary( groupId = ALL_COMMUNITIES_GROUP_ID, displayName = stringProvider.getString(R.string.group_all_communities), diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 943b6165..23ff7a31 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -486,7 +486,7 @@ class RoomDetailFragment : timelineEventController.setTimeline(state.timeline, state.eventId) inviteView.visibility = View.GONE - val uid = session.sessionParams.credentials.userId + val uid = session.myUserId val meMember = session.getRoom(state.roomId)?.getRoomMember(uid) avatarRenderer.render(meMember?.avatarUrl, uid, meMember?.displayName, composerLayout.composerAvatarImageView) @@ -780,7 +780,7 @@ class RoomDetailFragment : if (null != text) { // var vibrate = false - val myDisplayName = session.getUser(session.sessionParams.credentials.userId)?.displayName + val myDisplayName = session.getUser(session.myUserId)?.displayName if (TextUtils.equals(myDisplayName, text)) { // current user if (TextUtils.isEmpty(composerLayout.composerEditText.text)) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 36b989fe..fe83002f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -39,7 +39,6 @@ import im.vector.matrix.android.api.session.room.model.message.getFileUrl import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.rx.rx -import im.vector.riotx.R import im.vector.riotx.core.intent.getFilenameFromUri import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.UserPreferencesProvider @@ -52,8 +51,6 @@ import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import timber.log.Timber import java.io.File -import java.text.SimpleDateFormat -import java.util.* import java.util.concurrent.TimeUnit @@ -347,7 +344,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro } private fun handleUndoReact(action: RoomDetailActions.UndoReaction) { - room.undoReaction(action.key, action.targetEventId, session.sessionParams.credentials.userId) + room.undoReaction(action.key, action.targetEventId, session.myUserId) } @@ -355,7 +352,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro if (action.add) { room.sendReaction(action.selectedReaction, action.targetEventId) } else { - room.undoReaction(action.selectedReaction, action.targetEventId, session.sessionParams.credentials.userId) + room.undoReaction(action.selectedReaction, action.targetEventId, session.myUserId) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index b49731d9..dc7c926d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -139,11 +139,11 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, eventId)) } - if (canEdit(event, session.sessionParams.credentials.userId)) { + if (canEdit(event, session.myUserId)) { this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId)) } - if (canRedact(event, session.sessionParams.credentials.userId)) { + if (canRedact(event, session.myUserId)) { this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId)) } @@ -183,7 +183,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M } this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId)) - if (session.sessionParams.credentials.userId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) { + if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) { //not sent by me this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, event.root.eventId)) } diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt index a98c7535..6650bf0d 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotifiableEventResolver.kt @@ -70,7 +70,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St val bodyPreview = event.type return SimpleNotifiableEvent( - session.sessionParams.credentials.userId, + session.myUserId, eventId = event.eventId!!, noisy = false,//will be updated timestamp = event.originServerTs ?: System.currentTimeMillis(), @@ -109,7 +109,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St roomId = event.root.roomId!!, roomName = roomName) - notifiableEvent.matrixID = session.sessionParams.credentials.userId + notifiableEvent.matrixID = session.myUserId return notifiableEvent } else { if (event.root.isEncrypted() && event.root.mxDecryptionResult == null) { @@ -145,7 +145,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St roomName = roomName, roomIsDirect = room.roomSummary()?.isDirect ?: false) - notifiableEvent.matrixID = session.sessionParams.credentials.userId + notifiableEvent.matrixID = session.myUserId notifiableEvent.soundName = null // Get the avatars URL @@ -175,7 +175,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St val body = noticeEventFormatter.format(event, dName) ?: stringProvider.getString(R.string.notification_new_invitation) return InviteNotifiableEvent( - session.sessionParams.credentials.userId, + session.myUserId, eventId = event.eventId!!, roomId = roomId, timestamp = event.originServerTs ?: 0, diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt index 17a1b222..ac6068b0 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationBroadcastReceiver.kt @@ -121,9 +121,9 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { UUID.randomUUID().toString(), false, System.currentTimeMillis(), - session.getUser(session.sessionParams.credentials.userId)?.displayName + session.getUser(session.myUserId)?.displayName ?: context?.getString(R.string.notification_sender_me), - session.sessionParams.credentials.userId, + session.myUserId, message, room.roomId, room.roomSummary()?.displayName ?: room.roomId, diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt index 67eef393..45317da5 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt @@ -181,9 +181,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context val session = activeSessionHolder.getSafeActiveSession() ?: return - val user = session.getUser(session.sessionParams.credentials.userId) + val user = session.getUser(session.myUserId) // myUserDisplayName cannot be empty else NotificationCompat.MessagingStyle() will crash - val myUserDisplayName = user?.displayName?.takeIf { it.isNotBlank() } ?: session.sessionParams.credentials.userId + val myUserDisplayName = user?.displayName?.takeIf { it.isNotBlank() } ?: session.myUserId val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE) synchronized(eventList) { diff --git a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt index 774bdfb9..d34d2be4 100755 --- a/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt +++ b/vector/src/main/java/im/vector/riotx/features/rageshake/BugReporter.kt @@ -204,7 +204,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes var olmVersion = "undefined" activeSessionHolder.getSafeActiveSession()?.let { session -> - userId = session.sessionParams.credentials.userId + userId = session.myUserId deviceId = session.sessionParams.credentials.deviceId ?: "undefined" olmVersion = session.getCryptoVersion(context, true) } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt index 7fd60562..5db218c3 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt @@ -79,9 +79,9 @@ class VectorSettingsActivity : VectorBaseActivity(), var oFragment: Fragment? = null if (VectorPreferences.SETTINGS_NOTIFICATION_TROUBLESHOOT_PREFERENCE_KEY == pref?.key) { - oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.sessionParams.credentials.userId) + oFragment = VectorSettingsNotificationsTroubleshootFragment.newInstance(session.myUserId) } else if (VectorPreferences.SETTINGS_NOTIFICATION_ADVANCED_PREFERENCE_KEY == pref?.key) { - oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.sessionParams.credentials.userId) + oFragment = VectorSettingsAdvancedNotificationPreferenceFragment.newInstance(session.myUserId) } else { try { pref?.fragment?.let { diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt index 63246b25..2bec8cf1 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsGeneralFragment.kt @@ -95,7 +95,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { // Display name mDisplayNamePreference.let { - it.summary = session.getUser(session.sessionParams.credentials.userId)?.displayName ?: "" + it.summary = session.getUser(session.myUserId)?.displayName ?: "" it.text = it.summary.toString() it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> onDisplayNameClick(newValue?.let { (it as String).trim() }) @@ -148,7 +148,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() { // user account findPreference(VectorPreferences.SETTINGS_LOGGED_IN_PREFERENCE_KEY) - .summary = session.sessionParams.credentials.userId + .summary = session.myUserId // home server findPreference(VectorPreferences.SETTINGS_HOME_SERVER_PREFERENCE_KEY) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 10a4fcce..06a33434 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -367,7 +367,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() { * @param aMyDeviceInfo the device info */ private fun refreshCryptographyPreference(aMyDeviceInfo: DeviceInfo?) { - val userId = session.sessionParams.credentials.userId + val userId = session.myUserId val deviceId = session.sessionParams.credentials.deviceId // device name diff --git a/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutUiWorker.kt b/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutUiWorker.kt index e3e7fb30..fd0af94f 100644 --- a/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutUiWorker.kt +++ b/vector/src/main/java/im/vector/riotx/features/workers/signout/SignOutUiWorker.kt @@ -35,7 +35,7 @@ class SignOutUiWorker(private val activity: FragmentActivity) { activeSessionHolder = context.vectorComponent().activeSessionHolder() val session = activeSessionHolder.getActiveSession() if (SignOutViewModel.doYouNeedToBeDisplayed(session)) { - val signOutDialog = SignOutBottomSheetDialogFragment.newInstance(session.sessionParams.credentials.userId) + val signOutDialog = SignOutBottomSheetDialogFragment.newInstance(session.myUserId) signOutDialog.onSignOut = Runnable { doSignOut() } From 45f7d3e9c4c542173ab109a9be7e40baba782d56 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Jul 2019 15:59:08 +0200 Subject: [PATCH 11/37] Kotlin style --- .../matrix/android/internal/SessionManager.kt | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt index 0a87a7ce..21f16d3d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/SessionManager.kt @@ -50,16 +50,10 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M } private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { - val userId = sessionParams.credentials.userId - if (sessionComponents.containsKey(userId)) { - return sessionComponents[userId]!! + return sessionComponents.getOrPut(sessionParams.credentials.userId) { + DaggerSessionComponent + .factory() + .create(matrixComponent, sessionParams) } - return DaggerSessionComponent - .factory() - .create(matrixComponent, sessionParams) - .also { - sessionComponents[userId] = it - } } - } \ No newline at end of file From 2f01ad99b375a8d2f1011d6ece6e3c7ebf4a007d Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Jul 2019 16:35:36 +0200 Subject: [PATCH 12/37] Compact long tap menu --- .../adapter_item_action_quick_reaction.xml | 18 ++---------------- .../layout/bottom_sheet_message_actions.xml | 1 + vector/src/main/res/values/strings.xml | 1 - 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml b/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml index 9f26e95f..16a53cff 100644 --- a/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml +++ b/vector/src/main/res/layout/adapter_item_action_quick_reaction.xml @@ -7,18 +7,6 @@ android:layout_height="wrap_content" android:padding="8dp"> - - - + app:layout_constraintTop_toTopOf="parent" /> diff --git a/vector/src/main/res/layout/bottom_sheet_message_actions.xml b/vector/src/main/res/layout/bottom_sheet_message_actions.xml index d1cb8c9f..9fadcee1 100644 --- a/vector/src/main/res/layout/bottom_sheet_message_actions.xml +++ b/vector/src/main/res/layout/bottom_sheet_message_actions.xml @@ -28,6 +28,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0" tools:src="@tools:sample/avatars" /> Other third party notices You are already viewing this room! - Quick Reactions General From 9bdea5b325aa349f5128b14eb523f5ddedc0e233 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Jul 2019 16:35:57 +0200 Subject: [PATCH 13/37] Change order of actions (and reply on top) --- .../timeline/action/MessageMenuViewModel.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt index b49731d9..bc21de4d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageMenuViewModel.kt @@ -127,13 +127,6 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M //TODO is downloading attachement? if (!event.root.isRedacted()) { - if (event.canReact()) { - this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, eventId)) - } - if (canCopy(type)) { - //TODO copy images? html? see ClipBoard - this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body)) - } if (canReply(event, messageContent)) { this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, eventId)) @@ -147,6 +140,15 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId)) } + if (canCopy(type)) { + //TODO copy images? html? see ClipBoard + this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body)) + } + + if (event.canReact()) { + this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, eventId)) + } + if (canQuote(event, messageContent)) { this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, eventId)) } From d469299f426584df9e7e39b8a525e04b67c81cdd Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 15 Jul 2019 20:26:18 +0200 Subject: [PATCH 14/37] RoomMembers: should fix state events issues --- .../room/membership/LoadRoomMembersTask.kt | 2 +- .../room/timeline/TokenChunkEventPersistor.kt | 4 ++-- .../internal/session/sync/RoomSyncHandler.kt | 18 +++++++----------- .../internal/session/user/UserEntityFactory.kt | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt index 3364a8e6..a3090605 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/LoadRoomMembersTask.kt @@ -74,7 +74,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP for (roomMemberEvent in response.roomMemberEvents) { roomEntity.addStateEvent(roomMemberEvent) - UserEntityFactory.create(roomMemberEvent)?.also { + UserEntityFactory.createOrNull(roomMemberEvent)?.also { realm.insertOrUpdate(it) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt index e9a34699..fb8b6271 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -153,7 +153,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy for (event in receivedChunk.events) { event.eventId?.also { eventIds.add(it) } currentChunk.add(roomId, event, direction, isUnlinked = currentChunk.isUnlinked()) - UserEntityFactory.create(event)?.also { + UserEntityFactory.createOrNull(event)?.also { realm.insertOrUpdate(it) } } @@ -174,7 +174,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy roomEntity.addOrUpdate(currentChunk) for (stateEvent in receivedChunk.stateEvents) { roomEntity.addStateEvent(stateEvent, isUnlinked = currentChunk.isUnlinked()) - UserEntityFactory.create(stateEvent)?.also { + UserEntityFactory.createOrNull(stateEvent)?.also { realm.insertOrUpdate(it) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt index a50e7ca8..215321bd 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/RoomSyncHandler.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.model.ChunkEntity +import im.vector.matrix.android.internal.database.model.EventEntityFields import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.UserEntity import im.vector.matrix.android.internal.database.query.find @@ -124,34 +125,29 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch } roomEntity.membership = Membership.JOIN - val lastChunk = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId) - val isInitialSync = lastChunk == null - val lastStateIndex = lastChunk?.lastStateIndex(PaginationDirection.FORWARDS) ?: 0 - val numberOfStateEvents = roomSync.state?.events?.size ?: 0 - val stateIndexOffset = lastStateIndex + numberOfStateEvents - // State event if (roomSync.state != null && roomSync.state.events.isNotEmpty()) { - val untimelinedStateIndex = if (isInitialSync) Int.MIN_VALUE else stateIndexOffset + val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt() + ?: Int.MIN_VALUE + val untimelinedStateIndex = minStateIndex + 1 roomSync.state.events.forEach { event -> roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex) // Give info to crypto module cryptoManager.onStateEvent(roomId, event) - UserEntityFactory.create(event)?.also { + UserEntityFactory.createOrNull(event)?.also { realm.insertOrUpdate(it) } } } if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { - val timelineStateOffset = if (isInitialSync || roomSync.timeline.limited.not()) 0 else stateIndexOffset val chunkEntity = handleTimelineEvents( realm, roomEntity, roomSync.timeline.events, roomSync.timeline.prevToken, roomSync.timeline.limited, - timelineStateOffset + 0 ) roomEntity.addOrUpdate(chunkEntity) } @@ -227,7 +223,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch Timber.v("Can't find corresponding local echo for tx:$it") } } - UserEntityFactory.create(event)?.also { + UserEntityFactory.createOrNull(event)?.also { realm.insertOrUpdate(it) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt index 45bbca78..188c7d84 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityFactory.kt @@ -24,7 +24,7 @@ import im.vector.matrix.android.internal.database.model.UserEntity internal object UserEntityFactory { - fun create(event: Event): UserEntity? { + fun createOrNull(event: Event): UserEntity? { if (event.type != EventType.STATE_ROOM_MEMBER) { return null } From a8f06f609bc2958a53b62fba37d4e2723840ca33 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 15 Jul 2019 17:54:05 +0200 Subject: [PATCH 15/37] Use latest retrofit version to properly cancel requests Fix cancelation requests --- matrix-sdk-android/build.gradle | 6 +-- .../android/internal/network/Request.kt | 46 +++++++------------ .../internal/network/RetrofitExtensions.kt | 41 +++++++++++++++++ .../riotx/core/platform/VectorViewModel.kt | 13 +++++- .../home/room/detail/RoomDetailViewModel.kt | 2 +- 5 files changed, 73 insertions(+), 35 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 3eaf43eb..b62b3fea 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -99,14 +99,14 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" - implementation "androidx.appcompat:appcompat:1.1.0-beta01" - implementation "androidx.recyclerview:recyclerview:1.1.0-alpha06" + implementation "androidx.appcompat:appcompat:1.1.0-rc01" + implementation "androidx.recyclerview:recyclerview:1.1.0-beta01" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // Network - implementation 'com.squareup.retrofit2:retrofit:2.4.0' + implementation 'com.squareup.retrofit2:retrofit:2.6.0' implementation 'com.squareup.retrofit2:converter-moshi:2.4.0' implementation 'com.squareup.okhttp3:okhttp:3.14.1' implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0' diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index bbd5859b..4dce02db 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -19,54 +19,40 @@ package im.vector.matrix.android.internal.network import arrow.core.Try import arrow.core.failure import arrow.core.recoverWith -import arrow.effects.IO -import arrow.effects.fix -import arrow.effects.instances.io.async.async -import arrow.integrations.retrofit.adapter.runAsync import com.squareup.moshi.JsonDataException import com.squareup.moshi.Moshi import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.internal.di.MoshiProvider -import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.ResponseBody import retrofit2.Call import timber.log.Timber import java.io.IOException -import kotlin.coroutines.resume -internal suspend inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() +internal suspend inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() -internal class Request { +internal class Request { private val moshi: Moshi = MoshiProvider.providesMoshi() lateinit var apiCall: Call suspend fun execute(): Try { - return suspendCancellableCoroutine { continuation -> - continuation.invokeOnCancellation { - Timber.v("Request is canceled") - apiCall.cancel() + return Try { + val response = apiCall.awaitResponse() + if (response.isSuccessful) { + response.body() + ?: throw IllegalStateException("The request returned a null body") + } else { + throw manageFailure(response.errorBody(), response.code()) } - val result = Try { - val response = apiCall.runAsync(IO.async()).fix().unsafeRunSync() - if (response.isSuccessful) { - response.body() - ?: throw IllegalStateException("The request returned a null body") - } else { - throw manageFailure(response.errorBody(), response.code()) - } - }.recoverWith { - when (it) { - is IOException -> Failure.NetworkConnection(it) - is Failure.ServerError, - is Failure.OtherServerError -> it - else -> Failure.Unknown(it) - }.failure() - } - continuation.resume(result) + }.recoverWith { + when (it) { + is IOException -> Failure.NetworkConnection(it) + is Failure.ServerError, + is Failure.OtherServerError -> it + else -> Failure.Unknown(it) + }.failure() } - } private fun manageFailure(errorBody: ResponseBody?, httpCode: Int): Throwable { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt new file mode 100644 index 00000000..f7be0270 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -0,0 +1,41 @@ +/* + * + * * Copyright 2019 New Vector Ltd + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package im.vector.matrix.android.internal.network + +import kotlinx.coroutines.suspendCancellableCoroutine +import retrofit2.* +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +suspend fun Call.awaitResponse(): Response { + return suspendCancellableCoroutine { continuation -> + continuation.invokeOnCancellation { + cancel() + } + enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + continuation.resume(response) + } + + override fun onFailure(call: Call, t: Throwable) { + continuation.resumeWithException(t) + } + }) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt index 7a79bf37..1570a7f8 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorViewModel.kt @@ -18,7 +18,18 @@ package im.vector.riotx.core.platform import com.airbnb.mvrx.BaseMvRxViewModel import com.airbnb.mvrx.MvRxState +import im.vector.matrix.android.api.util.CancelableBag import im.vector.riotx.BuildConfig abstract class VectorViewModel(initialState: S) - : BaseMvRxViewModel(initialState, false) \ No newline at end of file + : BaseMvRxViewModel(initialState, false) { + + protected val cancelableBag = CancelableBag() + + override fun onCleared() { + super.onCleared() + cancelableBag.cancel() + } + + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 2a19914a..312fbd9b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -97,7 +97,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro observeRoomSummary() observeEventDisplayedActions() observeInvitationState() - room.loadRoomMembersIfNeeded() + cancelableBag += room.loadRoomMembersIfNeeded() timeline.start() setState { copy(timeline = this@RoomDetailViewModel.timeline) } } From b7e0b400fba0f610d048a8e9ff5664f69bf0b00a Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 16 Jul 2019 17:48:32 +0200 Subject: [PATCH 16/37] Timeline : set bigger initial load size --- .../android/internal/session/room/timeline/DefaultTimeline.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 87f78224..e1a8bdd7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -43,7 +43,7 @@ import kotlin.collections.ArrayList import kotlin.collections.HashMap -private const val INITIAL_LOAD_SIZE = 10 +private const val INITIAL_LOAD_SIZE = 30 private const val MIN_FETCHING_COUNT = 30 private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE From 9494174c3389edca915af23a2068cb1afa7002f4 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 17 Jul 2019 10:51:09 +0200 Subject: [PATCH 17/37] Swipe to reply in timeline (lab) --- CHANGES.md | 2 + .../home/room/detail/RoomDetailFragment.kt | 31 ++- .../detail/RoomMessageTouchHelperCallback.kt | 206 ++++++++++++++++++ .../features/settings/VectorPreferences.kt | 5 + vector/src/main/res/values/strings_riotX.xml | 1 + .../src/main/res/xml/vector_settings_labs.xml | 68 +++--- 6 files changed, 280 insertions(+), 33 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt diff --git a/CHANGES.md b/CHANGES.md index ee6d2c8c..f4c72ba7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,8 @@ Features: Improvements: - Handle click on redacted events: view source and create permalink + - Improve long tap menu: reply on top, more compact (#368) + - Quick reply in timeline with swipe gesture Other changes: - diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 943b6165..4f91539c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -37,9 +37,11 @@ import androidx.annotation.DrawableRes import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import butterknife.BindView +import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel @@ -57,6 +59,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.message.* +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.user.model.User @@ -87,7 +90,7 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerViewState import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.action.* import im.vector.riotx.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener -import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData +import im.vector.riotx.features.home.room.detail.timeline.item.* import im.vector.riotx.features.html.EventHtmlRenderer import im.vector.riotx.features.html.PillImageSpan import im.vector.riotx.features.invite.VectorInviteView @@ -323,6 +326,32 @@ class RoomDetailFragment : }) recyclerView.setController(timelineEventController) timelineEventController.callback = this + + if (VectorPreferences.swipeToReplyIsEnabled(requireContext())) { + val swipeCallback = RoomMessageTouchHelperCallback(requireContext(), + R.drawable.ic_reply, + object : RoomMessageTouchHelperCallback.QuickReplayHandler { + override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { + (model as? AbsMessageItem)?.informationData?.let { + val eventId = it.eventId + roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId)) + } + } + + override fun canSwipeModelModel(model: EpoxyModel<*>): Boolean { + return when (model) { + is MessageFileItem, + is MessageImageVideoItem, + is MessageTextItem -> { + return (model as AbsMessageItem).informationData.sendState == SendState.SYNCED + } + else -> false + } + } + }) + val touchHelper = ItemTouchHelper(swipeCallback) + touchHelper.attachToRecyclerView(recyclerView) + } } private fun setupComposer() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt new file mode 100644 index 00000000..d716d6b4 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.riotx.features.home.room.detail + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.util.TypedValue +import android.view.HapticFeedbackConstants +import android.view.MotionEvent +import android.view.View +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE +import androidx.recyclerview.widget.RecyclerView +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.EpoxyTouchHelperCallback +import com.airbnb.epoxy.EpoxyViewHolder +import timber.log.Timber + + +class RoomMessageTouchHelperCallback(private val context: Context, + @DrawableRes actionIcon: Int, + private val handler: QuickReplayHandler) : EpoxyTouchHelperCallback() { + + interface QuickReplayHandler { + fun performQuickReplyOnHolder(model: EpoxyModel<*>) + fun canSwipeModelModel(model: EpoxyModel<*>): Boolean + } + + private var swipeBack: Boolean = false + private var dX = 0f + private var startTracking = false + private var isVibrate = false + + private var replyButtonProgress: Float = 0F + private var lastReplyButtonAnimationTime: Long = 0 + + private var imageDrawable: Drawable = ContextCompat.getDrawable(context, actionIcon)!! + + + private val triggerDistance = convertToPx(100) + private val minShowDistance = convertToPx(20) + private val triggerDelta = convertToPx(20) + + override fun onSwiped(viewHolder: EpoxyViewHolder?, direction: Int) { + + } + + override fun onMove(recyclerView: RecyclerView?, viewHolder: EpoxyViewHolder?, target: EpoxyViewHolder?): Boolean { + return false + } + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: EpoxyViewHolder): Int { + if (handler.canSwipeModelModel(viewHolder.model)) { + return ItemTouchHelper.Callback.makeMovementFlags(0, ItemTouchHelper.START) //Should we use Left? + } else { + return 0 + } + } + + + //We never let items completely go out + override fun convertToAbsoluteDirection(flags: Int, layoutDirection: Int): Int { + if (swipeBack) { + swipeBack = false; + return 0; + } + return super.convertToAbsoluteDirection(flags, layoutDirection); + } + + override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: EpoxyViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { + if (actionState == ACTION_STATE_SWIPE) { + setTouchListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } + val size = triggerDistance + if (Math.abs(viewHolder.itemView.translationX) < size || dX > this.dX /*going back*/) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + this.dX = dX + startTracking = true + } + drawReplyButton(c, viewHolder.itemView) + } + + + @SuppressLint("ClickableViewAccessibility") + private fun setTouchListener(c: Canvas, + recyclerView: RecyclerView, + viewHolder: EpoxyViewHolder, + dX: Float, dY: Float, + actionState: Int, isCurrentlyActive: Boolean) { + //TODO can this interfer with other interactions? should i remove it + recyclerView.setOnTouchListener { v, event -> + swipeBack = event.action == MotionEvent.ACTION_CANCEL || event.action == MotionEvent.ACTION_UP + if (swipeBack) { + if (Math.abs(dX) >= triggerDistance) { + try { + viewHolder.model?.let { handler.performQuickReplyOnHolder(it) } + } catch (e: IllegalStateException) { + Timber.e(e) + } + } + } + false + } + } + + + private fun drawReplyButton(canvas: Canvas, itemView: View) { + + Timber.v("drawReplyButton") + val translationX = Math.abs(itemView.translationX) + val newTime = System.currentTimeMillis() + val dt = Math.min(17, newTime - lastReplyButtonAnimationTime) + lastReplyButtonAnimationTime = newTime + val showing = translationX >= minShowDistance + if (showing) { + if (replyButtonProgress < 1.0f) { + replyButtonProgress += dt / 180.0f + if (replyButtonProgress > 1.0f) { + replyButtonProgress = 1.0f + } else { + itemView.invalidate() + } + } + } else if (translationX <= 0.0f) { + replyButtonProgress = 0f + startTracking = false + isVibrate = false + } else { + if (replyButtonProgress > 0.0f) { + replyButtonProgress -= dt / 180.0f + if (replyButtonProgress < 0.1f) { + replyButtonProgress = 0f + } else { + itemView.invalidate() + } + } + } + val alpha: Int + val scale: Float + if (showing) { + scale = if (replyButtonProgress <= 0.8f) { + 1.2f * (replyButtonProgress / 0.8f) + } else { + 1.2f - 0.2f * ((replyButtonProgress - 0.8f) / 0.2f) + } + alpha = Math.min(255f, 255 * (replyButtonProgress / 0.8f)).toInt() + } else { + scale = replyButtonProgress + alpha = Math.min(255f, 255 * replyButtonProgress).toInt() + } + + imageDrawable.alpha = alpha + if (startTracking) { + if (!isVibrate && translationX >= triggerDistance) { + itemView.performHapticFeedback( + HapticFeedbackConstants.LONG_PRESS +// , HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING + ) + isVibrate = true + } + } + + val x: Int = itemView.width - if (translationX > triggerDistance + triggerDelta) { + (convertToPx(130) / 2).toInt() + } else { + (translationX / 2).toInt() + } + + val y = (itemView.top + itemView.measuredHeight / 2).toFloat() + //magic numbers? + imageDrawable.setBounds( + (x - convertToPx(12) * scale).toInt(), + (y - convertToPx(11) * scale).toInt(), + (x + convertToPx(12) * scale).toInt(), + (y + convertToPx(10) * scale).toInt() + ) + imageDrawable.draw(canvas) + imageDrawable.alpha = 255 + } + + private fun convertToPx(dp: Int): Float { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + context.resources.displayMetrics + ) + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index 00f66f7c..b607b5a6 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -148,6 +148,7 @@ object VectorPreferences { private const val SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY = "SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY" private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY" + private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY" // analytics const val SETTINGS_USE_ANALYTICS_KEY = "SETTINGS_USE_ANALYTICS_KEY" @@ -249,6 +250,10 @@ object VectorPreferences { return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false) } + fun swipeToReplyIsEnabled(context: Context): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY, true) + } + /** * Tells if we have already asked the user to disable battery optimisations on android >= M devices. * diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 2b531bf4..7ad389d4 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -32,4 +32,5 @@ View the room directory + Enable swipe to reply in timeline \ No newline at end of file diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 66eb3f39..56205172 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -1,45 +1,49 @@ - + - - + + - + - - - + + + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + + - + + + + From 87de7bd3e61f95e5b8fa1c45058d3cb6289ae65b Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 17 Jul 2019 11:41:14 +0200 Subject: [PATCH 18/37] fix lint code quality --- .../internal/session/room/send/LocalEchoEventFactory.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index 51aed8a7..40390d5d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -334,8 +334,7 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials formattedText = content.formattedBody } } - val isReply = content.isReply() || - originalContent.isReply() + val isReply = content.isReply() || originalContent.isReply() return if (isReply) TextContent(content.body, formattedText).removeInReplyFallbacks() else From 3651ec4870cbd613d562e822f9d6d12fb3a28aa6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 11:58:18 +0200 Subject: [PATCH 19/37] Add some doc --- .../java/im/vector/riotx/core/di/ViewModelModule.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt index 37abde20..534a346a 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ViewModelModule.kt @@ -57,9 +57,15 @@ import im.vector.riotx.features.workers.signout.SignOutViewModel interface ViewModelModule { + /** + * ViewModels with @IntoMap will be injected by this factory + */ @Binds fun bindViewModelFactory(factory: VectorViewModelFactory): ViewModelProvider.Factory + /** + * Below are bindings for the androidx view models (which extend ViewModel). Will be converted to MvRx ViewModel in the future. + */ @Binds @IntoMap @ViewModelKey(SignOutViewModel::class) @@ -110,6 +116,10 @@ interface ViewModelModule { @ViewModelKey(ConfigurationViewModel::class) fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel + /** + * Below are bindings for the MvRx view models (which extend VectorViewModel). Will be the only usage in the future. + */ + @Binds fun bindHomeActivityViewModelFactory(factory: HomeActivityViewModel_AssistedFactory): HomeActivityViewModel.Factory From c32ef02a12504a03d33009ec8eecc38306aa278e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 12:04:19 +0200 Subject: [PATCH 20/37] Pre fill the room directory filter and and the room name with the already entered string from the user --- .../room/filtered/FilteredRoomFooterItem.kt | 9 +++-- .../home/room/list/RoomListFragment.kt | 9 ++--- .../home/room/list/RoomSummaryController.kt | 5 ++- .../home/room/list/widget/FabMenuView.kt | 2 +- .../features/navigation/DefaultNavigator.kt | 8 ++-- .../riotx/features/navigation/Navigator.kt | 4 +- .../roomdirectory/PublicRoomsFragment.kt | 5 +++ .../roomdirectory/PublicRoomsViewState.kt | 2 + .../roomdirectory/RoomDirectoryActivity.kt | 26 +++++++++++++ .../roomdirectory/RoomDirectoryViewModel.kt | 39 +++++++------------ .../createroom/CreateRoomActivity.kt | 15 ++++++- .../createroom/CreateRoomFragment.kt | 5 +-- .../createroom/CreateRoomViewModel.kt | 11 +++++- 13 files changed, 92 insertions(+), 48 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomFooterItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomFooterItem.kt index 0e916a0e..777f0d32 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomFooterItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomFooterItem.kt @@ -30,10 +30,13 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel + if (publicRoomsFilter.text.toString() != state.currentFilter) { + // For initial filter + publicRoomsFilter.setText(state.currentFilter) + } + // Populate list with Epoxy publicRoomsController.setData(state) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt index 0ad1ac26..549d81cc 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsViewState.kt @@ -22,6 +22,8 @@ import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom data class PublicRoomsViewState( + // The current filter + val currentFilter: String = "", // Store cumul of pagination result val publicRooms: List = emptyList(), // Current pagination request diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryActivity.kt index d708de94..d3cefad2 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryActivity.kt @@ -16,8 +16,11 @@ package im.vector.riotx.features.roomdirectory +import android.content.Context +import android.content.Intent import android.os.Bundle import androidx.lifecycle.ViewModelProviders +import com.airbnb.mvrx.viewModel import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.addFragment @@ -25,6 +28,7 @@ import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomFragment +import im.vector.riotx.features.roomdirectory.createroom.CreateRoomViewModel import im.vector.riotx.features.roomdirectory.picker.RoomDirectoryPickerFragment import javax.inject.Inject @@ -39,7 +43,10 @@ class RoomDirectoryActivity : VectorBaseActivity() { } + @Inject lateinit var createRoomViewModelFactory: CreateRoomViewModel.Factory @Inject lateinit var roomDirectoryViewModelFactory: RoomDirectoryViewModel.Factory + private val roomDirectoryViewModel: RoomDirectoryViewModel by viewModel() + private val createRoomViewModel: CreateRoomViewModel by viewModel() private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel override fun getLayoutRes() = R.layout.activity_simple @@ -51,6 +58,11 @@ class RoomDirectoryActivity : VectorBaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(RoomDirectoryNavigationViewModel::class.java) + + if (isFirstCreation()) { + roomDirectoryViewModel.filterWith(intent?.getStringExtra(INITIAL_FILTER) ?: "") + } + navigationViewModel.navigateTo.observeEvent(this) { navigation -> when (navigation) { is Navigation.Back -> onBackPressed() @@ -59,6 +71,11 @@ class RoomDirectoryActivity : VectorBaseActivity() { is Navigation.Close -> finish() } } + + roomDirectoryViewModel.selectSubscribe(this, PublicRoomsViewState::currentFilter) { currentFilter -> + // Transmit the filter to the createRoomViewModel + createRoomViewModel.setName(currentFilter) + } } override fun initUiAndData() { @@ -67,4 +84,13 @@ class RoomDirectoryActivity : VectorBaseActivity() { } } + companion object { + private const val INITIAL_FILTER = "INITIAL_FILTER" + + fun getIntent(context: Context, initialFilter: String = ""): Intent { + val intent = Intent(context, RoomDirectoryActivity::class.java) + intent.putExtra(INITIAL_FILTER, initialFilter) + return intent + } + } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt index b34618e2..c47e8bbd 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/RoomDirectoryViewModel.kt @@ -59,9 +59,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: get() = _joinRoomErrorLiveData - // TODO Store in ViewState? - private var currentFilter: String = "" - private var since: String? = null private var currentTask: Cancelable? = null @@ -70,9 +67,6 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: private var roomDirectoryData = RoomDirectoryData() init { - // Load with empty filter - load() - setState { copy( roomDirectoryDisplayName = roomDirectoryData.displayName @@ -115,24 +109,20 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: this.roomDirectoryData = roomDirectoryData - reset() - load() + reset("") + load("") } - fun filterWith(filter: String) { - if (currentFilter == filter) { - return + fun filterWith(filter: String) = withState { state -> + if (state.currentFilter != filter) { + currentTask?.cancel() + + reset(filter) + load(filter) } - - currentTask?.cancel() - - currentFilter = filter - - reset() - load() } - private fun reset() { + private fun reset(newFilter: String) { // Reset since token since = null @@ -141,12 +131,13 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: publicRooms = emptyList(), asyncPublicRoomsRequest = Loading(), hasMore = false, - roomDirectoryDisplayName = roomDirectoryData.displayName + roomDirectoryDisplayName = roomDirectoryData.displayName, + currentFilter = newFilter ) } } - fun loadMore() { + fun loadMore() = withState { state -> if (currentTask == null) { setState { copy( @@ -154,15 +145,15 @@ class RoomDirectoryViewModel @AssistedInject constructor(@Assisted initialState: ) } - load() + load(state.currentFilter) } } - private fun load() { + private fun load(filter: String) { currentTask = session.getPublicRooms(roomDirectoryData.homeServer, PublicRoomsParams( limit = PUBLIC_ROOMS_LIMIT, - filter = PublicRoomsFilter(searchTerm = currentFilter), + filter = PublicRoomsFilter(searchTerm = filter), includeAllNetworks = roomDirectoryData.includeAllNetworks, since = since, thirdPartyInstanceId = roomDirectoryData.thirdPartyInstanceId diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomActivity.kt index ffc342d8..7aa81fec 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -21,6 +21,7 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.widget.Toolbar import androidx.lifecycle.ViewModelProviders +import com.airbnb.mvrx.viewModel import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.addFragment @@ -29,12 +30,16 @@ import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.RoomDirectoryNavigationViewModel +import javax.inject.Inject /** * Simple container for [CreateRoomFragment] */ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable { + @Inject lateinit var createRoomViewModelFactory: CreateRoomViewModel.Factory + private val createRoomViewModel: CreateRoomViewModel by viewModel() + private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel override fun getLayoutRes() = R.layout.activity_simple @@ -46,6 +51,8 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable { override fun initUiAndData() { if (isFirstCreation()) { addFragment(CreateRoomFragment(), R.id.simpleFragmentContainer) + + createRoomViewModel.setName(intent?.getStringExtra(INITIAL_NAME) ?: "") } } @@ -64,8 +71,12 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable { } companion object { - fun getIntent(context: Context): Intent { - return Intent(context, CreateRoomActivity::class.java) + private const val INITIAL_NAME = "INITIAL_NAME" + + fun getIntent(context: Context, initialName: String = ""): Intent { + return Intent(context, CreateRoomActivity::class.java).apply { + putExtra(INITIAL_NAME, initialName) + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt index 28dc761a..3a007901 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -21,7 +21,7 @@ import android.view.MenuItem import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.Success -import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent @@ -35,9 +35,8 @@ import javax.inject.Inject class CreateRoomFragment : VectorBaseFragment(), CreateRoomController.Listener { private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel - private val viewModel: CreateRoomViewModel by fragmentViewModel() + private val viewModel: CreateRoomViewModel by activityViewModel() @Inject lateinit var createRoomController: CreateRoomController - @Inject lateinit var createRoomViewModelFactory: CreateRoomViewModel.Factory override fun getLayoutResId() = R.layout.fragment_create_room diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt index c6a4ff17..f0ee0115 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.roomdirectory.createroom +import androidx.fragment.app.FragmentActivity import com.airbnb.mvrx.* import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -25,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateRoomViewState, private val session: Session @@ -39,8 +41,13 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr @JvmStatic override fun create(viewModelContext: ViewModelContext, state: CreateRoomViewState): CreateRoomViewModel? { - val fragment: CreateRoomFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.createRoomViewModelFactory.create(state) + val activity: FragmentActivity = (viewModelContext as ActivityViewModelContext).activity() + + return when (activity) { + is CreateRoomActivity -> activity.createRoomViewModelFactory.create(state) + is RoomDirectoryActivity -> activity.createRoomViewModelFactory.create(state) + else -> throw IllegalStateException("Wrong activity") + } } } From 7eea2ccfb464c2e99f554fc29b0379ae270c7742 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 12:09:09 +0200 Subject: [PATCH 21/37] Fix infinite opening of room once the room is created --- .../features/roomdirectory/createroom/CreateRoomActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomActivity.kt index 7aa81fec..d6cca2b6 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomActivity.kt @@ -65,7 +65,8 @@ class CreateRoomActivity : VectorBaseActivity(), ToolbarConfigurable { navigationViewModel = ViewModelProviders.of(this, viewModelFactory).get(RoomDirectoryNavigationViewModel::class.java) navigationViewModel.navigateTo.observeEvent(this) { navigation -> when (navigation) { - is RoomDirectoryActivity.Navigation.Back -> finish() + is RoomDirectoryActivity.Navigation.Back, + is RoomDirectoryActivity.Navigation.Close -> finish() } } } From 8e12f71535835ed094c80861ab09367ec5a1c17f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 12:16:10 +0200 Subject: [PATCH 22/37] Add top left back button --- .../home/room/filtered/FilteredRoomsActivity.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt index 986d04b3..98fd3fa2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt @@ -20,17 +20,15 @@ import android.content.Context import android.content.Intent import android.os.Bundle import androidx.appcompat.widget.SearchView -import androidx.appcompat.widget.Toolbar import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.replaceFragment -import im.vector.riotx.core.platform.ToolbarConfigurable import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.home.room.list.RoomListParams import kotlinx.android.synthetic.main.activity_filtered_rooms.* -class FilteredRoomsActivity : VectorBaseActivity(), ToolbarConfigurable { +class FilteredRoomsActivity : VectorBaseActivity() { private lateinit var roomListFragment: RoomListFragment @@ -44,6 +42,9 @@ class FilteredRoomsActivity : VectorBaseActivity(), ToolbarConfigurable { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + configureToolbar(filteredRoomsToolbar) + if (isFirstCreation()) { roomListFragment = RoomListFragment.newInstance(RoomListParams(RoomListFragment.DisplayMode.FILTERED)) replaceFragment(roomListFragment, R.id.filteredRoomsFragmentContainer, FRAGMENT_TAG) @@ -67,10 +68,6 @@ class FilteredRoomsActivity : VectorBaseActivity(), ToolbarConfigurable { filteredRoomsSearchView.requestFocus() } - override fun configure(toolbar: Toolbar) { - configureToolbar(toolbar) - } - companion object { private const val FRAGMENT_TAG = "RoomListFragment" From b935b9311ef80be11df3d95d5c4b9e08cb159a79 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 12:18:45 +0200 Subject: [PATCH 23/37] Scroll the list to top after each new filter --- .../vector/riotx/features/home/room/list/RoomListFragment.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index a9490472..f944c2ea 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -136,6 +136,9 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O } fun filterRoomsWith(filter: String) { + // Scroll the list to top + epoxyRecyclerView.scrollToPosition(0) + roomListViewModel.accept(RoomListActions.FilterWith(filter)) } From 786a7d75603e7e68d2982ae59ab9832f0f35be19 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 12:20:11 +0200 Subject: [PATCH 24/37] Rename id --- .../features/home/room/list/RoomListFragment.kt | 12 ++++++------ vector/src/main/res/layout/fragment_room_list.xml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index f944c2ea..74873e51 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -112,7 +112,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O } // Hide FAB when list is scrolling - epoxyRecyclerView.addOnScrollListener( + roomListEpoxyRecyclerView.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { createChatFabMenu.removeCallbacks(showFabRunnable) @@ -137,7 +137,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O fun filterRoomsWith(filter: String) { // Scroll the list to top - epoxyRecyclerView.scrollToPosition(0) + roomListEpoxyRecyclerView.scrollToPosition(0) roomListViewModel.accept(RoomListActions.FilterWith(filter)) } @@ -153,12 +153,12 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O private fun setupRecyclerView() { val layoutManager = LinearLayoutManager(context) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() - epoxyRecyclerView.layoutManager = layoutManager - epoxyRecyclerView.itemAnimator = RoomListAnimator() + roomListEpoxyRecyclerView.layoutManager = layoutManager + roomListEpoxyRecyclerView.itemAnimator = RoomListAnimator() roomController.listener = this roomController.addModelBuildListener { it.dispatchTo(stateRestorer) } - stateView.contentView = epoxyRecyclerView - epoxyRecyclerView.setController(roomController) + stateView.contentView = roomListEpoxyRecyclerView + roomListEpoxyRecyclerView.setController(roomController) } private val showFabRunnable = Runnable { diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml index d4b6bd0f..c3fa4530 100644 --- a/vector/src/main/res/layout/fragment_room_list.xml +++ b/vector/src/main/res/layout/fragment_room_list.xml @@ -8,7 +8,7 @@ android:background="?riotx_header_panel_background"> From b654025a3b814dc5582379032dad619fd1273fde Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 12:29:19 +0200 Subject: [PATCH 25/37] Fix alignment issue in toolbars --- vector/src/main/res/layout/activity_filtered_rooms.xml | 1 + vector/src/main/res/layout/fragment_create_room.xml | 2 +- vector/src/main/res/layout/fragment_home_detail.xml | 2 +- vector/src/main/res/layout/fragment_public_rooms.xml | 2 +- vector/src/main/res/layout/fragment_room_detail.xml | 2 +- .../src/main/res/layout/fragment_room_preview_no_preview.xml | 4 ++-- vector/src/main/res/values/styles_riot.xml | 1 + 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/vector/src/main/res/layout/activity_filtered_rooms.xml b/vector/src/main/res/layout/activity_filtered_rooms.xml index 4d3e7036..2953b5fd 100644 --- a/vector/src/main/res/layout/activity_filtered_rooms.xml +++ b/vector/src/main/res/layout/activity_filtered_rooms.xml @@ -14,6 +14,7 @@ android:layout_width="0dp" android:layout_height="?attr/actionBarSize" android:elevation="4dp" + app:contentInsetStart="0dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> diff --git a/vector/src/main/res/layout/fragment_create_room.xml b/vector/src/main/res/layout/fragment_create_room.xml index f5eeeb31..301d5dee 100644 --- a/vector/src/main/res/layout/fragment_create_room.xml +++ b/vector/src/main/res/layout/fragment_create_room.xml @@ -11,10 +11,10 @@ diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml index 5ee80314..13d3fea8 100644 --- a/vector/src/main/res/layout/fragment_home_detail.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -8,10 +8,10 @@ diff --git a/vector/src/main/res/layout/fragment_public_rooms.xml b/vector/src/main/res/layout/fragment_public_rooms.xml index de6fa436..14cdd30f 100644 --- a/vector/src/main/res/layout/fragment_public_rooms.xml +++ b/vector/src/main/res/layout/fragment_public_rooms.xml @@ -23,11 +23,11 @@ diff --git a/vector/src/main/res/layout/fragment_room_preview_no_preview.xml b/vector/src/main/res/layout/fragment_room_preview_no_preview.xml index d6a154da..ee8f87fc 100644 --- a/vector/src/main/res/layout/fragment_room_preview_no_preview.xml +++ b/vector/src/main/res/layout/fragment_room_preview_no_preview.xml @@ -13,10 +13,10 @@ + android:elevation="4dp"> @style/Vector.Toolbar.Title @style/Vector.Toolbar.SubTitle ?riotx_background + 0dp + + From 208460850e9ff212ba9529f3ace15a0865661f4e Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 16 Jul 2019 19:13:48 +0200 Subject: [PATCH 29/37] Dagger: activate incremental build --- build.gradle | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/build.gradle b/build.gradle index 4fa468ab..b52707d5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,5 @@ +import javax.tools.JavaCompiler + // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { @@ -52,6 +54,19 @@ allprojects { } } } + + tasks.withType(JavaCompile).all { + options.compilerArgs += [ + '-Adagger.gradle.incremental=enabled' + ] + } + + afterEvaluate { + extensions.findByName("kapt")?.arguments { + arg("dagger.gradle.incremental", "enabled") + } + } + } task clean(type: Delete) { From 4bfaa00be486f064b4ba0e85b0d2ea356d46558a Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 17 Jul 2019 14:27:02 +0200 Subject: [PATCH 30/37] Fix / clean bad method name --- .../riotx/features/home/room/detail/RoomDetailFragment.kt | 2 +- .../home/room/detail/RoomMessageTouchHelperCallback.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 4f91539c..bfe3d2e1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -338,7 +338,7 @@ class RoomDetailFragment : } } - override fun canSwipeModelModel(model: EpoxyModel<*>): Boolean { + override fun canSwipeModel(model: EpoxyModel<*>): Boolean { return when (model) { is MessageFileItem, is MessageImageVideoItem, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt index d716d6b4..2803c66b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt @@ -40,7 +40,7 @@ class RoomMessageTouchHelperCallback(private val context: Context, interface QuickReplayHandler { fun performQuickReplyOnHolder(model: EpoxyModel<*>) - fun canSwipeModelModel(model: EpoxyModel<*>): Boolean + fun canSwipeModel(model: EpoxyModel<*>): Boolean } private var swipeBack: Boolean = false @@ -67,7 +67,7 @@ class RoomMessageTouchHelperCallback(private val context: Context, } override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: EpoxyViewHolder): Int { - if (handler.canSwipeModelModel(viewHolder.model)) { + if (handler.canSwipeModel(viewHolder.model)) { return ItemTouchHelper.Callback.makeMovementFlags(0, ItemTouchHelper.START) //Should we use Left? } else { return 0 From 22dc2a6790efcbf887f88e8decbb9b2b99fcff30 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 15 Jul 2019 19:03:52 +0200 Subject: [PATCH 31/37] Fix Copying link from a message shouldn't open context menu --- CHANGES.md | 3 ++- .../java/im/vector/riotx/core/utils/SystemUtils.kt | 5 +++-- .../home/room/detail/RoomDetailFragment.kt | 2 +- .../room/detail/timeline/item/MessageTextItem.kt | 14 ++++++++++++-- vector/src/main/res/values/strings_riotX.xml | 3 +++ 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5b598a57..fbd38522 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Features: Improvements: - Handle click on redacted events: view source and create permalink - Improve long tap menu: reply on top, more compact (#368) - - Quick reply in timeline with swipe gesture + - Quick reply in timeline with swipe gesture (#167) - Improve edit of replies Other changes: @@ -19,6 +19,7 @@ Bugfix: - Fix crash reported by the PlayStore (#341) - Fix Chat composer separator color in dark/black theme - Fix bad layout for room directory filter (#349) + - Fix Copying link from a message shouldn't open context menu (#364) Translations: - diff --git a/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt index f387b296..9c7b7938 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/SystemUtils.kt @@ -28,6 +28,7 @@ import android.os.Build import android.os.PowerManager import android.provider.Settings import android.widget.Toast +import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import im.vector.riotx.R @@ -81,11 +82,11 @@ fun requestDisablingBatteryOptimization(activity: Activity, fragment: Fragment?, * @param context the context * @param text the text to copy */ -fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = true) { +fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = true, @StringRes toastMessage : Int = R.string.copied_to_clipboard) { val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager clipboard.primaryClip = ClipData.newPlainText("", text) if (showToast) { - context.toast(R.string.copied_to_clipboard) + context.toast(toastMessage) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index e0f67aaf..c8e2d944 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -602,7 +602,7 @@ class RoomDetailFragment : override fun onUrlLongClicked(url: String): Boolean { // Copy the url to the clipboard - copyToClipboard(requireContext(), url) + copyToClipboard(requireContext(), url, true, R.string.link_copied_to_clipboard) return true } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt index b5074882..fc867b12 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item +import android.view.MotionEvent import androidx.appcompat.widget.AppCompatTextView import androidx.core.text.PrecomputedTextCompat import androidx.core.text.toSpannable @@ -40,14 +41,23 @@ abstract class MessageTextItem : AbsMessageItem() { @EpoxyAttribute var urlClickCallback: TimelineEventController.UrlClickCallback? = null + // Better link movement methods fixes the issue when + // long pressing to open the context menu on a TextView also triggers an autoLink click. private val mvmtMethod = BetterLinkMovementMethod.newInstance().also { it.setOnLinkClickListener { _, url -> //Return false to let android manage the click on the link, or true if the link is handled by the application urlClickCallback?.onUrlClicked(url) == true } - it.setOnLinkLongClickListener { _, url -> + //We need also to fix the case when long click on link will trigger long click on cell + it.setOnLinkLongClickListener { tv, url -> //Long clicks are handled by parent, return true to block android to do something with url - urlClickCallback?.onUrlLongClicked(url) == true + if (urlClickCallback?.onUrlLongClicked(url) == true) { + tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)) + true + } else { + false + } + } } diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index a777e85f..f6ec4892 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -36,4 +36,7 @@ Enable swipe to reply in timeline + + Link copied to clipboard + \ No newline at end of file From 90d25ff45e44c833889690d58e2604f25cb730f1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 14:41:01 +0200 Subject: [PATCH 32/37] Code cleanup --- .../detail/RoomMessageTouchHelperCallback.kt | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt index 2803c66b..cb283511 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomMessageTouchHelperCallback.kt @@ -78,15 +78,21 @@ class RoomMessageTouchHelperCallback(private val context: Context, //We never let items completely go out override fun convertToAbsoluteDirection(flags: Int, layoutDirection: Int): Int { if (swipeBack) { - swipeBack = false; - return 0; + swipeBack = false + return 0 } - return super.convertToAbsoluteDirection(flags, layoutDirection); + return super.convertToAbsoluteDirection(flags, layoutDirection) } - override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: EpoxyViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { + override fun onChildDraw(c: Canvas, + recyclerView: RecyclerView, + viewHolder: EpoxyViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean) { if (actionState == ACTION_STATE_SWIPE) { - setTouchListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + setTouchListener(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } val size = triggerDistance if (Math.abs(viewHolder.itemView.translationX) < size || dX > this.dX /*going back*/) { @@ -102,8 +108,10 @@ class RoomMessageTouchHelperCallback(private val context: Context, private fun setTouchListener(c: Canvas, recyclerView: RecyclerView, viewHolder: EpoxyViewHolder, - dX: Float, dY: Float, - actionState: Int, isCurrentlyActive: Boolean) { + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean) { //TODO can this interfer with other interactions? should i remove it recyclerView.setOnTouchListener { v, event -> swipeBack = event.action == MotionEvent.ACTION_CANCEL || event.action == MotionEvent.ACTION_UP From 2948018453a3706d14c2b152a266180cb72ac249 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 17 Jul 2019 14:56:00 +0200 Subject: [PATCH 33/37] Clean code after review --- .../session/room/timeline/TimelineEvent.kt | 2 +- .../android/internal/network/Request.kt | 4 +- .../internal/network/RetrofitExtensions.kt | 28 ++++---- .../internal/session/sync/SyncModule.kt | 2 - .../internal/session/user/UpdateUserTask.kt | 44 ------------ .../session/user/UserEntityUpdater.kt | 67 ------------------- .../internal/session/user/UserModule.kt | 3 - 7 files changed, 17 insertions(+), 133 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UpdateUserTask.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt index 62943de6..f626e3a7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/timeline/TimelineEvent.kt @@ -33,7 +33,7 @@ data class TimelineEvent( val localId: Long, val displayIndex: Int, val senderName: String?, - val isUniqueDisplayName: Boolean = false, + val isUniqueDisplayName: Boolean, val senderAvatar: String?, val sendState: SendState, val annotations: EventAnnotationsSummary? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt index 4dce02db..4dfc5810 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/Request.kt @@ -29,9 +29,9 @@ import retrofit2.Call import timber.log.Timber import java.io.IOException -internal suspend inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() +internal suspend inline fun executeRequest(block: Request.() -> Unit) = Request().apply(block).execute() -internal class Request { +internal class Request { private val moshi: Moshi = MoshiProvider.providesMoshi() lateinit var apiCall: Call diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt index f7be0270..7528dee2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/RetrofitExtensions.kt @@ -23,19 +23,19 @@ import retrofit2.* import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException -suspend fun Call.awaitResponse(): Response { - return suspendCancellableCoroutine { continuation -> - continuation.invokeOnCancellation { - cancel() - } - enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - continuation.resume(response) - } +suspend fun Call.awaitResponse(): Response { + return suspendCancellableCoroutine { continuation -> + continuation.invokeOnCancellation { + cancel() + } + enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + continuation.resume(response) + } - override fun onFailure(call: Call, t: Throwable) { - continuation.resumeWithException(t) - } - }) - } + override fun onFailure(call: Call, t: Throwable) { + continuation.resumeWithException(t) + } + }) + } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt index 3d84d63e..598d5f07 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt @@ -20,8 +20,6 @@ import dagger.Binds import dagger.Module import dagger.Provides import im.vector.matrix.android.internal.session.SessionScope -import im.vector.matrix.android.internal.session.user.DefaultUpdateUserTask -import im.vector.matrix.android.internal.session.user.UpdateUserTask import retrofit2.Retrofit @Module diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UpdateUserTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UpdateUserTask.kt deleted file mode 100644 index 616fcfff..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UpdateUserTask.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.session.user - -import arrow.core.Try -import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.room.model.Membership -import im.vector.matrix.android.internal.database.mapper.asDomain -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.TimelineEventEntity -import im.vector.matrix.android.internal.database.model.UserEntity -import im.vector.matrix.android.internal.database.query.where -import im.vector.matrix.android.internal.session.SessionScope -import im.vector.matrix.android.internal.session.room.membership.RoomMembers -import im.vector.matrix.android.internal.task.Task -import im.vector.matrix.android.internal.util.tryTransactionSync -import javax.inject.Inject - -internal interface UpdateUserTask : Task { - - data class Params(val eventIds: List) - -} - -internal class DefaultUpdateUserTask @Inject constructor(private val monarchy: Monarchy) : UpdateUserTask { - - override suspend fun execute(params: UpdateUserTask.Params): Try { - return Try.just(Unit) - } -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt deleted file mode 100644 index 92581af1..00000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserEntityUpdater.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.matrix.android.internal.session.user - -import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.internal.database.RealmLiveEntityObserver -import im.vector.matrix.android.internal.database.model.EventEntity -import im.vector.matrix.android.internal.database.model.EventEntityFields -import im.vector.matrix.android.internal.database.model.UserEntity -import im.vector.matrix.android.internal.database.query.types -import im.vector.matrix.android.internal.di.SessionDatabase -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.TaskThread -import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.util.tryTransactionAsync -import im.vector.matrix.android.internal.util.tryTransactionSync -import io.realm.OrderedCollectionChangeSet -import io.realm.RealmConfiguration -import io.realm.RealmResults -import io.realm.Sort -import javax.inject.Inject - -internal class UserEntityUpdater @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, - private val monarchy: Monarchy, - private val updateUserTask: UpdateUserTask, - private val taskExecutor: TaskExecutor) - : RealmLiveEntityObserver(realmConfiguration) { - - override val query = Monarchy.Query { - EventEntity - .types(it, listOf(EventType.STATE_ROOM_MEMBER)) - .sort(EventEntityFields.STATE_INDEX, Sort.DESCENDING) - .distinct(EventEntityFields.STATE_KEY) - } - - override fun onChange(results: RealmResults, changeSet: OrderedCollectionChangeSet) { - monarchy.tryTransactionSync { realm -> - val userEntities = ArrayList(changeSet.insertions.size) - for (insertion in changeSet.insertions) { - val roomMemberEvent = results[insertion] ?: continue - val roomMemberTimelineEvent = roomMemberEvent.timelineEventEntity?.firstOrNull() - ?: continue - val userEntity = UserEntity(roomMemberEvent.stateKey - ?: "", roomMemberTimelineEvent.senderName ?: "", - roomMemberTimelineEvent.senderAvatar ?: "") - userEntities.add(userEntity) - } - realm.insertOrUpdate(userEntities) - } - } - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt index a35f5a3b..00368dfa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/UserModule.kt @@ -26,7 +26,4 @@ internal abstract class UserModule { @Binds abstract fun bindUserService(userService: DefaultUserService): UserService - @Binds - abstract fun bindUpdateUserTask(updateUserTask: DefaultUpdateUserTask): UpdateUserTask - } \ No newline at end of file From 77fa5af1b808c372d51a26536b5cc482082e8d11 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 14:58:23 +0200 Subject: [PATCH 34/37] Fix compilation issue after merge --- .../riotx/features/roomdirectory/PublicRoomsFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt index f072b1f4..32cd6732 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/PublicRoomsFragment.kt @@ -144,9 +144,9 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback } override fun invalidate() = withState(viewModel) { state -> - if (publicRoomsFilter.text.toString() != state.currentFilter) { + if (publicRoomsFilter.query.toString() != state.currentFilter) { // For initial filter - publicRoomsFilter.setText(state.currentFilter) + publicRoomsFilter.setQuery(state.currentFilter, false) } // Populate list with Epoxy From 844f6d16a44bebadfae2016b75553cd854379c33 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 15:05:29 +0200 Subject: [PATCH 35/37] Code quality --- CHANGES.md | 1 + .../session/room/membership/RoomDisplayNameResolver.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fbd38522..7cffa1ff 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ Improvements: - Improve long tap menu: reply on top, more compact (#368) - Quick reply in timeline with swipe gesture (#167) - Improve edit of replies + - Improve performance on Room Members and Users management (#381) Other changes: - migrate from rxbinding 2 to rxbinding 3 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt index c27b0a74..b6b7c3e1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -90,7 +90,9 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: val inviteMeEvent = roomMembers.queryRoomMemberEvent(credentials.userId).findFirst() val inviterId = inviteMeEvent?.sender name = if (inviterId != null) { - val inviterMemberEvent = loadedMembers.where().equalTo(EventEntityFields.STATE_KEY, inviterId).findFirst() + val inviterMemberEvent = loadedMembers.where() + .equalTo(EventEntityFields.STATE_KEY, inviterId) + .findFirst() inviterMemberEvent?.toRoomMember()?.displayName } else { context.getString(R.string.room_displayname_room_invite) From 32b79bd50e83291f6a7d3f4749d5dcc5333316da Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 15:13:12 +0200 Subject: [PATCH 36/37] Remove extra space around userId --- .../internal/session/room/membership/RoomDisplayNameResolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt index b6b7c3e1..948f1741 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -130,7 +130,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context: return if (isUnique) { roomMember.displayName } else { - "${roomMember.displayName} ( ${eventEntity.stateKey} )" + "${roomMember.displayName} (${eventEntity.stateKey})" } } From 977721881f62c40543810a2c6b941e447120d7d3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Jul 2019 18:35:41 +0200 Subject: [PATCH 37/37] Cancel invitation notification when handling the invitation in the application --- .../riotx/features/home/room/detail/RoomDetailFragment.kt | 2 ++ .../vector/riotx/features/home/room/list/RoomListFragment.kt | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index c8e2d944..55ee4dfa 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -858,10 +858,12 @@ class RoomDetailFragment : // VectorInviteView.Callback override fun onAcceptInvite() { + notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId) roomDetailViewModel.process(RoomDetailActions.AcceptInvite) } override fun onRejectInvite() { + notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId) roomDetailViewModel.process(RoomDetailActions.RejectInvite) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt index 74873e51..b61e69ac 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomListFragment.kt @@ -38,6 +38,7 @@ import im.vector.riotx.core.platform.OnBackPressed import im.vector.riotx.core.platform.StateView import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.features.home.room.list.widget.FabMenuView +import im.vector.riotx.features.notifications.NotificationDrawerManager import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.fragment_room_list.* import javax.inject.Inject @@ -69,6 +70,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O @Inject lateinit var roomController: RoomSummaryController @Inject lateinit var roomListViewModelFactory: RoomListViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter + @Inject lateinit var notificationDrawerManager: NotificationDrawerManager private val roomListViewModel: RoomListViewModel by fragmentViewModel() override fun getLayoutResId() = R.layout.fragment_room_list @@ -258,10 +260,12 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Listener, O } override fun onAcceptRoomInvitation(room: RoomSummary) { + notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId) roomListViewModel.accept(RoomListActions.AcceptInvitation(room)) } override fun onRejectRoomInvitation(room: RoomSummary) { + notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId) roomListViewModel.accept(RoomListActions.RejectInvitation(room)) }