From 1c69d8e4257d6b2559479aaef6ddb793d5a26358 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 2 Jul 2019 18:06:43 +0200 Subject: [PATCH 01/75] README: Update it for the beta launch --- README.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2fe92f58..ba8ae001 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,27 @@ # RiotX Android -RiotX is an Android Matrix Client currently in development. The application is not yet available on the PlayStore. +RiotX is an Android Matrix Client currently in beta but in active development. -It's based on a new Matrix SDK, written in Kotlin. +It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented. -Download nightly build here: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop) +[Get it on Google Play](https://play.google.com/store/apps/details?id=im.vector.riotx) + + +# New Android SDK + +RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the API is stable enough. + + +# Roadmap + +The current target is to release an application out of beta with the same level of features (and even more) as Riot. +The roadmap has 3 phases: + +- [phase 0](https://github.com/vector-im/riotX-android/labels/phase0): Prototyping / Project setup +- [phase 1](https://github.com/vector-im/riotX-android/labels/phase1): Beta release to the Play Store +- [phase 2](https://github.com/vector-im/riotX-android/labels/phase2): Out of beta -Matrix Room: [![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org) ## Contributing From 698fc35704ce2c5ee665e05f8e7291b0117f600a Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 2 Jul 2019 18:41:28 +0200 Subject: [PATCH 02/75] README: Put back link to #riotx:matrix.org --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ba8ae001..115fcc23 100644 --- a/README.md +++ b/README.md @@ -31,4 +31,6 @@ The roadmap has 3 phases: ## Contributing -Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute the Matrix on Android projects! +Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute on Matrix Android projects! + +Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#riotx:matrix.org). From 4b971a9e672ce8906439598f10ef2fe09afd7dc4 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 3 Jul 2019 10:04:35 +0200 Subject: [PATCH 03/75] README: Fix develop build links --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 115fcc23..d9f94454 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android) +[![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop) [![Weblate](https://translate.riot.im/widgets/riot-android/-/svg-badge.svg)](https://translate.riot.im/engage/riot-android/?utm_source=widget) [![RiotX Android Matrix room #riot-android:matrix.org](https://img.shields.io/matrix/riotx:matrix.org.svg?label=%23RiotX:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#riotx:matrix.org) [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=vector.android.riotx&metric=alert_status)](https://sonarcloud.io/dashboard?id=vector.android.riotx) @@ -13,6 +13,7 @@ It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-androi [Get it on Google Play](https://play.google.com/store/apps/details?id=im.vector.riotx) +Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop) # New Android SDK From bfb5fce80977eeb28fe3aded5414b1f1c151eabb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 11 Jul 2019 17:43:56 +0200 Subject: [PATCH 04/75] Update CHANGES.md --- CHANGES.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7b52a037..0e7f29e6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,23 +1,10 @@ -Changes in RiotX 0.XX (2019-XX-XX) +Changes in RiotX 0.1.0 (2019-07-11) =================================================== -Features: - - Contextual action menu for messages in room +First release! -Improvements: - - -Other changes: - - -Bugfix: - - - -Translations: - - - -Build: - - From 43b368077467f40fa16484455dc09205b3ba5bd2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 11 Jul 2019 17:44:58 +0200 Subject: [PATCH 05/75] Prepare next release --- vector/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/build.gradle b/vector/build.gradle index b4f27d7e..eaba03e1 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -13,7 +13,7 @@ androidExtensions { } ext.versionMajor = 0 -ext.versionMinor = 1 +ext.versionMinor = 2 ext.versionPatch = 0 static def getGitTimestamp() { @@ -65,8 +65,8 @@ android { multiDexEnabled true // For release, use generateVersionCodeFromVersionName() - // versionCode generateVersionCodeFromTimestamp() - versionCode generateVersionCodeFromVersionName() + versionCode generateVersionCodeFromTimestamp() + // versionCode generateVersionCodeFromVersionName() versionName "${versionMajor}.${versionMinor}.${versionPatch}" From 919dec4a5645d2ee97ae5ae7ed18f9dbef8f802f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 11 Jul 2019 17:59:07 +0200 Subject: [PATCH 06/75] Add ".debug" to the applicationId to be able to install the app along with the prod version --- vector/build.gradle | 5 +++ vector/src/gplay/debug/google-services.json | 40 +++++++++++++++++++++ vector/src/main/res/values/config.xml | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 vector/src/gplay/debug/google-services.json diff --git a/vector/build.gradle b/vector/build.gradle index eaba03e1..ace3135a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -96,6 +96,9 @@ android { buildTypes { debug { + applicationIdSuffix ".debug" + resValue "string", "app_name", "RiotX dbg" + resValue "bool", "debug_mode", "true" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" @@ -103,6 +106,8 @@ android { } release { + resValue "string", "app_name", "RiotX" + resValue "bool", "debug_mode", "false" buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false" diff --git a/vector/src/gplay/debug/google-services.json b/vector/src/gplay/debug/google-services.json new file mode 100644 index 00000000..185f7afb --- /dev/null +++ b/vector/src/gplay/debug/google-services.json @@ -0,0 +1,40 @@ +{ + "project_info": { + "project_number": "912726360885", + "firebase_url": "https://vector-alpha.firebaseio.com", + "project_id": "vector-alpha", + "storage_bucket": "vector-alpha.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:912726360885:android:4ef8f3a0021e774d", + "android_client_info": { + "package_name": "im.vector.riotx.debug" + } + }, + "oauth_client": [ + { + "client_id": "912726360885-e87n3jva9uoj4vbidvijq78ebg02asv2.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAFZX8IhIfgzdOZvxDP_ISO5WYoU7jmQ5c" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "912726360885-rsae0i66rgqt6ivnudu1pv4tksg9i8b2.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/vector/src/main/res/values/config.xml b/vector/src/main/res/values/config.xml index f3904499..508b942a 100755 --- a/vector/src/main/res/values/config.xml +++ b/vector/src/main/res/values/config.xml @@ -1,7 +1,7 @@ - "RiotX" + https://vector.im From 10e4d0190fee94218d274004696814ff7484a9cf Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 11 Jul 2019 18:55:13 +0200 Subject: [PATCH 07/75] 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 33252c3b65bc075d19a0a66245650a2eeab38d95 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 10:16:43 +0200 Subject: [PATCH 08/75] Green text color during encrypting --- .../timeline/factory/EncryptedItemFactory.kt | 1 + .../timeline/factory/MessageItemFactory.kt | 8 ++++ .../helper/ContentUploadStateTrackerBinder.kt | 16 ++++++-- .../detail/timeline/item/AbsMessageItem.kt | 11 ++++-- .../detail/timeline/item/MessageFileItem.kt | 2 +- .../timeline/item/MessageImageVideoItem.kt | 3 +- .../detail/timeline/item/MessageTextItem.kt | 2 +- .../riotx/features/ui/ColorProviderExt.kt | 37 +++++++++++++++++++ vector/src/main/res/values/theme_dark.xml | 2 +- vector/src/main/res/values/theme_light.xml | 2 +- 10 files changed, 72 insertions(+), 12 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/ui/ColorProviderExt.kt diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index 2a4a0c0f..080565cd 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -68,6 +68,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat return MessageTextItem_() .message(spannableStr) .avatarRenderer(avatarRenderer) + .colorProvider(colorProvider) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index d8f1c602..02e3e43b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -117,6 +117,7 @@ class MessageItemFactory @Inject constructor( callback: TimelineEventController.Callback?): MessageFileItem? { return MessageFileItem_() .avatarRenderer(avatarRenderer) + .colorProvider(colorProvider) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) @@ -144,6 +145,7 @@ class MessageItemFactory @Inject constructor( callback: TimelineEventController.Callback?): MessageFileItem? { return MessageFileItem_() .avatarRenderer(avatarRenderer) + .colorProvider(colorProvider) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) @@ -195,6 +197,7 @@ class MessageItemFactory @Inject constructor( ) return MessageImageVideoItem_() .avatarRenderer(avatarRenderer) + .colorProvider(colorProvider) .imageContentRenderer(imageContentRenderer) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .playable(messageContent.info?.mimeType == "image/gif") @@ -246,6 +249,7 @@ class MessageItemFactory @Inject constructor( .imageContentRenderer(imageContentRenderer) .contentUploadStateTrackerBinder(contentUploadStateTrackerBinder) .avatarRenderer(avatarRenderer) + .colorProvider(colorProvider) .playable(true) .informationData(informationData) .highlighted(highlight) @@ -288,6 +292,7 @@ class MessageItemFactory @Inject constructor( } .avatarRenderer(avatarRenderer) .informationData(informationData) + .colorProvider(colorProvider) .highlighted(highlight) .avatarCallback(callback) .urlClickCallback(callback) @@ -353,6 +358,7 @@ class MessageItemFactory @Inject constructor( return MessageTextItem_() .avatarRenderer(avatarRenderer) .message(message) + .colorProvider(colorProvider) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) @@ -393,6 +399,7 @@ class MessageItemFactory @Inject constructor( } } .avatarRenderer(avatarRenderer) + .colorProvider(colorProvider) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) @@ -414,6 +421,7 @@ class MessageItemFactory @Inject constructor( callback: TimelineEventController.Callback?): RedactedMessageItem? { return RedactedMessageItem_() .avatarRenderer(avatarRenderer) + .colorProvider(colorProvider) .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt index 688cac3d..ca796667 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/ContentUploadStateTrackerBinder.kt @@ -23,12 +23,16 @@ import android.widget.ProgressBar import android.widget.TextView import androidx.core.view.isVisible import im.vector.matrix.android.api.session.content.ContentUploadStateTracker +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder +import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.features.media.ImageContentRenderer +import im.vector.riotx.features.ui.getMessageTextColor import javax.inject.Inject -class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) { +class ContentUploadStateTrackerBinder @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, + private val colorProvider: ColorProvider) { private val updateListeners = mutableMapOf() @@ -38,7 +42,7 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess activeSessionHolder.getActiveSession().also { session -> val uploadStateTracker = session.contentUploadProgressTracker() - val updateListener = ContentMediaProgressUpdater(progressLayout, mediaData) + val updateListener = ContentMediaProgressUpdater(progressLayout, mediaData, colorProvider) updateListeners[eventId] = updateListener uploadStateTracker.track(eventId, updateListener) } @@ -56,7 +60,8 @@ class ContentUploadStateTrackerBinder @Inject constructor(private val activeSess } private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, - private val mediaData: ImageContentRenderer.Data) : ContentUploadStateTracker.UpdateListener { + private val mediaData: ImageContentRenderer.Data, + private val colorProvider: ColorProvider) : ContentUploadStateTracker.UpdateListener { override fun onUpdate(state: ContentUploadStateTracker.State) { when (state) { @@ -79,6 +84,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, progressBar?.isIndeterminate = true progressBar?.progress = 0 progressTextView?.text = progressLayout.context.getString(R.string.send_file_step_idle) + progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.UNSENT)) } else { progressLayout.isVisible = false } @@ -106,6 +112,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, val progressTextView = progressLayout.findViewById(R.id.mediaProgressTextView) progressBar?.isIndeterminate = true progressTextView?.text = progressLayout.context.getString(resId) + progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.ENCRYPTING)) } private fun doHandleProgress(resId: Int, current: Long, total: Long) { @@ -119,6 +126,7 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, progressTextView?.text = progressLayout.context.getString(resId, Formatter.formatShortFileSize(progressLayout.context, current), Formatter.formatShortFileSize(progressLayout.context, total)) + progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.SENDING)) } private fun handleFailure(state: ContentUploadStateTracker.State.Failure) { @@ -126,8 +134,8 @@ private class ContentMediaProgressUpdater(private val progressLayout: ViewGroup, val progressBar = progressLayout.findViewById(R.id.mediaProgressBar) val progressTextView = progressLayout.findViewById(R.id.mediaProgressTextView) progressBar?.isVisible = false - // TODO Red text progressTextView?.text = state.throwable.localizedMessage + progressTextView?.setTextColor(colorProvider.getMessageTextColor(SendState.UNDELIVERED)) } private fun handleSuccess(state: ContentUploadStateTracker.State.Success) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index 7a66c6ad..f76601cd 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -29,11 +29,13 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import im.vector.riotx.R +import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DimensionUtils.dpToPx import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.reactions.widget.ReactionButton +import im.vector.riotx.features.ui.getMessageTextColor abstract class AbsMessageItem : BaseEventItem() { @@ -42,6 +44,9 @@ abstract class AbsMessageItem : BaseEventItem() { abstract val avatarRenderer: AvatarRenderer + @EpoxyAttribute + lateinit var colorProvider: ColorProvider + @EpoxyAttribute var longClickListener: View.OnLongClickListener? = null @@ -153,9 +158,9 @@ abstract class AbsMessageItem : BaseEventItem() { return true } - protected fun View.renderSendState() { - isClickable = informationData.sendState.isSent() - alpha = if (informationData.sendState.isSent()) 1f else 0.5f + protected fun renderSendState(root: View, textView: TextView?) { + root.isClickable = informationData.sendState.isSent() + textView?.setTextColor(colorProvider.getMessageTextColor(informationData.sendState)) } abstract class Holder : BaseHolder() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt index 66b368df..3d5fb452 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -44,7 +44,7 @@ abstract class MessageFileItem : AbsMessageItem() { override fun bind(holder: Holder) { super.bind(holder) - holder.fileLayout.renderSendState() + renderSendState(holder.fileLayout, holder.filenameView) holder.filenameView.text = filename holder.fileImageView.setImageResource(iconRes) holder.filenameView.setOnClickListener(clickListener) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index 67f0ed7b..bc47be74 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -52,7 +52,8 @@ abstract class MessageImageVideoItem : AbsMessageItem() { null) holder.messageView.setTextFuture(textFuture) - holder.messageView.renderSendState() + renderSendState(holder.messageView, holder.messageView) holder.messageView.setOnClickListener(cellClickListener) holder.messageView.setOnLongClickListener(longClickListener) findPillsAndProcess { it.bind(holder.messageView) } diff --git a/vector/src/main/java/im/vector/riotx/features/ui/ColorProviderExt.kt b/vector/src/main/java/im/vector/riotx/features/ui/ColorProviderExt.kt new file mode 100644 index 00000000..a53ed921 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/ui/ColorProviderExt.kt @@ -0,0 +1,37 @@ +/* + * 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.ui + +import androidx.annotation.ColorInt +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.riotx.R +import im.vector.riotx.core.resources.ColorProvider + +@ColorInt +fun ColorProvider.getMessageTextColor(sendState: SendState): Int { + return when (sendState) { + // SendStates, in the classical order they will occur + SendState.UNKNOWN, + SendState.UNSENT -> getColorFromAttribute(R.attr.vctr_sending_message_text_color) + SendState.ENCRYPTING -> getColorFromAttribute(R.attr.vctr_encrypting_message_text_color) + SendState.SENDING -> getColorFromAttribute(R.attr.vctr_sending_message_text_color) + SendState.SENT, + SendState.SYNCED -> getColorFromAttribute(R.attr.vctr_message_text_color) + SendState.UNDELIVERED, + SendState.FAILED_UNKNOWN_DEVICES -> getColorFromAttribute(R.attr.vctr_unsent_message_text_color) + } +} diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml index 76cd365e..826ab5f1 100644 --- a/vector/src/main/res/values/theme_dark.xml +++ b/vector/src/main/res/values/theme_dark.xml @@ -93,7 +93,7 @@ @android:color/white @color/riot_primary_text_color_dark @color/accent_color_dark - ?android:textColorSecondary + @color/riotx_text_secondary_dark @color/vector_fuchsia_color @color/primary_color_light #CCC3C3C3 diff --git a/vector/src/main/res/values/theme_light.xml b/vector/src/main/res/values/theme_light.xml index f7f9a260..d55148e7 100644 --- a/vector/src/main/res/values/theme_light.xml +++ b/vector/src/main/res/values/theme_light.xml @@ -93,7 +93,7 @@ @color/riot_primary_text_color_light #FF61708b @color/accent_color_light - ?android:textColorSecondary + @color/riotx_text_secondary_light @color/vector_fuchsia_color @color/primary_color_light #333C3C3C From bd009caaf13c878e7a1a2075054d96896d59ef65 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 10:22:58 +0200 Subject: [PATCH 09/75] Code cleanup --- .../home/room/detail/timeline/item/AbsMessageItem.kt | 6 ++++-- .../home/room/detail/timeline/item/MessageFileItem.kt | 5 ----- .../room/detail/timeline/item/MessageImageVideoItem.kt | 5 ----- .../home/room/detail/timeline/item/MessageTextItem.kt | 5 ----- .../home/room/detail/timeline/item/RedactedMessageItem.kt | 7 ------- 5 files changed, 4 insertions(+), 24 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index f76601cd..cdcfceef 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -40,9 +40,11 @@ import im.vector.riotx.features.ui.getMessageTextColor abstract class AbsMessageItem : BaseEventItem() { - abstract val informationData: MessageInformationData + @EpoxyAttribute + lateinit var informationData: MessageInformationData - abstract val avatarRenderer: AvatarRenderer + @EpoxyAttribute + lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var colorProvider: ColorProvider diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt index 3d5fb452..e63881e2 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -25,7 +25,6 @@ import androidx.annotation.DrawableRes import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R -import im.vector.riotx.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageFileItem : AbsMessageItem() { @@ -36,10 +35,6 @@ abstract class MessageFileItem : AbsMessageItem() { @DrawableRes var iconRes: Int = 0 @EpoxyAttribute - override lateinit var informationData: MessageInformationData - @EpoxyAttribute - override lateinit var avatarRenderer: AvatarRenderer - @EpoxyAttribute var clickListener: View.OnClickListener? = null override fun bind(holder: Holder) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index bc47be74..b3cbaab0 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -22,7 +22,6 @@ import android.widget.ImageView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R -import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.riotx.features.media.ImageContentRenderer @@ -32,10 +31,6 @@ abstract class MessageImageVideoItem : AbsMessageItem() { @EpoxyAttribute var message: CharSequence? = null @EpoxyAttribute - override lateinit var avatarRenderer: AvatarRenderer - @EpoxyAttribute - override lateinit var informationData: MessageInformationData - @EpoxyAttribute var urlClickCallback: TimelineEventController.UrlClickCallback? = null private val mvmtMethod = BetterLinkMovementMethod.newInstance().also { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/RedactedMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/RedactedMessageItem.kt index 05967f00..f3979c54 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/RedactedMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/RedactedMessageItem.kt @@ -16,19 +16,12 @@ package im.vector.riotx.features.home.room.detail.timeline.item -import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R -import im.vector.riotx.features.home.AvatarRenderer @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class RedactedMessageItem : AbsMessageItem() { - @EpoxyAttribute - override lateinit var informationData: MessageInformationData - @EpoxyAttribute - override lateinit var avatarRenderer: AvatarRenderer - override fun getStubType(): Int = STUB_ID override fun shouldShowReactionAtBottom() = false From 41465450d8475d073aaf24bc37edd748deed6fec Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 10:45:08 +0200 Subject: [PATCH 10/75] Code cleanup --- .../detail/timeline/item/AbsMessageItem.kt | 4 ++-- .../detail/timeline/item/BaseEventItem.kt | 20 +++++-------------- .../room/detail/timeline/item/DefaultItem.kt | 6 ++---- .../detail/timeline/item/MergedHeaderItem.kt | 7 +++---- .../detail/timeline/item/MessageFileItem.kt | 9 +++------ .../timeline/item/MessageImageVideoItem.kt | 12 +++-------- .../detail/timeline/item/MessageTextItem.kt | 9 +++++---- .../room/detail/timeline/item/NoticeItem.kt | 5 ++--- .../timeline/item/RedactedMessageItem.kt | 10 ++++------ 9 files changed, 29 insertions(+), 53 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt index cdcfceef..88697db4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/AbsMessageItem.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import android.view.ViewStub import android.widget.ImageView import android.widget.TextView +import androidx.annotation.IdRes import androidx.constraintlayout.helper.widget.Flow import androidx.core.view.children import androidx.core.view.isGone @@ -165,8 +166,7 @@ abstract class AbsMessageItem : BaseEventItem() { textView?.setTextColor(colorProvider.getMessageTextColor(informationData.sendState)) } - abstract class Holder : BaseHolder() { - + abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) { val avatarImageView by bind(R.id.messageAvatarImageView) val memberNameView by bind(R.id.messageMemberNameView) val timeView by bind(R.id.messageTimeView) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt index 3a7d09e2..843f52b3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -26,6 +26,9 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.platform.CheckableView import im.vector.riotx.core.utils.DimensionUtils.dpToPx +/** + * Children must override getViewType() + */ abstract class BaseEventItem : VectorEpoxyModel() { var avatarStyle: AvatarStyle = AvatarStyle.SMALL @@ -43,31 +46,18 @@ abstract class BaseEventItem : VectorEpoxyModel holder.checkableBackground.isChecked = highlighted } - - override fun getViewType(): Int { - return getStubType() - } - - abstract fun getStubType(): Int - - - abstract class BaseHolder : VectorEpoxyHolder() { - + abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() { val leftGuideline by bind(R.id.messageStartGuideline) val checkableBackground by bind(R.id.messageSelectedBackground) - @IdRes - abstract fun getStubId(): Int - override fun bindView(itemView: View) { super.bindView(itemView) inflateStub() } private fun inflateStub() { - view.findViewById(getStubId()).inflate() + view.findViewById(stubId).inflate() } - } companion object { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt index cc50493a..0b30facf 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/DefaultItem.kt @@ -31,11 +31,9 @@ abstract class DefaultItem : BaseEventItem() { holder.messageView.text = text } - override fun getStubType(): Int = STUB_ID - - class Holder : BaseHolder() { - override fun getStubId(): Int = STUB_ID + override fun getViewType() = STUB_ID + class Holder : BaseHolder(STUB_ID) { val messageView by bind(R.id.stateMessageView) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt index 0ad13fcf..4f26f9bb 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MergedHeaderItem.kt @@ -46,7 +46,7 @@ data class MergedHeaderItem(private val isCollapsed: Boolean, return Holder() } - override fun getStubType(): Int = STUB_ID + override fun getViewType() = STUB_ID override fun bind(holder: Holder) { super.bind(holder) @@ -84,8 +84,7 @@ data class MergedHeaderItem(private val isCollapsed: Boolean, val avatarUrl: String? ) - class Holder : BaseHolder() { - override fun getStubId(): Int = STUB_ID + class Holder : BaseHolder(STUB_ID) { val expandView by bind(R.id.itemMergedExpandTextView) val summaryView by bind(R.id.itemMergedSummaryTextView) @@ -95,6 +94,6 @@ data class MergedHeaderItem(private val isCollapsed: Boolean, } companion object { - private val STUB_ID = R.id.messageContentMergedheaderStub + private const val STUB_ID = R.id.messageContentMergedheaderStub } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt index e63881e2..45e57b59 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageFileItem.kt @@ -46,19 +46,16 @@ abstract class MessageFileItem : AbsMessageItem() { holder.filenameView.paintFlags = (holder.filenameView.paintFlags or Paint.UNDERLINE_TEXT_FLAG) } - override fun getStubType(): Int = STUB_ID - - class Holder : AbsMessageItem.Holder() { - override fun getStubId(): Int = STUB_ID + override fun getViewType() = STUB_ID + class Holder : AbsMessageItem.Holder(STUB_ID) { val fileLayout by bind(R.id.messageFileLayout) val fileImageView by bind(R.id.messageFileImageView) val filenameView by bind(R.id.messageFilenameView) - } companion object { - private val STUB_ID = R.id.messageContentFileStub + private const val STUB_ID = R.id.messageContentFileStub } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index b3cbaab0..d551e44c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -57,23 +57,17 @@ abstract class MessageImageVideoItem : AbsMessageItem(R.id.messageMediaUploadProgressLayout) val imageView by bind(R.id.messageThumbnailView) val playContentView by bind(R.id.messageMediaPlayView) val mediaContentView by bind(R.id.messageContentMedia) - } - companion object { - private val STUB_ID = R.id.messageContentMediaStub + private const val STUB_ID = R.id.messageContentMediaStub } - } \ No newline at end of file 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 7b7d3cbf..b5074882 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 @@ -85,12 +85,13 @@ abstract class MessageTextItem : AbsMessageItem() { } } - override fun getStubType(): Int = R.id.messageContentTextStub + override fun getViewType() = STUB_ID - class Holder : AbsMessageItem.Holder() { + class Holder : AbsMessageItem.Holder(STUB_ID) { val messageView by bind(R.id.messageTextView) - override fun getStubId(): Int = R.id.messageContentTextStub - } + companion object { + private const val STUB_ID = R.id.messageContentTextStub + } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt index 08645357..2879073f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/NoticeItem.kt @@ -57,10 +57,9 @@ abstract class NoticeItem : BaseEventItem() { holder.view.setOnLongClickListener(longClickListener) } - override fun getStubType(): Int = STUB_ID + override fun getViewType() = STUB_ID - class Holder : BaseHolder() { - override fun getStubId(): Int = STUB_ID + class Holder : BaseHolder(STUB_ID) { val avatarImageView by bind(R.id.itemNoticeAvatarView) val noticeTextView by bind(R.id.itemNoticeTextView) } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/RedactedMessageItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/RedactedMessageItem.kt index f3979c54..88e2be2b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/RedactedMessageItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/RedactedMessageItem.kt @@ -22,15 +22,13 @@ import im.vector.riotx.R @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class RedactedMessageItem : AbsMessageItem() { - override fun getStubType(): Int = STUB_ID + override fun getViewType() = STUB_ID override fun shouldShowReactionAtBottom() = false - class Holder : AbsMessageItem.Holder() { - override fun getStubId(): Int = STUB_ID - } + class Holder : AbsMessageItem.Holder(STUB_ID) companion object { - private val STUB_ID = R.id.messageContentRedactedStub + private const val STUB_ID = R.id.messageContentRedactedStub } -} \ No newline at end of file +} From f9885fd04c7e433baf50b71e0c59d1606a6cf906 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 11:38:55 +0200 Subject: [PATCH 11/75] Update CHANGES.md --- CHANGES.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0e7f29e6..5a16a50f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,9 +3,7 @@ Changes in RiotX 0.1.0 (2019-07-11) First release! - - - +Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-android-b17952e8f771 ======================================================= From fe692063406a1246f5c4905f59fce1cb76ef22b0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 11:39:26 +0200 Subject: [PATCH 12/75] Prepare next release --- CHANGES.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5a16a50f..3d9ce454 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,26 @@ +Changes in RiotX 0.2.1 (2019-XX-XX) +=================================================== + +Features: + - + +Improvements: + - + +Other changes: + - + +Bugfix: + - + +Translations: + - + +Build: + - + + + Changes in RiotX 0.1.0 (2019-07-11) =================================================== From 3625c462f0612b8d47629970c5b3f0bb94492ba4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 13:51:05 +0200 Subject: [PATCH 13/75] Click on redacted event --- CHANGES.md | 2 +- .../riotx/core/extensions/TimelineEvent.kt | 2 +- .../timeline/action/MessageMenuViewModel.kt | 82 ++++++++++--------- .../timeline/factory/MessageItemFactory.kt | 8 ++ 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3d9ce454..2b212934 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features: - Improvements: - - + - Handle click on redacted events: view source and create permalink Other changes: - diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt b/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt index c82631c0..db171300 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/TimelineEvent.kt @@ -21,5 +21,5 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent fun TimelineEvent.canReact(): Boolean { // Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment - return root.getClearType() == EventType.MESSAGE && sendState.isSent() + return root.getClearType() == EventType.MESSAGE && sendState.isSent() && !root.isRedacted() } 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 21f5da52..b49731d9 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 @@ -126,51 +126,53 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M } //TODO is downloading attachement? - 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)) - } - - if (canEdit(event, session.sessionParams.credentials.userId)) { - this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId)) - } - - if (canRedact(event, session.sessionParams.credentials.userId)) { - this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId)) - } - - if (canQuote(event, messageContent)) { - this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, eventId)) - } - - if (canViewReactions(event)) { - this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, informationData)) - } - - if (canShare(type)) { - if (messageContent is MessageImageContent) { - this.add( - SimpleAction(ACTION_SHARE, - R.string.share, R.drawable.ic_share, - session.contentUrlResolver().resolveFullSize(messageContent.url)) - ) + 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)) + } + + if (canEdit(event, session.sessionParams.credentials.userId)) { + this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId)) + } + + if (canRedact(event, session.sessionParams.credentials.userId)) { + this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId)) + } + + if (canQuote(event, messageContent)) { + this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, eventId)) + } + + if (canViewReactions(event)) { + this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, informationData)) + } + + if (canShare(type)) { + if (messageContent is MessageImageContent) { + this.add( + SimpleAction(ACTION_SHARE, + R.string.share, R.drawable.ic_share, + session.contentUrlResolver().resolveFullSize(messageContent.url)) + ) + } + //TODO } - //TODO - } - if (event.sendState == SendState.SENT) { + if (event.sendState == SendState.SENT) { - //TODO Can be redacted + //TODO Can be redacted - //TODO sent by me or sufficient power level + //TODO sent by me or sufficient power level + } } this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, event.root.toContentStringWithIndent())) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt index d8f1c602..3f5c41bf 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -417,6 +417,14 @@ class MessageItemFactory @Inject constructor( .informationData(informationData) .highlighted(highlight) .avatarCallback(callback) + .cellClickListener( + DebouncedClickListener(View.OnClickListener { view -> + callback?.onEventCellClicked(informationData, null, view) + })) + .longClickListener { view -> + return@longClickListener callback?.onEventLongClicked(informationData, null, view) + ?: false + } } private fun linkifyBody(body: CharSequence, callback: TimelineEventController.Callback?): CharSequence { From 34d14eb3046a8ec40c3e56e6f3fb26ee404d2826 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 12:46:02 +0200 Subject: [PATCH 14/75] Fix regression on permalink click --- CHANGES.md | 2 +- .../im/vector/matrix/android/api/permalinks/MatrixLinkify.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2b212934..411a6c83 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,7 +11,7 @@ Other changes: - Bugfix: - - + - Fix regression on permalink click Translations: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt index cfe5a051..fb3dbcc2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt @@ -42,7 +42,7 @@ object MatrixLinkify { hasMatch = true val startPos = match.range.first if (startPos == 0 || text[startPos - 1] != '/') { - val endPos = match.range.last + val endPos = match.range.last + 1 val url = text.substring(match.range) val span = MatrixPermalinkSpan(url, callback) spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) @@ -51,5 +51,5 @@ object MatrixLinkify { } return hasMatch } - + } \ 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 15/75] 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 e2ea76f871a27725a1e76cb5e9f87b3eba5c3dc0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 16:48:35 +0200 Subject: [PATCH 16/75] Fix crash reported by PlayStore --- .../riotx/features/notifications/NotificationDrawerManager.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 763aea5e..97d3a67c 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 private fun refreshNotificationDrawerBg() { Timber.w("refreshNotificationDrawerBg()") - val session = activeSessionHolder.getActiveSession() + val session = activeSessionHolder.getSafeActiveSession() ?: return + val user = session.getUser(session.sessionParams.credentials.userId) val myUserDisplayName = user?.displayName ?: session.sessionParams.credentials.userId val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE) From e6dd1fbfecfe84e70c5f18560f2645d63431b85e Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 13 Jul 2019 15:18:16 +0100 Subject: [PATCH 17/75] 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 18/75] 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 222201cc645e69a125b80da0f27268780c5b2430 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 14:40:35 +0200 Subject: [PATCH 19/75] Fix crash observe on the PlayStore (#341) --- CHANGES.md | 1 + .../NotificationDrawerManager.kt | 34 +++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 411a6c83..a01b7416 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Other changes: Bugfix: - Fix regression on permalink click + - Fix crash reported by the PlayStore (#341) Translations: - 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 97d3a67c..e145a4fb 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 @@ -121,11 +121,13 @@ class NotificationDrawerManager @Inject constructor(private val context: Context Timber.v("clearMessageEventOfRoom $roomId") if (roomId != null) { - eventList.removeAll { e -> - if (e is NotifiableMessageEvent) { - return@removeAll e.roomId == roomId + synchronized(eventList) { + eventList.removeAll { e -> + if (e is NotifiableMessageEvent) { + return@removeAll e.roomId == roomId + } + return@removeAll false } - return@removeAll false } NotificationUtils.cancelNotificationMessage(context, roomId, ROOM_MESSAGES_NOTIFICATION_ID) } @@ -433,18 +435,20 @@ class NotificationDrawerManager @Inject constructor(private val context: Context fun persistInfo() { - if (eventList.isEmpty()) { - deleteCachedRoomNotifications(context) - return - } - try { - val file = File(context.applicationContext.cacheDir, ROOMS_NOTIFICATIONS_FILE_NAME) - if (!file.exists()) file.createNewFile() - FileOutputStream(file).use { - SecretStoringUtils.securelyStoreObject(eventList, "notificationMgr", it, this.context) + synchronized(eventList) { + if (eventList.isEmpty()) { + deleteCachedRoomNotifications(context) + return + } + try { + val file = File(context.applicationContext.cacheDir, ROOMS_NOTIFICATIONS_FILE_NAME) + if (!file.exists()) file.createNewFile() + FileOutputStream(file).use { + SecretStoringUtils.securelyStoreObject(eventList, "notificationMgr", it, this.context) + } + } catch (e: Throwable) { + Timber.e(e, "## Failed to save cached notification info") } - } catch (e: Throwable) { - Timber.e(e, "## Failed to save cached notification info") } } From 0289d2ee87b28b9e5f699954d0c2c4a76aac05c8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Jul 2019 14:50:33 +0200 Subject: [PATCH 20/75] Simpler code --- .../notifications/NotificationDrawerManager.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 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 e145a4fb..72a9e1e3 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 @@ -60,7 +60,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context //The first time the notification drawer is refreshed, we force re-render of all notifications private var firstTime = true - private var eventList = loadEventInfo() + private val eventList = loadEventInfo() private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size) @@ -123,10 +123,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context if (roomId != null) { synchronized(eventList) { eventList.removeAll { e -> - if (e is NotifiableMessageEvent) { - return@removeAll e.roomId == roomId - } - return@removeAll false + e is NotifiableMessageEvent && e.roomId == roomId } } NotificationUtils.cancelNotificationMessage(context, roomId, ROOM_MESSAGES_NOTIFICATION_ID) @@ -152,7 +149,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context fun homeActivityDidResume(matrixID: String?) { synchronized(eventList) { eventList.removeAll { e -> - return@removeAll e !is NotifiableMessageEvent //messages are cleared when entering room + // messages are cleared when entering room + e !is NotifiableMessageEvent } } } @@ -160,10 +158,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context fun clearMemberShipNotificationForRoom(roomId: String) { synchronized(eventList) { eventList.removeAll { e -> - if (e is InviteNotifiableEvent) { - return@removeAll e.roomId == roomId - } - return@removeAll false + e is InviteNotifiableEvent && e.roomId == roomId } } } From 25f1d21bc709c9a51624cf73d168dc102145b723 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 15 Jul 2019 14:26:13 +0200 Subject: [PATCH 21/75] Edit history Get history from API cleaning Updated change log Missing copyrights Code review cleaning --- CHANGES.md | 2 +- build.gradle | 6 + .../room/model/relation/RelationService.kt | 9 + .../internal/network/NetworkConstants.kt | 7 +- .../android/internal/session/room/RoomAPI.kt | 17 +- .../internal/session/room/RoomFactory.kt | 6 +- .../internal/session/room/RoomModule.kt | 3 + .../room/relation/DefaultRelationService.kt | 11 +- .../room/relation/FetchEditHistoryTask.kt | 48 ++++++ .../room/relation/RelationsResponse.kt | 27 +++ vector/build.gradle | 2 + .../src/main/assets/open_source_licenses.html | 7 + .../vector/riotx/core/di/ScreenComponent.kt | 7 +- .../vector/riotx/core/di/ViewModelModule.kt | 11 +- .../vector/riotx/core/ui/list/GenericItem.kt | 2 +- .../riotx/core/ui/list/GenericItemHeader.kt | 42 +++++ .../riotx/core/ui/list/GenericLoaderItem.kt | 20 +++ .../home/room/detail/RoomDetailActions.kt | 1 - .../home/room/detail/RoomDetailFragment.kt | 11 +- .../home/room/detail/RoomDetailViewModel.kt | 17 -- .../action/ViewEditHistoryBottomSheet.kt | 93 +++++++++++ .../action/ViewEditHistoryEpoxyController.kt | 155 ++++++++++++++++++ .../action/ViewEditHistoryViewModel.kt | 90 ++++++++++ .../action/ViewReactionBottomSheet.kt | 16 +- .../action/ViewReactionsEpoxyController.kt | 41 ++++- ... => bottom_sheet_epoxylist_with_title.xml} | 15 +- .../main/res/layout/item_generic_header.xml | 14 ++ .../main/res/layout/item_generic_loader.xml | 6 + vector/src/main/res/values/strings_riotX.xml | 4 + 29 files changed, 613 insertions(+), 77 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/RelationsResponse.kt create mode 100644 vector/src/main/java/im/vector/riotx/core/ui/list/GenericItemHeader.kt create mode 100644 vector/src/main/java/im/vector/riotx/core/ui/list/GenericLoaderItem.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryBottomSheet.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt rename vector/src/main/res/layout/{bottom_sheet_display_reactions.xml => bottom_sheet_epoxylist_with_title.xml} (71%) create mode 100644 vector/src/main/res/layout/item_generic_header.xml create mode 100644 vector/src/main/res/layout/item_generic_loader.xml diff --git a/CHANGES.md b/CHANGES.md index a01b7416..e4682292 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.2.1 (2019-XX-XX) =================================================== Features: - - + - Message Editing: View edit history Improvements: - Handle click on redacted events: view source and create permalink diff --git a/build.gradle b/build.gradle index 91415088..4fa468ab 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,12 @@ allprojects { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } google() jcenter() + maven { + url 'https://repo.adobe.com/nexus/content/repositories/public/' + content { + includeGroupByRegex "diff_match_patch" + } + } } } 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 81d7ddd4..7ffbd5f1 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 @@ -16,6 +16,8 @@ package im.vector.matrix.android.api.session.room.model.relation import androidx.lifecycle.LiveData +import im.vector.matrix.android.api.MatrixCallback +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.timeline.TimelineEvent import im.vector.matrix.android.api.util.Cancelable @@ -78,6 +80,11 @@ interface RelationService { newBodyAutoMarkdown: Boolean, compatibilityBodyText: String = "* $newBodyText"): Cancelable + /** + * Get's the edit history of the given event + */ + fun fetchEditHistory(eventId: String, callback: MatrixCallback>) + /** * Reply to an event in the timeline (must be in same room) @@ -91,4 +98,6 @@ interface RelationService { autoMarkdown: Boolean = false): Cancelable? fun getEventSummaryLive(eventId: String): LiveData + + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt index d6d2d9cc..cbd4d0c6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConstants.kt @@ -18,7 +18,8 @@ package im.vector.matrix.android.internal.network internal object NetworkConstants { - const val URI_API_PREFIX_PATH = "_matrix/client/" - const val URI_API_PREFIX_PATH_R0 = "_matrix/client/r0/" + private const val URI_API_PREFIX_PATH = "_matrix/client" + const val URI_API_PREFIX_PATH_R0 = "$URI_API_PREFIX_PATH/r0/" + const val URI_API_PREFIX_PATH_UNSTABLE = "$URI_API_PREFIX_PATH/unstable/" -} \ No newline at end of file +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index af263970..361a935d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -22,10 +22,11 @@ import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse +import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody -import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol +import im.vector.matrix.android.internal.session.room.relation.RelationsResponse import im.vector.matrix.android.internal.session.room.send.SendResponse import im.vector.matrix.android.internal.session.room.timeline.EventContextResponse import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse @@ -195,6 +196,20 @@ internal interface RoomAPI { @Body content: Content? ): Call + + /** + * Paginate relations for event based in normal topological order + * + * @param relationType filter for this relation type + * @param eventType filter for this event type + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}") + fun getRelations(@Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Path("relationType") relationType: String, + @Path("eventType") eventType: String + ): Call + /** * Join the given room. * diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt index 143ef60b..98cf872b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomFactory.kt @@ -30,8 +30,8 @@ import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRo import im.vector.matrix.android.internal.session.room.read.DefaultReadService import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.relation.DefaultRelationService +import im.vector.matrix.android.internal.session.room.relation.FetchEditHistoryTask import im.vector.matrix.android.internal.session.room.relation.FindReactionEventForUndoTask -import im.vector.matrix.android.internal.session.room.relation.UpdateQuickReactionTask import im.vector.matrix.android.internal.session.room.send.DefaultSendService import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.state.DefaultStateService @@ -56,7 +56,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, private val setReadMarkersTask: SetReadMarkersTask, private val cryptoService: CryptoService, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, - private val updateQuickReactionTask: UpdateQuickReactionTask, + private val fetchEditHistoryTask: FetchEditHistoryTask, private val joinRoomTask: JoinRoomTask, private val leaveRoomTask: LeaveRoomTask) { @@ -67,7 +67,7 @@ internal class RoomFactory @Inject constructor(private val context: Context, val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask) val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, credentials) val relationService = DefaultRelationService(context, - credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, monarchy, taskExecutor) + credentials, roomId, eventFactory, cryptoService, findReactionEventForUndoTask, fetchEditHistoryTask, monarchy, taskExecutor) return DefaultRoom( roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 09322e6a..942239ea 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -142,4 +142,7 @@ internal abstract class RoomModule { @Binds abstract fun bindFileService(fileService: DefaultFileService): FileService + + @Binds + abstract fun bindFetchEditHistoryTask(editHistoryTask: DefaultFetchEditHistoryTask): FetchEditHistoryTask } 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 3eb1c066..1b487d96 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 @@ -53,6 +53,7 @@ internal class DefaultRelationService @Inject constructor(private val context: C private val eventFactory: LocalEchoEventFactory, private val cryptoService: CryptoService, private val findReactionEventForUndoTask: FindReactionEventForUndoTask, + private val fetchEditHistoryTask: FetchEditHistoryTask, private val monarchy: Monarchy, private val taskExecutor: TaskExecutor) : RelationService { @@ -131,6 +132,13 @@ internal class DefaultRelationService @Inject constructor(private val context: C } + override fun fetchEditHistory(eventId: String, callback: MatrixCallback>) { + val params = FetchEditHistoryTask.Params(roomId, eventId) + fetchEditHistoryTask.configureWith(params) + .dispatchTo(callback) + .executeBy(taskExecutor) + } + override fun replyToMessage(eventReplied: TimelineEvent, replyText: String, autoMarkdown: Boolean): Cancelable? { val event = eventFactory.createReplyTextEvent(roomId, eventReplied, replyText, autoMarkdown)?.also { saveLocalEcho(it) @@ -169,7 +177,8 @@ internal class DefaultRelationService @Inject constructor(private val context: C EventAnnotationsSummaryEntity.where(realm, eventId) } return Transformations.map(liveEntity) { realmResults -> - realmResults.firstOrNull()?.asDomain() ?: EventAnnotationsSummary(eventId, emptyList(), null) + realmResults.firstOrNull()?.asDomain() + ?: EventAnnotationsSummary(eventId, emptyList(), null) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt new file mode 100644 index 00000000..e891ece8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/FetchEditHistoryTask.kt @@ -0,0 +1,48 @@ +/* + * 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.relation + +import arrow.core.Try +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.RelationType +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task +import javax.inject.Inject + + +internal interface FetchEditHistoryTask : Task> { + + data class Params( + val roomId: String, + val eventId: String + ) +} + + +internal class DefaultFetchEditHistoryTask @Inject constructor( + private val roomAPI: RoomAPI +) : FetchEditHistoryTask { + + override suspend fun execute(params: FetchEditHistoryTask.Params): Try> { + return executeRequest { + apiCall = roomAPI.getRelations(params.roomId, params.eventId, RelationType.REPLACE, EventType.MESSAGE) + }.map { resp -> + resp.chunks + } + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/RelationsResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/RelationsResponse.kt new file mode 100644 index 00000000..5d39e81c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/RelationsResponse.kt @@ -0,0 +1,27 @@ +/* + * 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.relation + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.Event + +@JsonClass(generateAdapter = true) +internal data class RelationsResponse( + @Json(name = "chunk") val chunks: List, + @Json(name = "next_batch") val nextBatch: String?, + @Json(name = "prev_batch") val prevBatch: String? +) \ No newline at end of file diff --git a/vector/build.gradle b/vector/build.gradle index ace3135a..d7332c47 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -254,6 +254,8 @@ dependencies { exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' } + implementation 'diff_match_patch:diff_match_patch:current' + // TESTS testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html index cec7c718..9e87d466 100755 --- a/vector/src/main/assets/open_source_licenses.html +++ b/vector/src/main/assets/open_source_licenses.html @@ -344,6 +344,13 @@ SOFTWARE.
Copyright (c) 2018, Jaisel Rahman +
  • + diff-match-patch +
    + Copyright 2018 The diff-match-patch Authors. https://github.com/google/diff-match-patch +
  • + +
     Apache License
    diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    index 06f65129..611ee171 100644
    --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    @@ -35,10 +35,7 @@ import im.vector.riotx.features.crypto.verification.SASVerificationIncomingFragm
     import im.vector.riotx.features.home.*
     import im.vector.riotx.features.home.group.GroupListFragment
     import im.vector.riotx.features.home.room.detail.RoomDetailFragment
    -import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
    -import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuFragment
    -import im.vector.riotx.features.home.room.detail.timeline.action.QuickReactionFragment
    -import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
    +import im.vector.riotx.features.home.room.detail.timeline.action.*
     import im.vector.riotx.features.home.room.list.RoomListFragment
     import im.vector.riotx.features.invite.VectorInviteView
     import im.vector.riotx.features.login.LoginActivity
    @@ -93,6 +90,8 @@ interface ScreenComponent {
     
         fun inject(viewReactionBottomSheet: ViewReactionBottomSheet)
     
    +    fun inject(viewEditHistoryBottomSheet: ViewEditHistoryBottomSheet)
    +
         fun inject(messageMenuFragment: MessageMenuFragment)
     
         fun inject(vectorSettingsActivity: VectorSettingsActivity)
    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 234d4a0c..37abde20 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
    @@ -29,11 +29,7 @@ import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsVie
     import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsViewModel_AssistedFactory
     import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupSharedViewModel
     import im.vector.riotx.features.crypto.verification.SasVerificationViewModel
    -import im.vector.riotx.features.home.HomeActivityViewModel
    -import im.vector.riotx.features.home.HomeActivityViewModel_AssistedFactory
    -import im.vector.riotx.features.home.HomeDetailViewModel
    -import im.vector.riotx.features.home.HomeDetailViewModel_AssistedFactory
    -import im.vector.riotx.features.home.HomeNavigationViewModel
    +import im.vector.riotx.features.home.*
     import im.vector.riotx.features.home.group.GroupListViewModel
     import im.vector.riotx.features.home.group.GroupListViewModel_AssistedFactory
     import im.vector.riotx.features.home.room.detail.RoomDetailViewModel
    @@ -59,7 +55,7 @@ import im.vector.riotx.features.workers.signout.SignOutViewModel
     
     @Module
     interface ViewModelModule {
    -    
    +
     
         @Binds
         fun bindViewModelFactory(factory: VectorViewModelFactory): ViewModelProvider.Factory
    @@ -156,6 +152,9 @@ interface ViewModelModule {
         @Binds
         fun bindViewReactionViewModelFactory(factory: ViewReactionViewModel_AssistedFactory): ViewReactionViewModel.Factory
     
    +    @Binds
    +    fun bindViewEditHistoryViewModelFactory(factory: ViewEditHistoryViewModel_AssistedFactory): ViewEditHistoryViewModel.Factory
    +
         @Binds
         fun bindCreateRoomViewModelFactory(factory: CreateRoomViewModel_AssistedFactory): CreateRoomViewModel.Factory
     
    diff --git a/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItem.kt b/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItem.kt
    index ebaeb2d3..f0a62ccd 100644
    --- a/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItem.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItem.kt
    @@ -51,7 +51,7 @@ abstract class GenericItem : VectorEpoxyModel() {
         var title: String? = null
     
         @EpoxyAttribute
    -    var description: String? = null
    +    var description: CharSequence? = null
     
         @EpoxyAttribute
         var style: STYLE = STYLE.NORMAL_TEXT
    diff --git a/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItemHeader.kt b/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItemHeader.kt
    new file mode 100644
    index 00000000..3c9ce20d
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/core/ui/list/GenericItemHeader.kt
    @@ -0,0 +1,42 @@
    +/*
    + * 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.core.ui.list
    +
    +import android.widget.TextView
    +import com.airbnb.epoxy.EpoxyAttribute
    +import com.airbnb.epoxy.EpoxyModelClass
    +import im.vector.riotx.R
    +import im.vector.riotx.core.epoxy.VectorEpoxyHolder
    +import im.vector.riotx.core.epoxy.VectorEpoxyModel
    +import im.vector.riotx.core.extensions.setTextOrHide
    +
    +/**
    + * A generic list item header left aligned with notice color.
    + */
    +@EpoxyModelClass(layout = R.layout.item_generic_header)
    +abstract class GenericItemHeader : VectorEpoxyModel() {
    +
    +    @EpoxyAttribute
    +    var text: String? = null
    +
    +    override fun bind(holder: Holder) {
    +        holder.text.setTextOrHide(text)
    +    }
    +
    +    class Holder : VectorEpoxyHolder() {
    +        val text by bind(R.id.itemGenericHeaderText)
    +    }
    +}
    \ No newline at end of file
    diff --git a/vector/src/main/java/im/vector/riotx/core/ui/list/GenericLoaderItem.kt b/vector/src/main/java/im/vector/riotx/core/ui/list/GenericLoaderItem.kt
    new file mode 100644
    index 00000000..56daca22
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/core/ui/list/GenericLoaderItem.kt
    @@ -0,0 +1,20 @@
    +package im.vector.riotx.core.ui.list
    +
    +import com.airbnb.epoxy.EpoxyModelClass
    +import im.vector.riotx.R
    +import im.vector.riotx.core.epoxy.VectorEpoxyHolder
    +import im.vector.riotx.core.epoxy.VectorEpoxyModel
    +
    +
    +/**
    + * A generic list item header left aligned with notice color.
    + */
    +@EpoxyModelClass(layout = R.layout.item_generic_loader)
    +abstract class GenericLoaderItem : VectorEpoxyModel() {
    +
    +    //Maybe/Later add some style configuration, SMALL/BIG ?
    +
    +    override fun bind(holder: Holder) {}
    +
    +    class Holder : VectorEpoxyHolder()
    +}
    \ No newline at end of file
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt
    index d52b16ca..ace0802e 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActions.kt
    @@ -32,7 +32,6 @@ sealed class RoomDetailActions {
         data class RedactAction(val targetEventId: String, val reason: String? = "") : RoomDetailActions()
         data class UndoReaction(val targetEventId: String, val key: String, val reason: String? = "") : RoomDetailActions()
         data class UpdateQuickReactAction(val targetEventId: String, val selectedReaction: String, val add: Boolean) : RoomDetailActions()
    -    data class ShowEditHistoryAction(val event: String, val editAggregatedSummary: EditAggregatedSummary) : RoomDetailActions()
         data class NavigateToEvent(val eventId: String, val position: Int?) : RoomDetailActions()
         data class DownloadFile(val eventId: String, val messageFileContent: MessageFileContent) : RoomDetailActions()
         object AcceptInvite : RoomDetailActions()
    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 f611fe99..943b6165 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
    @@ -85,10 +85,7 @@ import im.vector.riotx.features.home.room.detail.composer.TextComposerView
     import im.vector.riotx.features.home.room.detail.composer.TextComposerViewModel
     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.ActionsHandler
    -import im.vector.riotx.features.home.room.detail.timeline.action.MessageActionsBottomSheet
    -import im.vector.riotx.features.home.room.detail.timeline.action.MessageMenuViewModel
    -import im.vector.riotx.features.home.room.detail.timeline.action.ViewReactionBottomSheet
    +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.html.EventHtmlRenderer
    @@ -666,10 +663,8 @@ class RoomDetailFragment :
         }
     
         override fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) {
    -        editAggregatedSummary?.also {
    -            roomDetailViewModel.process(RoomDetailActions.ShowEditHistoryAction(informationData.eventId, it))
    -        }
    -
    +        ViewEditHistoryBottomSheet.newInstance(roomDetailArgs.roomId, informationData)
    +                .show(requireActivity().supportFragmentManager, "DISPLAY_EDITS")
         }
     // AutocompleteUserPresenter.Callback
     
    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..36b989fe 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
    @@ -114,7 +114,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
                 is RoomDetailActions.RedactAction           -> handleRedactEvent(action)
                 is RoomDetailActions.UndoReaction           -> handleUndoReact(action)
                 is RoomDetailActions.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
    -            is RoomDetailActions.ShowEditHistoryAction  -> handleShowEditHistoryReaction(action)
                 is RoomDetailActions.EnterEditMode          -> handleEditAction(action)
                 is RoomDetailActions.EnterQuoteMode         -> handleQuoteAction(action)
                 is RoomDetailActions.EnterReplyMode         -> handleReplyAction(action)
    @@ -309,22 +308,6 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
             return finalText
         }
     
    -    private fun handleShowEditHistoryReaction(action: RoomDetailActions.ShowEditHistoryAction) {
    -        //TODO temporary implementation
    -        val lastReplace = action.editAggregatedSummary.sourceEvents.lastOrNull()?.let {
    -            room.getTimeLineEvent(it)
    -        } ?: return
    -
    -        val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
    -        _nonBlockingPopAlert.postValue(LiveEvent(
    -                Pair(R.string.last_edited_info_message, listOf(
    -                        lastReplace.getDisambiguatedDisplayName(),
    -                        dateFormat.format(Date(lastReplace.root.originServerTs ?: 0)))
    -                ))
    -        )
    -    }
    -
    -
         private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
             _sendMessageResultLiveData.postValue(LiveEvent(SendMessageResult.SlashCommandHandled))
     
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryBottomSheet.kt
    new file mode 100644
    index 00000000..aefbde43
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryBottomSheet.kt
    @@ -0,0 +1,93 @@
    +/*
    + * 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.timeline.action
    +
    +import android.os.Bundle
    +import android.view.LayoutInflater
    +import android.view.View
    +import android.view.ViewGroup
    +import android.widget.LinearLayout
    +import androidx.recyclerview.widget.DividerItemDecoration
    +import butterknife.BindView
    +import butterknife.ButterKnife
    +import com.airbnb.epoxy.EpoxyRecyclerView
    +import com.airbnb.mvrx.MvRx
    +import com.airbnb.mvrx.fragmentViewModel
    +import com.airbnb.mvrx.withState
    +import im.vector.riotx.R
    +import im.vector.riotx.core.di.ScreenComponent
    +import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
    +import im.vector.riotx.features.html.EventHtmlRenderer
    +import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.*
    +import javax.inject.Inject
    +
    +
    +/**
    + * Bottom sheet displaying list of edits for a given event ordered by timestamp
    + */
    +class ViewEditHistoryBottomSheet : VectorBaseBottomSheetDialogFragment() {
    +
    +    private val viewModel: ViewEditHistoryViewModel by fragmentViewModel(ViewEditHistoryViewModel::class)
    +
    +    @Inject lateinit var viewEditHistoryViewModelFactory: ViewEditHistoryViewModel.Factory
    +    @Inject lateinit var eventHtmlRenderer: EventHtmlRenderer
    +
    +    @BindView(R.id.bottom_sheet_display_reactions_list)
    +    lateinit var epoxyRecyclerView: EpoxyRecyclerView
    +
    +    private val epoxyController by lazy {
    +        ViewEditHistoryEpoxyController(requireContext(), viewModel.timelineDateFormatter, eventHtmlRenderer)
    +    }
    +
    +    override fun injectWith(screenComponent: ScreenComponent) {
    +        screenComponent.inject(this)
    +    }
    +
    +    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    +        val view = inflater.inflate(R.layout.bottom_sheet_epoxylist_with_title, container, false)
    +        ButterKnife.bind(this, view)
    +        return view
    +    }
    +
    +    override fun onActivityCreated(savedInstanceState: Bundle?) {
    +        super.onActivityCreated(savedInstanceState)
    +        epoxyRecyclerView.setController(epoxyController)
    +        val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
    +                LinearLayout.VERTICAL)
    +        epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
    +        bottomSheetTitle.text = context?.getString(R.string.message_edits)
    +    }
    +
    +
    +    override fun invalidate() = withState(viewModel) {
    +        epoxyController.setData(it)
    +    }
    +
    +    companion object {
    +        fun newInstance(roomId: String, informationData: MessageInformationData): ViewEditHistoryBottomSheet {
    +            val args = Bundle()
    +            val parcelableArgs = TimelineEventFragmentArgs(
    +                    informationData.eventId,
    +                    roomId,
    +                    informationData
    +            )
    +            args.putParcelable(MvRx.KEY_ARG, parcelableArgs)
    +            return ViewEditHistoryBottomSheet().apply { arguments = args }
    +
    +        }
    +    }
    +}
    +
    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
    new file mode 100644
    index 00000000..4ae62fbd
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryEpoxyController.kt
    @@ -0,0 +1,155 @@
    +/*
    + * 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.timeline.action
    +
    +import android.content.Context
    +import android.text.Spannable
    +import android.text.format.DateUtils
    +import androidx.core.content.ContextCompat
    +import com.airbnb.epoxy.TypedEpoxyController
    +import com.airbnb.mvrx.Fail
    +import com.airbnb.mvrx.Incomplete
    +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.riotx.R
    +import im.vector.riotx.core.extensions.localDateTime
    +import im.vector.riotx.core.ui.list.genericFooterItem
    +import im.vector.riotx.core.ui.list.genericItem
    +import im.vector.riotx.core.ui.list.genericItemHeader
    +import im.vector.riotx.core.ui.list.genericLoaderItem
    +import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
    +import im.vector.riotx.features.html.EventHtmlRenderer
    +import me.gujun.android.span.span
    +import name.fraser.neil.plaintext.diff_match_patch
    +import timber.log.Timber
    +import java.util.*
    +
    +/**
    + * Epoxy controller for reaction event list
    + */
    +class ViewEditHistoryEpoxyController(private val context: Context,
    +                                     val timelineDateFormatter: TimelineDateFormatter,
    +                                     val eventHtmlRenderer: EventHtmlRenderer) : TypedEpoxyController() {
    +
    +    override fun buildModels(state: ViewEditHistoryViewState) {
    +        when (state.editList) {
    +            is Incomplete -> {
    +                genericLoaderItem {
    +                    id("Spinner")
    +                }
    +            }
    +            is Fail       -> {
    +                genericFooterItem {
    +                    id("failure")
    +                    text(context.getString(R.string.unknown_error))
    +                }
    +            }
    +            is Success    -> {
    +                state.editList()?.let { renderEvents(it) }
    +            }
    +
    +        }
    +    }
    +
    +    private fun renderEvents(sourceEvents: List) {
    +        if (sourceEvents.isEmpty()) {
    +            genericItem {
    +                id("footer")
    +                title(context.getString(R.string.no_message_edits_found))
    +            }
    +        } else {
    +            var lastDate: Calendar? = null
    +            sourceEvents.sortedByDescending {
    +                it.originServerTs ?: 0
    +            }.forEachIndexed { index, timelineEvent ->
    +
    +                val evDate = Calendar.getInstance().apply {
    +                    timeInMillis = timelineEvent.originServerTs
    +                            ?: System.currentTimeMillis()
    +                }
    +                if (lastDate?.get(Calendar.DAY_OF_YEAR) != evDate.get(Calendar.DAY_OF_YEAR)) {
    +                    //need to display header with day
    +                    val dateString = if (DateUtils.isToday(evDate.timeInMillis)) context.getString(R.string.today)
    +                    else timelineDateFormatter.formatMessageDay(timelineEvent.localDateTime())
    +                    genericItemHeader {
    +                        id(evDate.hashCode())
    +                        text(dateString)
    +                    }
    +                }
    +                lastDate = evDate
    +                val cContent = getCorrectContent(timelineEvent)
    +                val body = cContent.second?.let { eventHtmlRenderer.render(it) }
    +                        ?: cContent.first
    +
    +                val nextEvent = if (index + 1 <= sourceEvents.lastIndex) sourceEvents[index + 1] else null
    +
    +                var spannedDiff: Spannable? = null
    +                if (nextEvent != null && cContent.second == null /*No diff for html*/) {
    +                    //compares the body
    +                    val nContent = getCorrectContent(nextEvent)
    +                    val nextBody = nContent.second?.let { eventHtmlRenderer.render(it) }
    +                            ?: nContent.first
    +                    val dmp = diff_match_patch()
    +                    val diff = dmp.diff_main(nextBody.toString(), body.toString())
    +                    Timber.e("#### Diff: $diff")
    +                    dmp.diff_cleanupSemantic(diff)
    +                    Timber.e("#### Diff: $diff")
    +                    spannedDiff = span {
    +                        diff.map {
    +                            when (it.operation) {
    +                                diff_match_patch.Operation.DELETE -> {
    +                                    span {
    +                                        text = it.text
    +                                        textColor = ContextCompat.getColor(context, R.color.vector_error_color)
    +                                        textDecorationLine = "line-through"
    +                                    }
    +                                }
    +                                diff_match_patch.Operation.INSERT -> {
    +                                    span {
    +                                        text = it.text
    +                                        textColor = ContextCompat.getColor(context, R.color.vector_success_color)
    +                                    }
    +                                }
    +                                else                              -> {
    +                                    span {
    +                                        text = it.text
    +                                    }
    +                                }
    +                            }
    +                        }
    +
    +                    }
    +                }
    +                genericItem {
    +                    id(timelineEvent.eventId)
    +                    title(timelineDateFormatter.formatMessageHour(timelineEvent.localDateTime()))
    +                    description(spannedDiff ?: body)
    +                }
    +            }
    +        }
    +    }
    +
    +    private fun getCorrectContent(event: Event): Pair {
    +        val clearContent = event.getClearContent().toModel()
    +        val newContent = clearContent
    +                ?.newContent
    +                ?.toModel()
    +        return (newContent?.body ?: clearContent?.body ?: "") to (newContent?.formattedBody
    +                ?: clearContent?.formattedBody)
    +    }
    +}
    \ No newline at end of file
    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
    new file mode 100644
    index 00000000..64005c3f
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewEditHistoryViewModel.kt
    @@ -0,0 +1,90 @@
    +/*
    + * 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.timeline.action
    +
    +import com.airbnb.mvrx.*
    +import com.squareup.inject.assisted.Assisted
    +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.riotx.core.platform.VectorViewModel
    +import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineDateFormatter
    +
    +
    +data class ViewEditHistoryViewState(
    +        val eventId: String,
    +        val roomId: String,
    +        val editList: Async> = Uninitialized)
    +    : MvRxState {
    +
    +    constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
    +
    +}
    +
    +class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
    +                                                           initialState: ViewEditHistoryViewState,
    +                                                           val session: Session,
    +                                                           val timelineDateFormatter: TimelineDateFormatter
    +) : VectorViewModel(initialState) {
    +
    +    private val roomId = initialState.roomId
    +    private val eventId = initialState.eventId
    +    private val room = session.getRoom(roomId)
    +            ?: throw IllegalStateException("Shouldn't use this ViewModel without a room")
    +
    +    @AssistedInject.Factory
    +    interface Factory {
    +        fun create(initialState: ViewEditHistoryViewState): ViewEditHistoryViewModel
    +    }
    +
    +
    +    companion object : MvRxViewModelFactory {
    +
    +        override fun create(viewModelContext: ViewModelContext, state: ViewEditHistoryViewState): ViewEditHistoryViewModel? {
    +            val fragment: ViewEditHistoryBottomSheet = (viewModelContext as FragmentViewModelContext).fragment()
    +            return fragment.viewEditHistoryViewModelFactory.create(state)
    +        }
    +
    +    }
    +
    +    init {
    +        loadHistory()
    +    }
    +
    +    private fun loadHistory() {
    +        setState { copy(editList = Loading()) }
    +        room.fetchEditHistory(eventId, object : MatrixCallback> {
    +            override fun onFailure(failure: Throwable) {
    +                setState {
    +                    copy(editList = Fail(failure))
    +                }
    +            }
    +
    +            override fun onSuccess(data: List) {
    +                //TODO until supported by API Add original event manually
    +                val withOriginal = data.toMutableList()
    +                room.getTimeLineEvent(eventId)?.let {
    +                    withOriginal.add(it.root)
    +                }
    +                setState {
    +                    copy(editList = Success(withOriginal))
    +                }
    +            }
    +        })
    +    }
    +
    +}
    \ No newline at end of file
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
    index 760b74da..d7e41784 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
    @@ -21,7 +21,6 @@ import android.view.LayoutInflater
     import android.view.View
     import android.view.ViewGroup
     import android.widget.LinearLayout
    -import androidx.core.view.isVisible
     import androidx.recyclerview.widget.DividerItemDecoration
     import butterknife.BindView
     import butterknife.ButterKnife
    @@ -33,7 +32,7 @@ import im.vector.riotx.EmojiCompatFontProvider
     import im.vector.riotx.R
     import im.vector.riotx.core.di.ScreenComponent
     import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
    -import kotlinx.android.synthetic.main.bottom_sheet_display_reactions.*
    +import kotlinx.android.synthetic.main.bottom_sheet_epoxylist_with_title.*
     import javax.inject.Inject
     
     /**
    @@ -49,14 +48,16 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() {
         @BindView(R.id.bottom_sheet_display_reactions_list)
         lateinit var epoxyRecyclerView: EpoxyRecyclerView
     
    -    private val epoxyController by lazy { ViewReactionsEpoxyController(emojiCompatFontProvider.typeface) }
    +    private val epoxyController by lazy {
    +        ViewReactionsEpoxyController(requireContext(), emojiCompatFontProvider.typeface)
    +    }
     
         override fun injectWith(screenComponent: ScreenComponent) {
             screenComponent.inject(this)
         }
     
         override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    -        val view = inflater.inflate(R.layout.bottom_sheet_display_reactions, container, false)
    +        val view = inflater.inflate(R.layout.bottom_sheet_epoxylist_with_title, container, false)
             ButterKnife.bind(this, view)
             return view
         }
    @@ -67,16 +68,11 @@ class ViewReactionBottomSheet : VectorBaseBottomSheetDialogFragment() {
             val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
                     LinearLayout.VERTICAL)
             epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
    +        bottomSheetTitle.text = context?.getString(R.string.reactions)
         }
     
     
         override fun invalidate() = withState(viewModel) {
    -        if (it.mapReactionKeyToMemberList() == null) {
    -            bottomSheetViewReactionSpinner.isVisible = true
    -            bottomSheetViewReactionSpinner.animate()
    -        } else {
    -            bottomSheetViewReactionSpinner.isVisible = false
    -        }
             epoxyController.setData(it)
         }
     
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
    index 57c3d265..74b3f492 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
    @@ -16,24 +16,47 @@
     
     package im.vector.riotx.features.home.room.detail.timeline.action
     
    +import android.content.Context
     import android.graphics.Typeface
     import com.airbnb.epoxy.TypedEpoxyController
    +import com.airbnb.mvrx.Fail
    +import com.airbnb.mvrx.Incomplete
    +import com.airbnb.mvrx.Success
    +import im.vector.riotx.R
    +import im.vector.riotx.core.ui.list.genericFooterItem
    +import im.vector.riotx.core.ui.list.genericLoaderItem
     
     /**
      * Epoxy controller for reaction event list
      */
    -class ViewReactionsEpoxyController(private val emojiCompatTypeface: Typeface?) : TypedEpoxyController() {
    +class ViewReactionsEpoxyController(private val context: Context, private val emojiCompatTypeface: Typeface?)
    +    : TypedEpoxyController() {
     
         override fun buildModels(state: DisplayReactionsViewState) {
    -        val map = state.mapReactionKeyToMemberList() ?: return
    -        map.forEach {
    -            reactionInfoSimpleItem {
    -                id(it.eventId)
    -                emojiTypeFace(emojiCompatTypeface)
    -                timeStamp(it.timestamp)
    -                reactionKey(it.reactionKey)
    -                authorDisplayName(it.authorName ?: it.authorId)
    +        when (state.mapReactionKeyToMemberList) {
    +            is Incomplete -> {
    +                genericLoaderItem {
    +                    id("Spinner")
    +                }
    +            }
    +            is Fail       -> {
    +                genericFooterItem {
    +                    id("failure")
    +                    text(context.getString(R.string.unknown_error))
    +                }
    +            }
    +            is Success    -> {
    +                state.mapReactionKeyToMemberList()?.forEach {
    +                    reactionInfoSimpleItem {
    +                        id(it.eventId)
    +                        emojiTypeFace(emojiCompatTypeface)
    +                        timeStamp(it.timestamp)
    +                        reactionKey(it.reactionKey)
    +                        authorDisplayName(it.authorName ?: it.authorId)
    +                    }
    +                }
                 }
             }
    +
         }
     }
    \ No newline at end of file
    diff --git a/vector/src/main/res/layout/bottom_sheet_display_reactions.xml b/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml
    similarity index 71%
    rename from vector/src/main/res/layout/bottom_sheet_display_reactions.xml
    rename to vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml
    index 0f5b6365..9b3ffb26 100644
    --- a/vector/src/main/res/layout/bottom_sheet_display_reactions.xml
    +++ b/vector/src/main/res/layout/bottom_sheet_epoxylist_with_title.xml
    @@ -7,23 +7,14 @@
         android:orientation="vertical">
     
         
    -
    -    
    -
    +        android:textSize="16sp"
    +        tools:text="@string/reactions" />
     
         
    +
    \ No newline at end of file
    diff --git a/vector/src/main/res/layout/item_generic_loader.xml b/vector/src/main/res/layout/item_generic_loader.xml
    new file mode 100644
    index 00000000..53460c4e
    --- /dev/null
    +++ b/vector/src/main/res/layout/item_generic_loader.xml
    @@ -0,0 +1,6 @@
    +
    \ No newline at end of file
    diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
    index 7c1d39c4..9a20356e 100644
    --- a/vector/src/main/res/values/strings_riotX.xml
    +++ b/vector/src/main/res/values/strings_riotX.xml
    @@ -21,4 +21,8 @@
         Use the old app
     
     
    +
    +    Message Edits
    +    No edits found
    +
     
    \ No newline at end of file
    
    From fc1c0caea35f6f3e7bc4c05abcb0c7bdf57ba75e Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Mon, 15 Jul 2019 14:08:51 +0200
    Subject: [PATCH 22/75] Avoid displaying two loaders if there is no elements
     between them
    
    ---
     .../timeline/TimelineEventController.kt       | 20 ++++++++++++-------
     1 file changed, 13 insertions(+), 7 deletions(-)
    
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt
    index b6cf00a3..0b78f815 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/TimelineEventController.kt
    @@ -18,7 +18,6 @@ package im.vector.riotx.features.home.room.detail.timeline
     
     import android.os.Handler
     import android.os.Looper
    -import android.util.LongSparseArray
     import android.view.View
     import androidx.recyclerview.widget.DiffUtil
     import androidx.recyclerview.widget.ListUpdateCallback
    @@ -84,7 +83,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
         }
     
         private val collapsedEventIds = linkedSetOf()
    -    private val mergeItemCollapseStates = HashMap()
    +    private val mergeItemCollapseStates = HashMap()
         private val modelCache = arrayListOf()
     
         private var currentSnapshot: List = emptyList()
    @@ -178,16 +177,19 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
         }
     
         override fun buildModels() {
    -        LoadingItem_()
    +        val loaderAdded = LoadingItem_()
                     .id("forward_loading_item")
                     .addWhen(Timeline.Direction.FORWARDS)
     
             val timelineModels = getModels()
             add(timelineModels)
     
    -        LoadingItem_()
    -                .id("backward_loading_item")
    -                .addWhen(Timeline.Direction.BACKWARDS)
    +        // Avoid displaying two loaders if there is no elements between them
    +        if (!loaderAdded || timelineModels.isNotEmpty()) {
    +            LoadingItem_()
    +                    .id("backward_loading_item")
    +                    .addWhen(Timeline.Direction.BACKWARDS)
    +        }
         }
     
         // Timeline.LISTENER ***************************************************************************
    @@ -310,9 +312,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Tim
             }
         }
     
    -    private fun LoadingItem_.addWhen(direction: Timeline.Direction) {
    +    /**
    +     * Return true if added
    +     */
    +    private fun LoadingItem_.addWhen(direction: Timeline.Direction): Boolean {
             val shouldAdd = timeline?.hasMoreToLoad(direction) ?: false
             addIf(shouldAdd, this@TimelineEventController)
    +        return shouldAdd
         }
     
         fun searchPositionOfEvent(eventId: String): Int? {
    
    From 3c250882436bc28ac90fe4756e8681e649d9b45b Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Mon, 15 Jul 2019 16:58:27 +0200
    Subject: [PATCH 23/75] Filter rooms
    
    ---
     vector/src/main/AndroidManifest.xml           |  1 +
     .../vector/riotx/core/di/ScreenComponent.kt   |  8 +-
     .../vector/riotx/core/extensions/Activity.kt  |  4 +-
     .../riotx/features/home/HomeActivity.kt       |  6 ++
     .../home/room/detail/RoomDetailActivity.kt    |  2 +-
     .../room/filtered/FilteredRoomFooterItem.kt   | 49 +++++++++++
     .../room/filtered/FilteredRoomsActivity.kt    | 81 +++++++++++++++++++
     .../home/room/list/RoomListActions.kt         |  2 +
     .../room/list/RoomListDisplayModeFilter.kt    |  7 +-
     .../home/room/list/RoomListFragment.kt        | 42 +++++++---
     .../RoomListNameFilter.kt}                    | 19 ++++-
     .../home/room/list/RoomListViewModel.kt       | 19 ++++-
     .../home/room/list/RoomListViewState.kt       |  1 +
     .../home/room/list/RoomSummaryController.kt   | 63 +++++++++++----
     .../features/navigation/DefaultNavigator.kt   |  6 ++
     .../riotx/features/navigation/Navigator.kt    |  2 +
     vector/src/main/res/drawable/ic_filter.xml    | 20 +++++
     .../res/layout/activity_filtered_rooms.xml    | 42 ++++++++++
     .../res/layout/item_room_filter_footer.xml    | 43 ++++++++++
     vector/src/main/res/menu/home.xml             |  9 ++-
     vector/src/main/res/values/strings_riotX.xml  |  7 ++
     21 files changed, 390 insertions(+), 43 deletions(-)
     create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomFooterItem.kt
     create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomsActivity.kt
     rename vector/src/main/java/im/vector/riotx/features/home/room/{VisibleRoomStore.kt => list/RoomListNameFilter.kt} (55%)
     create mode 100644 vector/src/main/res/drawable/ic_filter.xml
     create mode 100644 vector/src/main/res/layout/activity_filtered_rooms.xml
     create mode 100644 vector/src/main/res/layout/item_room_filter_footer.xml
    
    diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
    index 7e5242ab..c7cf4c2b 100644
    --- a/vector/src/main/AndroidManifest.xml
    +++ b/vector/src/main/AndroidManifest.xml
    @@ -60,6 +60,7 @@
                 android:label="@string/title_activity_emoji_reaction_picker" />
             
             
    +        
             
             
     
    diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    index 611ee171..c54cb942 100644
    --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt
    @@ -32,10 +32,14 @@ import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupStep1Frag
     import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupStep2Fragment
     import im.vector.riotx.features.crypto.keysbackup.setup.KeysBackupSetupStep3Fragment
     import im.vector.riotx.features.crypto.verification.SASVerificationIncomingFragment
    -import im.vector.riotx.features.home.*
    +import im.vector.riotx.features.home.HomeActivity
    +import im.vector.riotx.features.home.HomeDetailFragment
    +import im.vector.riotx.features.home.HomeDrawerFragment
    +import im.vector.riotx.features.home.HomeModule
     import im.vector.riotx.features.home.group.GroupListFragment
     import im.vector.riotx.features.home.room.detail.RoomDetailFragment
     import im.vector.riotx.features.home.room.detail.timeline.action.*
    +import im.vector.riotx.features.home.room.filtered.FilteredRoomsActivity
     import im.vector.riotx.features.home.room.list.RoomListFragment
     import im.vector.riotx.features.invite.VectorInviteView
     import im.vector.riotx.features.login.LoginActivity
    @@ -130,6 +134,8 @@ interface ScreenComponent {
     
         fun inject(imageMediaViewerActivity: ImageMediaViewerActivity)
     
    +    fun inject(filteredRoomsActivity: FilteredRoomsActivity)
    +
         fun inject(vectorInviteView: VectorInviteView)
     
         fun inject(videoMediaViewerActivity: VideoMediaViewerActivity)
    diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt
    index 75817779..b4afb569 100644
    --- a/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Activity.kt
    @@ -23,8 +23,8 @@ fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int) {
         supportFragmentManager.inTransaction { add(frameId, fragment) }
     }
     
    -fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int) {
    -    supportFragmentManager.inTransaction { replace(frameId, fragment) }
    +fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int, tag: String? = null) {
    +    supportFragmentManager.inTransaction { replace(frameId, fragment, tag) }
     }
     
     fun AppCompatActivity.addFragmentToBackstack(fragment: Fragment, frameId: Int, tag: String? = null) {
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt
    index 67d1af4e..4ec2c0ad 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt
    @@ -41,6 +41,7 @@ import im.vector.riotx.core.platform.ToolbarConfigurable
     import im.vector.riotx.core.platform.VectorBaseActivity
     import im.vector.riotx.core.pushers.PushersManager
     import im.vector.riotx.features.disclaimer.showDisclaimerDialog
    +import im.vector.riotx.features.navigation.Navigator
     import im.vector.riotx.features.notifications.NotificationDrawerManager
     import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
     import im.vector.riotx.features.workers.signout.SignOutViewModel
    @@ -64,6 +65,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
         @Inject lateinit var activeSessionHolder: ActiveSessionHolder
         @Inject lateinit var homeActivityViewModelFactory: HomeActivityViewModel.Factory
         @Inject lateinit var homeNavigator: HomeNavigator
    +    @Inject lateinit var navigator: Navigator
         @Inject lateinit var vectorUncaughtExceptionHandler: VectorUncaughtExceptionHandler
         @Inject lateinit var pushManager: PushersManager
         @Inject lateinit var notificationDrawerManager: NotificationDrawerManager
    @@ -192,6 +194,10 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
                     bugReporter.openBugReportScreen(this, false)
                     return true
                 }
    +            R.id.menu_home_filter -> {
    +                navigator.openRoomsFiltering(this)
    +                return true
    +            }
             }
     
             return true
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt
    index 9108f5d0..6ad9a61f 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailActivity.kt
    @@ -35,7 +35,7 @@ class RoomDetailActivity : VectorBaseActivity(), ToolbarConfigurable {
             super.onCreate(savedInstanceState)
             if (isFirstCreation()) {
                 val roomDetailArgs: RoomDetailArgs = intent?.extras?.getParcelable(EXTRA_ROOM_DETAIL_ARGS)
    -                                                 ?: return
    +                    ?: return
                 val roomDetailFragment = RoomDetailFragment.newInstance(roomDetailArgs)
                 replaceFragment(roomDetailFragment, R.id.roomDetailContainer)
             }
    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
    new file mode 100644
    index 00000000..0e916a0e
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/filtered/FilteredRoomFooterItem.kt
    @@ -0,0 +1,49 @@
    +/*
    + * 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.filtered
    +
    +import android.widget.Button
    +import com.airbnb.epoxy.EpoxyAttribute
    +import com.airbnb.epoxy.EpoxyModelClass
    +import im.vector.riotx.R
    +import im.vector.riotx.core.epoxy.VectorEpoxyHolder
    +import im.vector.riotx.core.epoxy.VectorEpoxyModel
    +import im.vector.riotx.features.home.room.list.widget.FabMenuView
    +
    +@EpoxyModelClass(layout = R.layout.item_room_filter_footer)
    +abstract class FilteredRoomFooterItem : VectorEpoxyModel() {
    +
    +    @EpoxyAttribute
    +    var listener: FilteredRoomFooterItemListener? = null
    +
    +    override fun bind(holder: Holder) {
    +        holder.createRoomButton.setOnClickListener { listener?.createRoom() }
    +        holder.createDirectChat.setOnClickListener { listener?.createDirectChat() }
    +        holder.openRoomDirectory.setOnClickListener { listener?.openRoomDirectory() }
    +    }
    +
    +    class Holder : VectorEpoxyHolder() {
    +        val createRoomButton by bind